buuctf_python审计_[HCTF 2018]admin
题目:[HCTF 2018]admin 1
出处:https://buuoj.cn/challenges
知识点:
1.发现源码泄露的能力
2.python审计
3.flask框架知识
4.flask session加解密方法
题面:
解题步骤:
法二:
1.本题的/change页面的前端代码中,有一句 <!-- https://github.com/woadsl1234/hctf_flask/ -->
,这说明这题的源代码已经在github上泄露
2.先看看https://github.com/woadsl1234/hctf_flask/config.py看到如下内容:
3.由此猜测可能可以直接访问数据库,我们来测试一下3306是否存活telnet一下:
失败了。
4.既然源码泄露了,config.py里面的信息也不能帮我登录到数据库,我也不想对着wp照抄,我决定来审计一下源码
python和php不同所有传输的参数都通过route来接收,我们下载源码利用search and replace
这个软件查询route(
我们可以看见,整个程序用户可以控制的包其实也就这几个,而这次代码泄漏的提示这么难找非要放在/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
找到了,secret_key
为ckj123
:
开始使用白嫖来的解密脚本:
#!/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:
然后在解密后的session中将asdf改成admin,再加密:
通过burp将加密后的session修改到xxxx/change
的包里
成功得到flag。
总结:这道题做完半条命没了,明天见短短不宜晚睡,祝看文章的大佬们做一个苏维埃梦。
参考:
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