#!/usr/bin/env python3 """Generate synthetic hostile fixtures for image-ingress security tests. All content is synthetic test data. No real medical images, no external uploads. """ import os from PIL import Image, PngImagePlugin D = os.path.join(os.path.dirname(__file__), "..", "tests", "fixtures") D = os.path.abspath(D) os.makedirs(D, exist_ok=True) img = Image.new("RGB", (64, 64)) for y in range(64): for x in range(64): img.putpixel((x, y), (x * 4 % 256, y * 4 % 256, 128)) # 1. Clean small JPEG (valid control) img.save(os.path.join(D, "ingress-clean.jpg"), "JPEG", quality=90) # 2. JPEG with EXIF metadata (Make/Model + GPS IFD pointer) ex = img.getexif() ex[0x010F] = "HostileCam" ex[0x0110] = "Model-X" gps_ifd = ex.get_ifd(0x8825) gps_ifd[1] = "N" # GPSLatitudeRef gps_ifd[2] = (44, 30, 0) # GPSLatitude (degrees, minutes, seconds) gps_ifd[4] = (68, 15, 0) # GPSLongitude img.save(os.path.join(D, "ingress-exif.jpg"), "JPEG", quality=90, exif=ex) # 3. PNG with tEXt metadata chunks png_meta = PngImagePlugin.PngInfo() png_meta.add_text("Comment", "sensitive-metadata") png_meta.add_text("GPS", "lat:44.0 lon:-68.0") img.save(os.path.join(D, "ingress-metadata.png"), "PNG", pnginfo=png_meta) # 4. Spoofed content: HTML masquerading as an image with open(os.path.join(D, "ingress-spoofed.html"), "wb") as f: f.write(b"
not an image") # 5. GIF-header polyglot with embedded script payload poly = (b"GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00" b"\x00\x02\x00;" + b"" * 4) with open(os.path.join(D, "ingress-polyglot.gif"), "wb") as f: f.write(poly) # 6. ZIP-in-JPEG polyglot (GIFAR-style) jpg_bytes = open(os.path.join(D, "ingress-clean.jpg"), "rb").read() zip_poly = jpg_bytes[:2] + b"PK\x03\x04" + jpg_bytes[2:10] + b"PK\x05\x06" + b"\x00" * 18 with open(os.path.join(D, "ingress-zip-polyglot.jpg"), "wb") as f: f.write(zip_poly) # 7. Truncated JPEG (SOI present, cut before EOI) with open(os.path.join(D, "ingress-truncated.jpg"), "wb") as f: f.write(jpg_bytes[: len(jpg_bytes) // 2]) # 8. Decompression bomb: 12000x12000 sparse PNG, tiny on disk bomb = Image.new("L", (12000, 12000), 7) bomb.save(os.path.join(D, "ingress-bomb.png"), "PNG", optimize=True) print("bomb size:", os.path.getsize(os.path.join(D, "ingress-bomb.png"))) # 9. Oversized-dimension JPEG (6000x6000, small on disk) big = Image.new("RGB", (6000, 6000), (90, 90, 90)) big.save(os.path.join(D, "ingress-oversized.jpg"), "JPEG", quality=40) print("oversized size:", os.path.getsize(os.path.join(D, "ingress-oversized.jpg"))) # 10. Random garbage with jpeg extension with open(os.path.join(D, "ingress-garbage.jpg"), "wb") as f: f.write(os.urandom(2048)) # 11. Empty file open(os.path.join(D, "ingress-empty.jpg"), "wb").close() # 12. SVG with embedded script with open(os.path.join(D, "ingress-script.svg"), "wb") as f: f.write(b'") # --- Trailing-data / container polyglots that bypass naive substring scans --- # Every one of these is a structurally valid image followed by appended bytes. # A canonical parser must reject them on the trailing data itself, not on a # signature keyword, so casing and container choice cannot evade the check. TRAILERS = { "ingress-tail-upper-script.jpg": b"", "ingress-tail-mixed-script.jpg": b"", "ingress-tail-html.jpg": b"