Real-world JS - 1
Real-world JS 1
Real-world JS Vulnerabilities Series 1
express-fileupload
JavaScript Vulnerabilities (prototype pollution, redos, type confusion etc) is a popular topic in recent security competition such as CTFs
But, there seems to be a lack of real-world research for them, so I started research to find it and share data.
This research aims to improve the nodejs ecosystem security level.
This vulnerability is in the first case about the express-fileupload
.
As shown in the name, this module provide file upload function as express middleware
Until today, this express-fileupload
has been downloaded a total of 7,193,433
times.
The express-fileupload
module provides several options for uploading and managing files in the nodejs application.
Among them, the parseNested
make argument flatten.
Therefore, if we provide {"a.b.c": true}
as an input,
Internally, It will used as {"a": {"b": {"c": true}}}
1 | busboy.on('finish', () => { |
So, if options.parseNested
has a value. If calls processNested
Function, and argument will be req.body
and req.files
.
1 | function processNested(data){ |
The above is the full source of the processNested
function.
Here provides flatten
function for key, of req.files
.
It split the key value of the first argument of object obtained through Object.keys(data)
by .
and makes loop using that, and refers/define object repeatedly.
1 | let some_obj = JSON.parse(`{"__proto__.polluted": true}`); |
In this function, prototype pollution vulnerability is caused by the above usage.
Therefore, if we can put manufactured objects in this function, it can affect the express web application.
1 | const express = require('express'); |
Therefore, configure and run the express server using express-fileupload
in the above form.
1 | POST / HTTP/1.1 |
And I send the above POST
request.
Then we can confirm that the some object is given as the argument of processNested
function. (I added code for debug)
1 | POST / HTTP/1.1 |
Let’s try prototype pollution
If we send this with the name changed to __proto__.toString
.
An object with the key __proto__.toString
is created and call processNested function.
and pollute toString
method of Object.prototype
.
And from the moment this value is covered with a object that is not a function.
The express
application makes error for every request !
1 | var isRegExp = function isRegExp(obj) { |
In the qs
module used within the express
, location.search
part of the HTTP request will be parsed and make it to req.query
object.
In that logic, qs
uses Object.prototype.toString
.
Therefore, this function called for every request in the express application (even if there is no search part)
If Object.prototype.toString
can be polluted, this will cause an error.
and for every request, express always returns 500 error.
1 | import requests |
Actually, if we use script above to pollute the prototype of server
For all requests, the server returns either these error messages (development mode)
or only a blank screen and 500 Internal Server Error
! 😮
How to get shell?
We can already make a DOS, but everyone wants a shell.
So, I’ll describe one way to acquire shell through the vulnerability above.
The simplest way to obtain shell through prototype solution
in the express application is by using the ejs
.
Yes, There is a limitation to whether the application should be using the ejs template engine
But the EJS is the most popular template engine for the nodejs
and also used very often in combination with the express.
If this vulnerability exists, you can bet on this. (no guaranteed 😏)
1 | const express = require('express'); |
The above is an example of using the ejs module.
There was only one line change in replacing the rendering engine.
Because the parseNested option is still active, we can still pollute prototype.
Unlike the above here, I will use req.body
object.
Because we can manipulated the value of that as string.
1 | POST / HTTP/1.1 |
Similar with above, but the filename
of Content-Disposition
has been deleted.
Then the value will go to req.body
not req.files
.
By checking the values that enter the processNested
function
You can see that the values that were previously objects is now string.
pollution happens the same as before.
1 | function Template(text, opts) { |
The target value to pollute is the outputFunctionName
, which is an option in the ejs rendering function.
1 | compile: function () { |
The ejs makes Function for implement their template and executing
and the outputFunctionName
option used in the process is included in the function.
Therefore, if we can manipulate this value, any command can be executed.
This technique was introduced by a Chinese CTF in 2019.
Please refer to here for details.
That part has not been patched so far, and it is expected to remain in the future.
So we can take advantage of it.
1 | POST / HTTP/1.1 |
So first, we’re going to pollute the Object.prototype.outputFunctionName
using the prototype pollution
.
1 | GET / HTTP/1.1 |
and calls template function of ejs.
Then we can get the shell !
If all the process can be represented by python:
1 | import requests |