How attackers deliver malicious payloads through ordinary-looking HTML files that assemble themselves inside the browser — bypassing every network filter that never gets to see the payload.
Sign in to mark this article as read and track your progress.
HTML smuggling is a delivery technique where the malicious payload is not sent across the network as a recognisable file. Instead, it is encoded inside an HTML page or JavaScript attachment and reconstructed entirely inside the victim's browser. By the time the payload exists as a file on disk, it has never traversed the network in a form that a proxy, secure email gateway, or IDS can match against a signature.
The core mechanism is straightforward. The attacker encodes the payload — a ZIP archive, an ISO image, an executable, whatever the campaign requires — as a Base64 string embedded in JavaScript. When the victim opens the HTML file in a browser, the JavaScript decodes the string, constructs a Blob object in memory, generates a temporary object URL, and triggers an automatic download. The browser presents this as a normal file download from "the page the user is on." No external request is made to fetch the payload.
Three browser APIs appear in virtually every HTML smuggling sample:
Blob and URL.createObjectURL. A Blob is an in-memory file-like object. URL.createObjectURL(blob) creates a temporary local URL (prefixed blob:) pointing to that object. The attacker creates an anchor element, sets its href to the blob URL and its download attribute to the desired filename, then programmatically clicks it. The user sees a file download named whatever the attacker chose.
const b64 = "TVqQAAMAAAA..."; // truncated Base64 PE
const raw = atob(b64);
const bytes = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
const blob = new Blob([bytes], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "invoice.iso";
a.click();
atob. The built-in Base64 decoder. Payload is almost always Base64-encoded, sometimes with additional XOR or Caesar layers to defeat simple string matching on the encoded content.
msSaveBlob. The Internet Explorer and legacy Edge equivalent, still found in samples targeting older enterprise environments. Its presence is a strong indicator of a campaign prioritising legacy Windows targets.
NOBELIUM (APT29 / Cozy Bear). Microsoft documented NOBELIUM using HTML smuggling in 2021 as part of spear-phishing campaigns against government and think-tank targets. The HTML attachment delivered an ISO file containing a LNK launcher and a DLL. The ISO was the payload; the HTML was the delivery mechanism. The combination of HTML smuggling → ISO → LNK → DLL sideloading became a template copied extensively by other threat actors.
Lazarus Group. Multiple Lazarus campaigns documented by ESET and Mandiant between 2022 and 2024 used HTML smuggling to deliver ZIP files containing malicious executables. The HTML pages were crafted to resemble legitimate financial documents or job application forms, consistent with Lazarus's social engineering focus on financial sector targets.
QakBot pre-disruption. Prior to the August 2023 FBI-led infrastructure takedown, QakBot delivery infrastructure used HTML smuggling extensively in the OneNote and later ISO-based delivery chains. Samples from this period show multi-layer encoding: XOR-obfuscated JavaScript inside the HTML, producing a Base64-encoded ZIP, containing an ISO, containing LNK and DLL components.
The evasion properties are structural, not incidental:
text/html. Filters looking for application/zip or application/x-msdownload see nothing of interest.When a suspicious HTML file arrives for analysis:
atob, Blob, createObjectURL, msSaveBlob, or any large Base64-looking strings.Endpoint visibility is the primary control once network controls are ineffective:
atob, Blob, createObjectURL, and msSaveBlob in HTML attachments catch most commodity-kit samples. Obfuscated variants require rules on the structure of large Base64 strings rather than the decoded function names.