RC RANDOM CHAOS

A favicon is a code execution primitive.

How attackers hide skimmers and full payloads in favicon files, why MIME and CSP misconfiguration lets image bytes run as code, and what defenders miss.

· 7 min read
A favicon is a code execution primitive.

A favicon can carry a working card skimmer, a base64-packed single-page app, or a second-stage loader. The browser paints it as a 16x16 icon in the tab. Fetched a second way, the same bytes execute. No CVE applies here. No patch boundary. No memory corruption. The mechanism is interpretation. A browser assigns meaning to bytes based on how they were requested, not what they contain. CWE-116, improper encoding or escaping of output. CWE-79 once the reconstructed payload reaches the DOM. MITRE T1027, obfuscated files or information. T1027.003 when the carrier is steganographic. T1140 for the decode step on the client.

The ICO container is a directory format. A six-byte header holds two reserved bytes, two for type, two for image count. Then one 16-byte directory entry per image records dimensions, bit depth, the size of the image data in bytes, and the offset where that data begins. The renderer reads exactly the declared byte count at the declared offset. Everything outside those bounds is never parsed. Append a payload after the legitimate image data and the icon still renders. The trailing bytes stay invisible to the image decoder. Since Windows Vista the image data inside an ICO can itself be a full PNG, and PNG carries ancillary text chunks - tEXt, zTXt, iTXt - each with its own CRC. Arbitrary data sits inside those chunks and passes structural validation. The file is a valid image to every parser that checks it.

A file that is both a valid image and valid script is a polyglot. The browser decides which one it is at request time. Loaded through a link rel=“icon” tag, the bytes go to the image pipeline. Loaded through a script tag, or fetched and evaluated, the same bytes become code. The boundary that should stop this is MIME enforcement. A response served as image/x-icon or image/vnd.microsoft.icon, with X-Content-Type-Options: nosniff, will not execute as script. The browser refuses the type mismatch. Chrome’s Opaque Response Blocking extends the rule to cross-origin no-cors responses and blocks image-typed bodies from reaching a script context. Both controls depend on configuration. Strip nosniff, serve a permissive content type, or keep the request same-origin, and the protection is gone.

The script-tag variant carries a narrow constraint worth stating. For the bytes to parse as JavaScript, the leading magic must also be valid script. A GIF89a header survives as the start of a JavaScript expression and the rest of the binary becomes a string or a commented region. ICO’s leading 00 00 01 00 does not parse as script, so a pure-ICO script-tag polyglot is harder than the GIF case. That is why the fetch-and-decode path dominates real campaigns. The attacker does not need the file to be syntactically valid script. They need it readable as bytes and decodable in memory.

The favicon is one instance of a general condition. Browsers do not bind a file to a single interpretation. The same response becomes an image, a stylesheet, a script, or a font depending on the element that requests it and the headers that describe it. data: and blob: URLs collapse the transport entirely, turning bytes into a resource with no network fetch left to inspect. Content-type confusion is the bug class. The favicon is the most overlooked instance because it is the one asset every page requests and no one audits.

Weaponisation rarely uses a script tag. The reliable path is data. A small first-stage loader already on the page fetches /favicon.ico as an array buffer, slices out the appended segment or reads the PNG text chunk, base64-decodes it, and reconstructs the payload in memory. Execution comes through eval, the Function constructor, or a Blob URL imported as a module. Content Security Policy is the gate. ‘unsafe-eval’ permits the eval and Function paths. A blob: source in script-src permits the Blob import. The fetch of the image is authorised by connect-src or passes as a same-origin request. A site with no CSP, or a CSP carrying ‘unsafe-inline’ and ‘unsafe-eval’ - the common production state - has nothing on this path. The skimmer never appears as a .js file in page source. It never shows as a script request in the network tab. It arrives as an image.

This is documented, not theoretical. Malwarebytes reported a Magecart campaign in 2020 that staged a skimmer behind a favicon. The actor stood up a host posing as an icon CDN. For ordinary pages it returned a valid favicon image. For checkout pages it returned JavaScript, the skimmer, from the same favicon URL. Content negotiation on an image endpoint. The page referenced an icon and the icon endpoint served code. The same year a separate skimmer hid its exfiltration logic in image EXIF metadata and shipped stolen card data back out as an image request. Different carrier, identical principle. The image is a transport, not a picture. The actors map to the Magecart umbrella, financially motivated, initial access through T1190 against vulnerable e-commerce platforms and execution through T1059.007, JavaScript in the victim’s browser.

Once reconstructed, the payload behaves like any client-side skimmer. It registers listeners on input, change, and submit events bound to payment and credential fields, or it overlays a fake form on the legitimate one. Captured values serialize to JSON, get base64 or XOR-encoded, and leave through a beacon. Exfiltration commonly reuses the same trick that delivered the code. The stolen data rides out as a query string on an image request or a favicon fetch to attacker infrastructure, so the egress also reads as benign asset traffic. The loader, the payload, and the exfil channel wear the same disguise. None of the three crosses a control that treats an image request as anything but an image.

The technique extends past delivery. Jonas Strehle’s 2021 favicon supercookie work used the browser’s favicon cache as persistent state. That cache is a separate store keyed by path. By redirecting through a set of favicon paths, a unique identifier could be written and read back across sessions, surviving incognito. The favicon held an ID, not an icon. The same property cuts the other way for defenders. Shodan indexes favicons by MurmurHash3, and reused favicons fingerprint Cobalt Strike team servers, phishing kits, and C2 panels. A favicon is queryable infrastructure.

The telemetry gap is structural. The browser auto-requests /favicon.ico on nearly every navigation. In proxy and secure web gateway logs that request is constant background noise and no analyst reads it. Worse, many content inspection engines, DLP and inline AV, skip image/* MIME types from deep scanning for throughput. The skimmer rides through inside a file class the inspection layer was told to ignore. Endpoint telemetry sees nothing useful. Sysmon and EDR observe browser.exe making outbound connections, but in-renderer JavaScript execution is not a process event. There is no Sysmon Event ID for eval. No child process spawns. No file writes to disk. The payload lives and dies in renderer memory.

What does fire is narrow and configuration-dependent. An enforced CSP with report-to generates a violation report when the reconstruction hits script-src, but most sites run report-only or no policy at all. At the network layer the signal is the file itself. A favicon of anomalous size, 50KB and up where a real icon is two to four. High Shannon entropy from a base64 or compressed blob. Trailing bytes past the ICONDIR bounds. None of that sits in a default detection stack. It requires deliberate inspection of a file class nobody inspects.

Detection that works is static and proactive. Baseline favicon size and entropy per property and alert on deviation, because a checkout-page favicon that jumps from 4KB to 60KB is the lead. Parse ICO directory entries and flag any bytes beyond the last declared image offset plus its byte count. Flag PNG text chunks in files served as icons. Pull favicon hashes and check them against threat intel. On the egress side, treat a fetch of an image endpoint followed by a same-origin write to the DOM as a correlation candidate, not two unrelated events. None of this ships by default. It is detection content someone has to write.

There is nothing to patch. No bug exists. The exposure is configuration and assumption. The misconfiguration defenders miss is the plainest one: static assets served without X-Content-Type-Options: nosniff, a CSP that is absent or carries ‘unsafe-eval’, content inspection that exempts image MIME types, and a /favicon.ico endpoint trusted to return an image because it always has. Cloudflare and every CDN caches and serves whatever the origin returns at that path, image or not, and edge caching extends the reach of a poisoned favicon to every client. Subresource integrity does not help once an attacker already has script execution on the page, which Magecart does. nosniff does not help on same-origin reconstruction. After every header is set correctly the residual reality holds. The favicon is still a file the page can read, decode, and run, and the only control between an image and executable code is a CSP that forbids eval. The favicon was never the entry. It is the obfuscation layer. The entry was the page, and the page was already compromised.

See also: NordVPN for tunneled traffic when operating outside controlled networks.


#ad Contains an affiliate link.

Share

Keep Reading

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.