PHP 的弱類型特性極大地提升了開發靈活性,但也引入了大量隱蔽的安全隱患。本文系統梳理 PHP 類型轉換、比較運算、內建函數鬆散參數等核心問題,並結合實戰案例(CTF/攻防平台常見題型)提供防禦建議。
PHP 變數無需聲明類型,可自由切換:
$param = 1; $param = []; $param = 'string';
當不同類型進行比較或運算時,PHP 會自動進行隱式類型轉換,這是漏洞產生的根源。
$a = null; $b = false; // true $a = ''; $b = null; // true 0 == '0' // true 0 == 'abcdefg' // true (字串轉數值為 0) 0 === 'abcdefg' // false (全等比較不轉換) 1 == '1abcdef' // true (字串開頭數字部分)
核心規則:數值與字串比較時,字串會被轉換為數值(從開頭解析數字,非數字字元停止解析,預設為 0)。
"0e132456789" == "0e7124511451155" // true 皆解析為 0e+N = 0 "0e123456abc" == "0e1dddada" // false 不符合 0e\d+ 模式 "0e1abc" == "0" // true
符合 0e\d+ 格式的字串會被解析為科學計數法(值為 0),導致任意兩個此類字串 == 比較為 true。這是 MD5 碰撞繞過的常見手法。
"0x1e240" == "123456" // true (0x1e240 = 123456) "0x1e240" == 123456 // true "0x1e240" == "1e240" // false (右側非十六進制格式)
以 0x 開頭的字串會被解析為十六進制數值再比較。
intval('2') // 2
intval('3abcd') // 3 (遇到非數字停止)
intval('abcd') // 0
漏洞案例:
// 危險代碼
if (intval($id) > 1000) {
mysql_query("SELECT * FROM news WHERE id = $id"); // $id = '1002 union select...' 繞過
}
修復:使用 (int)$id 或嚴格驗證 ctype_digit(),並採用預處理語句。
$arr1 = ['foo'=>'bar', 'bar'=>'foo']; $arr2 = ['foo', 'bar', 'hello', 'world']; var_dump(md5($arr1) == md5($arr2)); // true (PHP Warning 但返回固定字串)
任意兩個陣列的 md5() 結果相同,可用於繞過簽名驗證。
$arr = [1,2,3]; var_dump(strcmp($arr, '123')); // NULL (視為 false/0 即相等)
在 if (strcmp($input, $secret) == 0) 判斷中,傳入陣列可繞過檢查。
$i = "2abc";
switch ($i) {
case 0: case 1: case 2:
echo "i is less than 3"; // 輸出此行,因 $i 被轉為 2
break;
}
$arr = [0, 1, 2, '3'];
in_array('abc', $arr); // true ('abc' 轉為 0)
in_array('1bc', $arr); // true ('1bc' 轉為 1)
// 修復:啟用第三參數 strict = true
in_array('abc', $arr, true); // false
=== 和 !==,避免隱式轉換。filter_var()、ctype_*()、正則表達式嚴格校驗格式。declare(strict_types=1),強制嚴格模式。password_hash()/password_verify(),勿用 md5()/sha1()。hash_equals() 防時序攻擊。display_errors=Off,避免洩露堆疊資訊。PHP 弱類型本身不是錯誤,但「不了解轉換規則」才是漏洞溫床。開發者必須建立類型安全意識,善用嚴格模式與現代框架的封裝,從源頭消除此類風險。