27 Aug, 2015

The Evil Side of JavaScript: Server-Side JavaScript Injection

by Anand Vemuri

Ever since its humble inception, JavaScript has gained a lot of traction in the world of software development. What originally started as an experimental language meant to increase responsiveness in the browser has evolved into a full-fledged language with the capability to produce full stack web applications.

Full stack JavaScript development has quite a few perks, including enhanced performance times. For this reason, it can be an ideal solution for rapid development. That being said, JavaScript has been notorious for security vulnerabilities. Client-side JavaScript vulnerabilities have been extensively studied for years, but are still one of the most common classes of vulnerabilities in applications. For example, Cross-Site scripting (XSS) has been on the OWASP Top 10 vulnerability list since its inception in 2003. While client-side XSS is certainly a problem, server-side JavaScript injection (SSJI) can be much more dangerous in an application. In fact, one could argue that SSJI is one of the most crippling web application vulnerabilities on the web today.

The Vulnerability

One of the most difficult aspects of writing secure JavaScript code is how easy it is to “accidentally” introduce a vulnerability into an application through simple misconfiguration. Consider the following vulnerable code snippet:

var http = require('http');
http.createServer(function (request, response) {
  if (request.method === 'POST') {
    var data = '';
    request.addListener('data', function(chunk) { data += chunk; });
    request.addListener('end', function() {
      var bankData = eval("(" + data + ")");
      bankQuery(bankData.balance);
   });
  }
});

While this might seem safe to the untrained eye, the eval function is vulnerable here, and attackers can exploit this code to do a multitude of evil things.

In this example, the eval function evaluates the data that is being passed in dynamically by the user. Therefore, if the user submits a JSON object as expected, the eval function will evaluate that as JSON. However, if the user has malicious intentions and submits an actual JavaScript command such as response.end(“Ended Response”);, the server will evaluate and execute this command, which in this case terminates the response prematurely.

Now that we have introduced a vector to get code execution access on a server, we can shift our attention to determining the types of exploits that can be executed. Traditionally speaking, client-side JavaScript injection vulnerabilities attack users and can be most effective when coupled with some form of social engineering, such as phishing. In the case of SSJI, no social engineering is necessary, and attackers can directly access the filesystem with a combination of a few clever tricks.

Brian Sullivan demonstrated a novel approach to exploiting SSJI at Black Hat in 2011. By leveraging the Node.js CommonJS API, attackers can require the filesystem (fs) module. The following payloads can then be used to read the contents of the current directory and the previous one:

response.end(require('fs').readdirSync('.').toString())
response.end(require('fs').readdirSync('..').toString())

If these payloads are successfully executed, then the attacker can effectively read any file on the server. Moreover, the attacker could leverage the writeFileSync functionality to write or overwrite any files on the server.

In order to take things one step further, the attacker can require the child_process module in order to execute binary files. The following payload can be used to execute files on the system:

require('child_process').spawn(filename);

As Brian mentions in his talk, at this point, any further exploits are limited only by the attacker’s imagination.

So How Do We Fix It?

I hope I’ve successfully convinced you that SSJI is a very, very bad vulnerability. But fixing the issue is more important than simply realizing that it’s dangerous. So what measures can developers take to prevent SSJI in their applications?

SSJI is an injection vulnerability, and all the typical symptoms associated with these vulnerabilities need to be carefully mitigated. String concatenation of unsanitized dynamic user input almost always results in a vulnerability, and JavaScript injection is no different. This is particularly important to remember when creating “ad-hoc” JavaScript commands with user input. If user input is required for a server-side command, then the input should be properly validated. Many times, this can be easily done with a regular expression whitelist. If a telephone number input is supposed to contain only the digits 0-9, then there is no need for any other characters, and any inputs containing “bad” characters can be filtered and rejected at the server.

Moreover, avoiding the eval function altogether significantly decreases the risk of this vulnerability. Particularly when parsing JSON objects, JSON.parse() is a much safer method. The eval function is used particularly for its speed benefits; however, it can compile and execute any JavaScript code. As demonstrated earlier, this introduces significant risk into the security posture of the application. The following code snippet is a remediated version of the previous vulnerable code. By making a simple substitution of the eval function with the JSON.parse() function, the code is no longer injectable:

var http = require('http');
http.createServer(function (request, response) {
  if (request.method === 'POST') {
    var data = '';
    request.addListener('data', function(chunk) { data += chunk; });
    request.addListener('end', function() {
      var bankData = JSON.parse(data);
      bankQuery(bankData.balance);
    });
  }
});

References