After the Last Byte: Finding an Archive Stapled to an Image
The challenge
A recruitment mailbox received a CV with a profile photo attached. The gateway checked the attachment, found a valid image, rendered a thumbnail for the moderation queue and let it through. The thumbnail is fine. The file is not: a JPEG decoder stops at a specific two-byte marker and ignores everything after it, and this file keeps going for another sixty-seven bytes. Read what was stapled on the end and give the name of the file it carries. All 217 bytes are below, the first four are highlighted, and the ASCII column on the right is where this file gives itself away without decoding anything.
What you'll learn
- Recognise the JPEG end-of-image marker and what decoders do with it
- Identify a second file signature inside a single file
- Locate a ZIP entry name from the local file header layout
- Explain why a file can be two valid formats at once
- Describe the difference between validating a file and identifying it
Skills tested
Prerequisites
- Reading hex and ASCII side by side
- What a file signature is
How it works
Most formats are parsed from the front and stop when they say they have finished. JPEG is a run of markers ending at FF D9, and every decoder treats that as the end of the file, regardless of how many bytes remain. ZIP is the opposite: a reader starts at the tail, finds the end of central directory record, and follows offsets backwards into the file.
Put a ZIP after a JPEG's FF D9 and both parsers are satisfied by the same bytes. An image viewer renders a picture, an archive tool lists an entry, and neither is being fooled. The file genuinely is both. Renaming the extension is enough to switch which one you get.
Gateways fall for this because of the question they ask. Validating that an attachment is a well-formed JPEG returns a truthful yes, and a thumbnail proves it. Identifying the file means asking what else is in there, which requires reading to the end rather than stopping where the first format says to stop.
Common mistakes
- Answering ZIP or archive. The format is how you find the answer, not the answer. The label asks for a name.
- Reading the name at the start of the PK header. Thirty bytes of fields come first. Start counting at the signature and the ASCII column will show you where the text begins.
- Assuming the file is corrupt. Both halves are structurally valid, which is exactly why it passed.
- Stopping at the thumbnail. The image is real and renders correctly. It is there to be looked at.
How to defend against it
Inspect the whole file, not the part the first parser claims.
- Compare the declared length of the image against the size on disk. Trailing bytes after
FF D9are a finding on their own and cost one comparison to detect. - Re-encode uploaded images server-side rather than storing what arrived. A decode and re-encode discards everything a decoder ignored, including the appended archive.
- Scan attachments for every signature present, not just the one at offset zero, and treat a second container as a quarantine event.
- Do not trust the extension or the MIME type the sender supplied. Both are labels, and the bytes here disagree with both.