When Hearts Collide - TryHackMe Writeup | MD5 Collision to Match Flag
When Hearts Collide TryHackMe writeup — Love At First Breach 2026: MD5 match logic flaw, generic collision with fastcoll, and flag from duplicate-hash match.
##TryHackMe Room — When Hearts Collide (Love At First Breach 2026)
Matchmaker pairs humans with dogs by comparing your photo’s MD5 hash.
The app claims it compares your upload’s MD5 to “every doggo snapshot” and reveals the flag when there’s a match. At first that sounds like a chosen-prefix MD5 collision (hard). By testing the actual behavior, I found the backend treats any duplicate hash — including between two user uploads — as a match. So the task became: generate two different files with the same MD5 (generic collision), upload both, and let the app “match” them. This writeup documents that path and why chosen-prefix was unnecessary.
##Overview
| Item | Detail |
|---|---|
| Goal | Trigger a “match” and retrieve the flag |
| Attack chain | Understand match logic → generic MD5 collision (fastcoll) → upload file A → upload file B (same hash) → match → flag |
| Key concepts | MD5 collision, generic vs chosen-prefix, application logic vs crypto hardness |
##Reconnaissance
The app had a simple upload form and explained that it compares your photo’s MD5 to existing “doggo” hashes. So:
- >MD5 is used as an identity for files.
- >A “match” likely means:
md5(upload) in stored_hashes→ show flag.
I checked for sample content; e.g. a profile like /view/<uuid> with an image at /static/uploads/<uuid>.jpg. I downloaded that file and confirmed its MD5 locally (e.g. md5sum). So we had a known “dog” image and its hash — but we did not need to collide with it.
##Understanding the upload flow
Uploading a file returned a redirect like:
302 → /upload_success/<uuid>The success page showed either “No match found” or, when a match occurred, a div with the flag (e.g. data-match="true" and a match-flag span). So the backend was comparing the upload’s hash against something. The critical question: was it only a fixed set of “dog” hashes, or any previously stored hash (including from earlier uploads)?
##Key behavioral discovery
I tested whether the server modified uploads (e.g. recompressing images), which would break simple collision reuse:
- >I appended a byte to a file, uploaded it, and re-downloaded it (if possible) or checked response.
- >The hash of what I uploaded matched what the server used (no normalization observed).
So the server hashed raw uploaded bytes. That kept collision attacks in play. Then I tested the match condition: after uploading file A, I uploaded file B with the same MD5 as A (using a collision pair). The app reported a match and showed the flag. So the logic was effectively:
- >“Match” = “this upload’s hash already exists in our store” (including from prior uploads).
So we did not need to collide with the original dog image — only with ourselves: upload two different files with the same MD5.
##Key concept: Generic vs chosen-prefix MD5 collision
| Type | Description | Difficulty |
|---|---|---|
| Generic | Two arbitrary files with the same MD5 | Feasible (e.g. fastcoll in seconds) |
| Chosen-prefix | Two files with chosen prefixes and same MD5 | Much harder (hours/days) |
This challenge only required a generic collision: produce any pair of different files with the same hash, upload the first to “register” the hash, then upload the second to trigger the match.
##Generating a collision pair with fastcoll
I used fastcoll to generate two different files with the same MD5:
- >GitHub: brimstone/fastcoll
- >Docker:
brimstone/fastcoll
Example (optional: use the dog image as a prefix so the files look more like images):
docker run --rm -it -v "$PWD:/work" -w /work brimstone/fastcoll \
--prefixfile dog.jpg -o collision1 collision2Then:
md5sum collision1 collision2Both hashes were identical. Upload collision1 first; then upload collision2. The second upload’s hash is already in the store, so the app returns a match and displays the flag.
##Flag
THM{*redacted*}(Replace with the actual flag when you solve the room.)
##Attack chain summary
| Step | Action |
|---|---|
| 1 | Confirm app uses MD5 and “match” shows flag |
| 2 | Verify server doesn’t normalize uploads (hash preserved) |
| 3 | Conclude “match” = duplicate hash in store (any source) |
| 4 | Generate collision pair with fastcoll |
| 5 | Upload first file → hash stored |
| 6 | Upload second file → same hash → match → flag |
##Pitfalls and notes
- >Assuming chosen-prefix is required: That would mean colliding with the known dog image and could take a long time. Testing behavior (does a second upload with the same hash trigger a match?) revealed the simpler path.
- >Real-world: Don’t use MD5 for security-sensitive comparisons; don’t treat “match” as “matches this specific set” if user input can extend that set.
##References and tools
- >TryHackMe — Love At First Breach
- >fastcoll — MD5 collision generator
- >Docker image:
brimstone/fastcoll - >MD5 and collision background (e.g. collision attacks)
This writeup is part of my Love At First Breach 2026 event writeups.