Category: Web Exploitation
Difficulty: Medium
Points: 100
Description
The server lets you upload two PDF files. It grants the flag only if both files have the same MD5 hash but different contents. This requires exploiting a known weakness in the MD5 algorithm.
Overview
- The challenge tests whether two uploaded files produce an identical MD5 hash while having different byte content.
- MD5 is a broken cryptographic hash function: researchers demonstrated practical collision generation in 2004, and pre-computed collision pairs have been publicly available since.
- The solution is to obtain a known MD5 collision pair and submit those two files.
Background: MD5 Collision
- A hash function collision occurs when two distinct inputs produce the same hash output.
- MD5 is particularly weak because collisions can be generated in seconds on modern hardware using the Chosen-Prefix Collision technique.
- The
fastcolltool by Marc Stevens generates pairs of files with identical MD5 hashes given any shared prefix. - In practice, pre-computed PDF collision pairs are widely available for exactly this type of challenge.
Solution
Step 1: Obtain a Collision Pair
- Two PDFs (
hello.pdfanderase.pdf) are prepared such thatmd5(hello.pdf) == md5(erase.pdf)but their byte contents differ. - These can be generated with
fastcollor downloaded from known collision repositories.
Step 2: Upload Both Files
- The server expects a
multipart/form-dataPOST with fieldsfile1,file2, andsubmit. requests_toolbelt.MultipartEncoderis used to preserve the correct content type boundary.
from requests_toolbelt import MultipartEncoder
mp_encoder = MultipartEncoder(fields={
"file1": ("hello.pdf", open("hello.pdf", "rb"), "application/pdf"),
"file2": ("hello.pdf", open("erase.pdf", "rb"), "application/pdf"),
"submit": "Upload",
})
r = requests.post(url, data=mp_encoder,
headers={"Content-Type": mp_encoder.content_type})
- Both files are uploaded with the filename
hello.pdfso the server does not reject them on name mismatch.
Step 3: Extract the Flag
- The server confirms the MD5 match and returns the flag in the HTML response body.
soup = BeautifulSoup(r.text, "html.parser")
result = [c for c in soup.get_text(separator="\n").splitlines()
if "picoCTF{" in c][0]
Flag
picoCTF{c0ngr4ts_u_r_1nv1t3d_73b0c8ad}