Artifactory

JFrog Artifactory is a binary repository manager commonly found in enterprise CI/CD pipelines. During assessments it may be accessible with low-privilege or stolen credentials — enumerate permissions and write access to determine the blast radius.


Enumerate Accessible Repositories & Permissions

Lists all repositories and shows effective permissions for any where access is granted. Silently skips 403s.

#!/bin/bash
 
# show effective permissions only for repositories where access is granted.
# Usage: ./get_granted_perms.sh <username> <password/token> <artifactory_url>
 
# --- Configuration ---
USERNAME=$1
PASSWORD=$2
ARTIFACTORY_URL=$3
 
# --- Validation ---
if [ -z "$USERNAME" ] || [ -z "$PASSWORD" ] || [ -z "$ARTIFACTORY_URL" ]; then
  echo "Usage: $0 <username> <password/token> <artifactory_url>"
  exit 1
fi
 
# --- Main Logic ---
echo "Fetching list of all repositories..."
REPO_LIST=$(curl -k -s -u "${USERNAME}:${PASSWORD}" "${ARTIFACTORY_URL}/artifactory/api/repositories" | jq -r '.[].key')
 
echo "Scanning for accessible repositories..."
echo "=========================================="
 
# Create a temporary file to store the response body
BODY=$(mktemp)
 
for REPO in $REPO_LIST; do
  # Use -w to get the HTTP code, and -o to write the body to our temp file
  HTTP_STATUS=$(curl -k -s -u "${USERNAME}:${PASSWORD}" -w "%{http_code}" -o "$BODY" "${ARTIFACTORY_URL}/artifactory/api/security/effectivePermissions/${REPO}")
 
  # Check if the HTTP status is 200 (OK)
  if [ "$HTTP_STATUS" -eq 200 ]; then
    echo "Repository: $REPO"
    # Print the body from the temp file and pretty-print it with jq
    cat "$BODY" | jq .
    echo "------------------------------------------"
  fi
  # The loop will silently continue if the status is 403 or anything else
done
 
# Clean up the temporary file
rm -f "$BODY"
 
echo "Scan complete. Only accessible repositories are shown above."

Check for Write / Deploy Access

Attempts to initiate an upload to each repository. A 202 Accepted response confirms write access.

#!/bin/bash
 
# check if upload is possible
# Usage: ./check_write_access.sh <username> <password/token> <artifactory_url>
 
# --- Configuration ---
USERNAME=$1
PASSWORD=$2
ARTIFACTORY_URL=$3
 
# --- Validation ---
if [ -z "$USERNAME" ] || [ -z "$PASSWORD" ] || [ -z "$ARTIFACTORY_URL" ]; then
  echo "Usage: $0 <username> <password/token> <artifactory_url>"
  exit 1
fi
 
# --- Main Logic ---
echo "Fetching list of all repositories..."
REPO_LIST=$(curl -k -s -u "${USERNAME}:${PASSWORD}" "${ARTIFACTORY_URL}/artifactory/api/repositories" | jq -r '.[].key')
 
echo "Scanning for repositories with WRITE/DEPLOY access..."
echo "====================================================="
 
SUCCESS_COUNT=0
for REPO in $REPO_LIST; do
  # Send a POST request to initiate an upload. We send no data.
  # We use -I to get only headers, and grep to check the status code.
  HTTP_STATUS=$(curl -k -s -u "${USERNAME}:${PASSWORD}" -X POST -w "%{http_code}" -o /dev/null "${ARTIFACTORY_URL}/v2/${REPO}/blobs/uploads/")
 
  # A 202 status code means "Accepted", confirming we can start an upload.
  if [ "$HTTP_STATUS" -eq 202 ]; then
    echo "✅ WRITE ACCESS CONFIRMED on repository: $REPO"
    SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
  fi
  # The loop will silently continue for any other status code (e.g., 401, 403, 404).
done
 
echo "====================================================="
if [ "$SUCCESS_COUNT" -eq 0 ]; then
  echo "Scan complete. No repositories with write access were found."
else
  echo "Scan complete. Found $SUCCESS_COUNT repositories with write access."
fi

Note

Write access to a package repository may enable supply chain attacks — malicious package injection, dependency confusion, or poisoning internal builds. Confirm scope and rules of engagement before attempting any uploads.