导语 这也能偷鸡?
[HCTF 2018]admin 偷鸡 看到有个login,看到题目是admin,于是先随便试试密码,试了123,直接登进去了(啊这
但是感觉应该还有芝士点
分析 这个webapp有login和register两个功能
随便注册一个账号并登陆,发现没有如之前一样显示flag,页面里有个注释说you are not admin,所以是登陆了admin即可获取flag。
在change password里可以看到一个github
但是发现403木得了,看了别人的writeup,是源码
from flask import Flask, render_template, url_for, flash, request, redirect, session, make_responsefrom flask_login import logout_user, LoginManager, current_user, login_userfrom app import app, dbfrom config import Configfrom app.models import Userfrom forms import RegisterForm, LoginForm, NewpasswordFormfrom twisted.words.protocols.jabber.xmpp_stringprep import nodeprepfrom io import BytesIOfrom 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() response = make_response(buf_str) response.headers['Content-Type' ] = 'image/gif' 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
unicode欺骗 审计代码可以看到在register、login还有change都用了strlower()将用户名转小写,python自带的转小写是lower(),所以去看看strlower()
查看strlower()函数具体实现
def strlower (username ): username = nodeprep.prepare(username) return username
这里用的nodeprep.prepare函数,而nodeprep是从Twisted模块导入的,在requirements.txt文件中发现 Twisted==10.2.0
,是非常老的版本
https://symbl.cc/en/search/?q=small+capital
small capital进过一次nodeprep.prepare会变成大写字母,在经过一次nodeprep.prepare会变成小写字母。小写字母nodeprep.prepare后还是他自己
所以注册一个ᴬdmin账号,密码自己设置,在注册完成之后变成Admin,登录
修改密码,此时变成admin,相当于直接改了admin的密码
session伪造 在index.html可以看到,如果把session里的name修改为admin,即可让他输出flag
flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的。
用抄来的脚本解析
import sysimport zlibfrom base64 import b64decodefrom flask.sessions import session_json_serializerfrom itsdangerous import base64_decodedef 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还需要知道SECRET_KEY,然后我们在config.py里发现了SECRET_KEY
SECRET_KEY = os.environ.get('SECRET_KEY' ) or 'ckj123'
添加SECRET_KEY,把name改成admin,用github上的脚本加密
https://github.com/noraj/flask-session-cookie-manager
然后用生成的session来request即可
同时找答案的时候看到这个,觉得很不错
https://www.leavesongs.com/PENETRATION/client-session-security.html#
参考 https://www.cnblogs.com/chrysanthemum/p/11722351.html
https://blog.csdn.net/weixin_44677409/article/details/100733581
https://xz.aliyun.com/t/3569