README.md
Rendering markdown...
/**
* Vulnerable PDF Generation Server
*
* This server demonstrates a realistic scenario where pdfmake
* is used server-side with user-controlled document definitions.
*
* VULNERABILITY: User-controlled URLs in docDefinition trigger SSRF
*/
import express from 'express';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const pdfmake = require('pdfmake');
const app = express();
app.use(express.json({ limit: '10mb' }));
// Configure fonts - use standard PDF fonts (no external TTF needed)
pdfmake.setFonts({
Helvetica: {
normal: 'Helvetica',
bold: 'Helvetica-Bold',
italics: 'Helvetica-Oblique',
bolditalics: 'Helvetica-BoldOblique'
}
});
/**
* VULNERABLE ENDPOINT
*
* Accepts user-controlled docDefinition which can contain
* arbitrary URLs in images, attachments, or files fields.
*
* pdfmake will fetch these URLs without any validation!
*/
app.post('/api/generate-pdf', async (req, res) => {
console.log('[SERVER] Received PDF generation request');
try {
const docDefinition = req.body;
// Log what we received (for demo purposes)
if (docDefinition.images) {
console.log('[SERVER] Document contains images:', Object.keys(docDefinition.images));
}
if (docDefinition.attachments) {
console.log('[SERVER] Document contains attachments:', Object.keys(docDefinition.attachments));
}
console.log('[SERVER] Creating PDF...');
// Add default font to docDefinition if not specified
if (!docDefinition.defaultStyle) {
docDefinition.defaultStyle = { font: 'Helvetica' };
} else if (!docDefinition.defaultStyle.font) {
docDefinition.defaultStyle.font = 'Helvetica';
}
// THIS IS WHERE THE VULNERABILITY IS TRIGGERED
// pdfmake.createPdf() will call URLResolver.resolve() for any URLs
// in images, attachments, or files - NO VALIDATION
const pdfDoc = pdfmake.createPdf(docDefinition);
// Get the PDF as buffer (pdfmake 0.3.x API)
const buffer = await pdfDoc.getBuffer();
console.log('[SERVER] PDF generated successfully');
res.contentType('application/pdf');
res.send(buffer);
} catch (error) {
console.error('[SERVER] Error:', error.message);
// Note: Even if PDF generation fails, the SSRF has already occurred!
res.status(500).json({
error: error.message,
note: 'SSRF may have still been triggered before this error'
});
}
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', vulnerable: true });
});
const PORT = 3000;
app.listen(PORT, () => {
console.log('='.repeat(50));
console.log('VULNERABLE PDF GENERATION SERVER');
console.log('='.repeat(50));
console.log(`[*] Listening on http://127.0.0.1:${PORT}`);
console.log('[*] POST /api/generate-pdf - Accepts docDefinition');
console.log('[*] WARNING: This server is intentionally vulnerable!');
console.log('');
});