Writeups/TryHackMe/When Hearts Collide - TryHackMe Writeup | MD5 Collision to Match Flag
TryHackMeMediumRoom

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

ItemDetail
GoalTrigger a “match” and retrieve the flag
Attack chainUnderstand match logic → generic MD5 collision (fastcoll) → upload file A → upload file B (same hash) → match → flag
Key conceptsMD5 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:

text
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

TypeDescriptionDifficulty
GenericTwo arbitrary files with the same MD5Feasible (e.g. fastcoll in seconds)
Chosen-prefixTwo files with chosen prefixes and same MD5Much 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:

Example (optional: use the dog image as a prefix so the files look more like images):

bash
docker run --rm -it -v "$PWD:/work" -w /work brimstone/fastcoll \ --prefixfile dog.jpg -o collision1 collision2

Then:

bash
md5sum collision1 collision2

Both 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

text
THM{*redacted*}

(Replace with the actual flag when you solve the room.)


##Attack chain summary

StepAction
1Confirm app uses MD5 and “match” shows flag
2Verify server doesn’t normalize uploads (hash preserved)
3Conclude “match” = duplicate hash in store (any source)
4Generate collision pair with fastcoll
5Upload first file → hash stored
6Upload 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


This writeup is part of my Love At First Breach 2026 event writeups.

$ echo "Open to Red Team Security Research and Security Engineering roles."

> Open to Red Team Security Research and Security Engineering roles.

$ uptime

> Portfolio online since 2024 | Last updated: Mar 2026

"No one is useless in this world who lightens the burdens of another." — Charles Dickens

Considered a small donation if you found any of the walkthrough or blog posts helpful. Much appreciate :)

Buy me a coffee

© 2026 Shivang Tiwari. Built with Next.js. Hack the planet.