webfreemedium

review

pingCTF

Task: Node/Express PDF review app with Pug templates and admin bot. Solution: Exploit discrepancy between upload-time and view-time PDF text extraction after form flattening to inject XSS via Pug &attributes() spread.

$ ls tags/ techniques/
admin_bot_exploitationstored_xsspug_attribute_injectionpdf_form_flattening_bypass

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

review — pingCTF 2026

Description

Every good CTF needs a review!

A Node.js/Express application that allows users to upload PDF review forms. An admin bot can be triggered to view the uploaded PDFs. The goal is to steal the flag from the authenticated admin session.

Analysis

Application Architecture

The application uses several key technologies:

  • Express with Pug templates
  • pdf-lib for PDF manipulation (creating forms, flattening)
  • pdfjs-dist for text extraction
  • Puppeteer for the admin bot
  • multer for file uploads

Key Endpoints

  1. GET /download - Generates a fillable PDF template with hidden pdf_id field
  2. POST /upload - Validates and stores uploaded PDFs
  3. POST /admin - Triggers admin bot to visit /view?formId=<id>
  4. GET /view - Displays extracted PDF text (requires authentication)
  5. GET /flag - Returns the flag (requires authentication)

The Vulnerability Chain

1. PDF Validation at Upload Time

async function validatePDF(id) { // ... const htmlTagRegex = /<\/?[a-z][\s\S]*>/i; for (const field of storedFields) { try { const uploadedField = form.getTextField(field); const text = uploadedField.getText(); if (htmlTagRegex.test(text)) return false; // Check field values } catch { return false; } } // ... // Also extracts page text with pdfjs and checks for HTML tags const textContent = await docPage.getTextContent(); // ... if(htmlTagRegex.test(content)) return false; return true; }

The validation checks:

  1. Form field values for HTML tags
  2. Page text content (extracted via pdfjs) for HTML tags

Critical observation: At upload time, form fields are NOT flattened, so pdfjs only extracts static page text, not the form field values.

2. PDF Processing at View Time

...

$ grep --similar

Similar writeups