Test files › Images › fake-jpeg-is-executable.jpg
Fake JPEG with wrong magic bytes
A file named .jpg whose magic bytes are the DOS/PE "MZ" signature. Contains no executable code - just the header and filler.
↓ Download fake-jpeg-is-executable.jpg| Filename | fake-jpeg-is-executable.jpg |
| Format | JPEG (spoofed) |
| Size | 2.1 KB (2,131 bytes) |
| MIME type | image/jpeg |
| SHA-256 | 4ba83fec93b79c99a1d2f2be5e10620d7ca634766d5a783a8d9a7a19c47d610f |
What this file catchesUpload validation that trusts the file extension or the client-supplied Content-Type instead of sniffing actual magic bytes. This is the number one file-upload bypass.
Handle it in code
Each snippet below was run against this exact file with Python 3.14.4 and Node 24.15.0 when this page was built, and the output shown is what it printed.
Python
Identify the file by its first bytes and compare that with the name. Never trust the extension or the Content-Type the browser sent.
SIGNATURES = {
b"\x89PNG\r\n\x1a\n": "png", b"\xff\xd8\xff": "jpg", b"GIF8": "gif",
b"%PDF-": "pdf", b"PK\x03\x04": "zip", b"MZ": "exe", b"\x7fELF": "elf",
}
def sniff(path):
with open(path, "rb") as f:
head = f.read(16)
return next((ext for sig, ext in SIGNATURES.items() if head.startswith(sig)), None)
name = "fake-jpeg-is-executable.jpg"
actual = sniff(name)
claimed = name.rsplit(".", 1)[1].lower() if "." in name else None
claimed = {"jpeg": "jpg", "jpe": "jpg"}.get(claimed, claimed)
verdict = "ok" if actual == claimed else "MISMATCH: reject it"
print(f"name says {claimed or 'nothing'}, bytes say {actual or 'unknown'}: {verdict}")Run against this file, it prints: name says jpg, bytes say exe: MISMATCH: reject it
Node.js
The same check in Node, reading only the first 16 bytes.
const fs = require('node:fs');
const SIGNATURES = [['89504e470d0a1a0a', 'png'], ['ffd8ff', 'jpg'], ['47494638', 'gif'],
['255044462d', 'pdf'], ['504b0304', 'zip'], ['4d5a', 'exe'], ['7f454c46', 'elf']];
const name = 'fake-jpeg-is-executable.jpg';
const head = Buffer.alloc(16);
const fd = fs.openSync(name, 'r');
fs.readSync(fd, head, 0, 16, 0);
fs.closeSync(fd);
const hex = head.toString('hex');
const actual = (SIGNATURES.find(([sig]) => hex.startsWith(sig)) || [])[1] || 'unknown';
const dot = name.lastIndexOf('.');
const claimed = dot > 0 ? name.slice(dot + 1).toLowerCase().replace(/^jpe?g$/, 'jpg') : 'nothing';
console.log('name says ' + claimed + ', bytes say ' + actual + ': ' + (actual === claimed ? 'ok' : 'MISMATCH: reject it'));Run against this file, it prints: name says jpg, bytes say exe: MISMATCH: reject it
Related guides and tools
Sample image filesFile signaturesMIME typesUpload validation checklistCheck a file's real typeVerify its SHA-256
Download & verify
curl -fsSL -o "fake-jpeg-is-executable.jpg" "https://files.hexaqa.com/image/fake-jpeg-is-executable.jpg"
curl -fsSL "https://files.hexaqa.com/image/fake-jpeg-is-executable.jpg" | sha256sum
# expect: 4ba83fec93b79c99a1d2f2be5e10620d7ca634766d5a783a8d9a7a19c47d610f