Web
Writeup for Upload (Web) - Akasec CTF (2024) 💜
Last updated
Writeup for Upload (Web) - Akasec CTF (2024) 💜
Last updated
Navigate a mysterious file upload journey.
The challenge provides two URLs. The first is a web application with /home
, /register
and /login
endpoints. The other is an admin bot, so I immediately think about XSS. Before registering an account, I decided to check the source code.
I'll start with bot.js
since there's less code, and that's where we might expect the flag to be on an XSS challenge (admin cookie). There's no flag, though; the bot simply visits a user-provided URL.
When I opened the main app, I quickly found the flag endpoint.
It's validating the IP to ensure the request originated from the localhost, so it looks like we've got an SSRF vulnerability. We can provide the URL to the bot; it will visit the page and get the flag in the response - simple. The question is, how can we retrieve the contents of that response?
An /upload
endpoint allows users to upload files and then returns the URL of the hosted file.
There's some validation on the file type allowed. - it must be PDF format.
This makes me think about Server Side XSS (Dynamic PDF), so I created an account and started testing different payloads. First, I thought we might need to change the content type to application/pdf
but keep the filename as exploit.html
. It's easy to bypass the MIME check, but you'll see a flood of errors in the console and messages like InvalidPDFException
. That makes sense since it's trying to render a PDF 😅 Anyway, I didn't waste much time before returning to the source code.
We want to know the type of PDF generator being used in case there are any known vulnerabilities.
I Googled pdfjs-dist exploit
and the 5th result stood out to me; CVE-2024-4367 – Arbitrary JavaScript execution in PDF.js
This bug allows an attacker to execute arbitrary JavaScript code as soon as a malicious PDF file is opened. This affects all Firefox users (<126) because PDF.js is used by Firefox to show PDF files
PDF.js is bundled into a Node module called
pdfjs-dist
, with ~2.7 million weekly downloads according to NPM
The blog post is around 6 weeks old, so it's a good candidate! I'd encourage you to read the complete analysis because there's a lot in there, but here are some of the key points:
The bug lies in the glyph (font) rendering code
Some fonts are handled by the browser's font renderer, e.g. TrueType
For more obscure fonts, PDF.js turns glyph (character) descriptions into curves on the page
To improve performance, a path generator function is pre-compiled for every glyph
The function takes a list of cmds
(potentially dangerous if an attacker can control)
One of these commands is interesting ({ cmd: "transform", args: fontMatrix.slice() },
) as it copies the array with slice()
and inserts it into the body of the Function
object. It's assumed to be a numeric array, so injecting a string may lead to unintended behaviour!
The value of fontMatrix
defaults to [0.001, 0, 0, 0.001, 0, 0]
but is often set to a custom matrix directly by a font (metadata)
Unfortunately, most PDF readers will only accept a numeric array, i.e., even if we can inject a string into the matrix within the font metadata, it won't be processed
Luckily, there is another way to specify a fontMatrix
- within a PDF metadata object (instead of the font)
Therefore, if we insert something like /FontMatrix [1 2 3 4 5 (cat)]
into the PDF, we'll find that cat
is inserted directly into the function body.
All that's left is to take care of the syntax, leveraging the trailing parenthesis: /FontMatrix [1 2 3 4 5 (0\); alert\('cat')]
They provide a link to a PDF PoC, so I downloaded it and uploaded it to the challenge site. It pops an alert! We can review the request in Burp to see the responsible code.
So, how did I develop my XSS payload? I told ChatGPT I want to fetch the contents of another page and then send them to another web server
🤓
It initially provides some ridiculously long payloads, but through our iterative feedback (aka "shorter plz"), we guide it to produce a more reasonable one.
I got some errors and figured I wasn't escaping all the required characters, so I Googled CVE-2024-4367 PoC
and came across another PoC.
A type check was missing when handling fonts in PDF.js, which would allow arbitrary JavaScript execution in the PDF.js context. This vulnerability affects Firefox < 126, Firefox ESR < 115.11, and Thunderbird < 115.11.
If pdf.js is used to load a malicious PDF, and PDF.js is configured with isEvalSupported set to true (which is the default value), unrestricted attacker-controlled JavaScript will be executed in the context of the hosting domain.
The PDF was generated successfully, but I got an error about bad substitution.
Turns out I just needed to escape some characters.
When I upload the PDF and visit the URL, my browser console and web server logs are filled with errors.
Now that we know we can exfiltrate the HTTP response, we just need to share the link of our note with the admin/bot. The XSS exploit will forge a request to the /flag
endpoint, and since it comes from the server-side (localhost), the validation check will pass.
Note: Maybe this is technically CSRF since the bot simulates an admin user, and the request happens via the headless Chrome browser rather than directly on the server? Then again, the admin bot code is technically on the server side, and the validation function checks that the request came from the server side (localhost), so I'm labelling it SSRF for now! Let me know if you disagree ✅
Regardless, the XSS payload will subsequently issue a request to our server, with the response included in the GET parameter.
The final step is to URL-decode the flag.
Flag: AKASEC{PDF_1s_4w3s0m3_W1th_XSS_&&_Fr33_P4le5T1n3}
🕊✌