导语 这也能偷鸡?
[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