欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

buuctf_python审计_[HCTF 2018]admin

程序员文章站 2022-06-23 21:20:13
题目:[HCTF 2018]admin 1出处:https://buuoj.cn/challenges知识点:1.发现源码泄露的能力2.python审计3.flask框架知识4.flask session加解密方法题面:解题步骤:法二:1.本题的/change页面的前端代码中,有一句 ,这说明这题的源代码已经在github上泄露2.先看看https://github.com...

题目:[HCTF 2018]admin 1

出处:https://buuoj.cn/challenges

知识点

1.发现源码泄露的能力
2.python审计
3.flask框架知识
4.flask session加解密方法

题面buuctf_python审计_[HCTF 2018]admin
解题步骤

法二
1.本题的/change页面的前端代码中,有一句 <!-- https://github.com/woadsl1234/hctf_flask/ -->,这说明这题的源代码已经在github上泄露
buuctf_python审计_[HCTF 2018]admin
2.先看看https://github.com/woadsl1234/hctf_flask/config.py看到如下内容:
buuctf_python审计_[HCTF 2018]admin
3.由此猜测可能可以直接访问数据库,我们来测试一下3306是否存活telnet一下:
buuctf_python审计_[HCTF 2018]admin
失败了。

4.既然源码泄露了,config.py里面的信息也不能帮我登录到数据库,我也不想对着wp照抄,我决定来审计一下源码
buuctf_python审计_[HCTF 2018]admin
python和php不同所有传输的参数都通过route来接收,我们下载源码利用search and replace这个软件查询route(
buuctf_python审计_[HCTF 2018]admin
我们可以看见,整个程序用户可以控制的包其实也就这几个,而这次代码泄漏的提示这么难找非要放在/change里面基本应该是在暗示,问题出在/change这里,我们打开这个route.py文件读一下源代码,避免浪费时间,不太重要的源码的功能我直接写在程序的注释里:

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')
#处理xxxxx/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('/')
#处理xxxxx/的请求
@app.route('/index')
#处理xxxxx/index的请求
def index():
    return render_template('index.html', title = 'hctf')

@app.route('/register', methods = ['GET', 'POST'])
#处理xxxx/register?xxx=xxx
def register():

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

    form = RegisterForm()
    if request.method == 'POST':
    	#读取form-data的数据username为name
        name = strlower(form.username.data)
        #通过session检验验证码是否正确(此处验证码似乎可以绕过)
        if session.get('image').lower() != form.verify_code.data.lower():
            flash('Wrong verify code.')
            return render_template('register.html', title = 'register', form=form)
        #检验用户名是否被注册过,若绕过这一步可能可以直接重置admin的密码,但好像不行
        if User.query.filter_by(username = name).first():
            flash('The username has been registered')
            return redirect(url_for('register'))
        #直接将密码赋予从username读取到的user
        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']
        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':
    	#session['name']是login时候赋值的其值为当前用户的用户名,但session分明是一串乱码,查阅资料之后了解到这涉及到了flask session的一个漏洞,flask将session存在客户端且只通过一个时间戳和secret_key对session中用户名进行了加密,仅需要session本身和secret_key就可以对破解出name。
        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

目前我们发现了这题最容易得到admin权限的方法是利用/change修改admin的密码,这可能需要利用漏洞flask session的漏洞(实际该漏洞可利用还是由于源码泄漏):flask将session存在客户端且只通过一个时间戳和secret_key对session中用户名进行了加密,仅需要session本身和secret_key就可以对破解出name。

我们来读取一下secret_key
buuctf_python审计_[HCTF 2018]admin
找到了,secret_keyckj123
开始使用白嫖来的解密脚本:

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

得到解密后的session:
buuctf_python审计_[HCTF 2018]admin

然后在解密后的session中将asdf改成admin,再加密:
buuctf_python审计_[HCTF 2018]admin
通过burp将加密后的session修改到xxxx/change的包里
buuctf_python审计_[HCTF 2018]admin
成功得到flag。
buuctf_python审计_[HCTF 2018]admin
总结:这道题做完半条命没了,明天见短短不宜晚睡,祝看文章的大佬们做一个苏维埃梦。

参考
https://www.cnblogs.com/apossin/p/10083937.html
https://blog.csdn.net/rfrder/article/details/109188719
https://github.com/noraj/flask-session-cookie-manager

本文地址:https://blog.csdn.net/ratear/article/details/109788823