Introduction
This challenge is a WEB challenge from the PWNME 2023 CTF.
Background explanation
A company needs a website to generate a QR Code. They asked a freelancer to do this work.
Since the website went live, they have noticed strange behavior on their server.
They need your help to audit their code and help them solve their problem.
Directive
The flag is located in /app/flag.txt.
Solution
The website’s sources are available for download here.
The application is developed in Node.js and runs an Express server.
It allows users to generate a QR Code from a value entered by the user.
For example, here we generated a QRCode with the value PWNME2023.
There is a mention at the top of the page indicating that the maximum size of the input is 150 characters. Beyond that, a random sentence is generated for the QR Code.
The project contains two endpoints that are used:
- /: which displays the homepage
- /generate: which generates the QR Code
Code analysis
Home page endpoint
|
|
The homepage is rendered by the EJS template engine.
There is nothing particular to note about this resource.
Generation endpoint
|
|
This function is more interesting to analyze.
We see that the value input is retrieved from the request body, and if the length of the value is greater than 150 characters, then the value is not taken into account, only its length.
Finally, the getImage()
function is called on the newQrCode
object, and the result is returned to the client.
Let’s look at the sources of the QRCode
class:
|
|
To generate a QRCode, the getImage()
function first checks that the value is not empty.
If there is a value, then the execFortune()
function is called with the defaultLength
value as a parameter.
|
|
Here, we clearly identify a possible command injection by manipulating the size of the value.
One might think it’s impossible to exploit given that it’s an integer passed as a parameter to execFortune, but let’s take a closer look at how the size of the value is checked:
|
|
We notice a typo in the variable value.lenght
, which should be value.length
in the case where the value is greater than 150 characters.
The initial request sent a JSON like this:
|
|
We can modify the request to take advantage of this typo by sending:
|
|
Thus, the control is interpreted:
|
|
Then in the getImage()
function:
|
|
So, with this final request, we can read the flag:
|
|
Response:
|
|
Flag: PWNME{E4Sy_P34sI_B4CkdO0R}
Tips & Tricks
- Be careful with typos in the code.
- Identify functions that can be called with user-manipulated parameters.
- If the data is in JSON format, try modifying the data type (string, int, array, object, etc.).