PHP in_array的坑
ps: 應該是弱類型語言的坑
顧名思義,in_array就是查找一個值是否在數組裏面。
問題
事故現場
一個sql注入的測試代碼如下:
$type = $_GET['type'];
$types = [2,3,4,5,6];
if(!in_array($type, $types)) {
throw new ParamsException('參數錯誤');
}
$sql = sprintf('select * from test where `type` = %s', $type);
$result = $db->fetch($sql);
emm,讓我們理一下。
- 從參數獲取type
- 檢驗type合法性
- 拼sql
- 查找數據
好像是這麼回事。
但是,線上報警系統說,這個接口有sql注入風險。
wait,我知道你們會說,這是沒有用過預處理,肯定會注入。但是看官,且聽我慢慢道來,這個項目是一個及其遠古的項目,若是要改動的話,成本和收益不成正比,領導也不會同意大改。而且,嗯,我們的參數只有一個,而且,我們校驗了參數的合法性。爲什麼會被注入呢?
問題查找
既然我們的參數只有一個,而且我們確認已經過濾了,那麼仍然存在能被注入的風險,就證明了,我們的校驗不對。
ok,我們先了解下什麼是常見的sql注入。
所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。具體來說,它是利用現有應用程序,將(惡意的)SQL命令注入到後臺數據庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的數據庫,而不是按照設計者意圖去執行SQL語句。 [1] 比如先前的很多影視網站泄露VIP會員密碼大多就是通過WEB表單遞交查詢字符暴出的,這類表單特別容易受到SQL注入式攻擊.
舉個栗子:
select * from a where id = 10; 正常sql
select * from a where id = 10 or 1; 注入sql
換到我們的場景而言,就是 type = 3 變成了 type = 3 or 1,那豈不是把所有值都返回了。
嗯???不是校驗了type嗎?怎麼會這樣注入。
好,實踐是檢驗真理的唯一標準。
$arr = [3,4,5];
$str = '3 or 1';
var_dump(in_array($str, $arr));
打印出來bool(true)!!!!
想必都驚呆了,原來是in_array校驗數據出了問題。
再進行我的猜測。
$str = '3 or 1';
$int = intval($str);
echo $int; //3
var_dump($a == $int); // bool(true)
soga,原來,檢測是這麼容易通過的。我推斷是因爲在進行值比價的時候,因爲沒有使用強類型驗證,所以,會將 3 or 1 == 3,從而判定是是在數組裏。
然後繼續仔細查看文檔,發現in_array有第三個參數,作用是強類型判斷是否相等(嚴格校驗)。但是我想,還是不適合我的場景,我的場景是允許字符串傳入的。
解決問題
居然問題找到了,解決就自然有簡單了。
解決方案還是不說了,因爲不同場景各位看官要用的解決方式也不一樣。
思路基本是一樣的,就是保證不會被異常數據注入(我這選擇的是根據參數返回已經寫好的where,哈,項目求穩)。
源碼關注
PHP_FUNCTION(in_array)
{
php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
ps: array_search最終調用的也是php_search_array
zval *value, /* value to check for */
*array, /* array to check in */
*entry; /* pointer to array entry */
zend_ulong num_idx;
zend_string *str_idx;
zend_bool strict = 0; /* strict comparison or not */
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_ZVAL(value)
Z_PARAM_ARRAY(array)
Z_PARAM_OPTIONAL
Z_PARAM_BOOL(strict)
ZEND_PARSE_PARAMETERS_END();
if (strict) {
//強類型驗證......
//這裏用的fast_is_identical_function(value, entry))
} else {
//可以觀察到,php是先檢測數組的值得類型,而不是根據查找值得類型進行比較
if (Z_TYPE_P(value) == IS_LONG) {
//long驗證,在php裏是整形
//if (fast_equal_check_long(value, entry)) {
} else if (Z_TYPE_P(value) == IS_STRING) {
}//...其他類型
而 fast_equal_check_string的原型如下
static zend_always_inline int fast_equal_check_long(zval *op1, zval *op2)
{
zval result;
if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
return Z_LVAL_P(op1) == Z_LVAL_P(op2);
}
compare_function(&result, op1, op2);
return Z_LVAL(result) == 0;
}
可以看到,當2個參數類型不一樣的時候,會走compare_function
compare_function是zend Api內核提供的,源碼暫時無法追蹤,但是根據官方的說明,結果和預測的一樣。
具體請查看官方:php對比2個值是否相等--比較運算符
後記
不要脫離了框架就沒法寫代碼
不要只說新標準,通常都要維護很多老代碼