BSides Ahmedabad CTF 2021
web
/WfteW6oJ
, which is a permalink for the image./uploads/(UUID).(png|jpg)
. Image pages shows them as <img>
tag./flag
, but only the admin can see it.As the source code for Roda is provided, you can check how the service validates if an uploaded file is JPEG or PNG format.
Let's check the handler of /upload
. It gets the contents of the uploaded file at line 112 first, then it extracts the extension from the original file name at lines 114-115.
After the preparation, it checks if the file is valid using isValidFile
function. The contents and the extension are passed as arguments.
isValidFile
checks if the contents start with the specific bytes corresponding to the extension. The signatures are defined as SIGNATURES
.
As the contents are provided as Uint8Array
, this function compares the signature and the first bytes of the contents using compareUint8Arrays
function. It checks if the length of both is the same, then both are compared byte by byte.
This check has some problems. Since SIGNATURES
is not a Map
but an Object
, if the extension is toString
, valueOf
, or something that Object
has as a method, a function is selected as a signature. Of course, the result of !(ext in SIGNATURES)
is false
if the extension meets the condition, so it can pass the check at line 94.
Functions have length
property that returns the number of parameters. signature.length
is 0 at line 99 if you select a correct extension, and both known.length
and input.length
will be 0 at line 74, and Line 78-82 will be skipped. So, compareUint8Arrays
always returns true
.
This means that you can upload files other than JPEG and PNG!
Now you can bypass the file upload validation, but how it can be used? To think about it, let's see how uploaded files are treated.
The procedure is simple. If the requested file does not exist, or the extension is suspicious, it raises an error as HTML. Then, it sends Content-Type
header corresponding to the extension and the contents.
What happens if the extension is valueOf
or something like that? MIME_TYPES
is an Object
just like SIGNATURES
, so MIMES_TYPES[ext]
at line 173 will be a function. res.type
raises an error if the argument is not suitable, so res.type(MIME_TYPES[ext]);
fails. As Content-Type
is set to text/html
at line 152 even if there is no error until line 173, there will be Content-Type: text/html
header.
This means that if the extension is valueOf
or something like that, the contents are shown as HTML!
Now you achieved XSS on Roda, so the last thing you need to do is to let the admin access your payload as below.
If the path of the image page is /WfteW6oJ
, a report is sent to /WfteW6oJ/report
as POST. But, since the admin will access the image page, not the image directly, the image will be loaded via <img>
tag, so even though your image has a script, it will not be executed.
Let's check the source code to find a way to bypass it. The path posted is extracted using Express function. There is no check about the id, so if you can change id
parameter as upload/(UUID).valueOf
, it means that you win.
Percent encoding is the way to do it. Let's replace the function of reporting with the following function and press the report button. You will get the flag 🐈
ろだ (roda) is a slang that means uploader in Japanese. ↩︎