使用 PHP 的 Filter 函式(過濾器)高效、安全地獲取請求引數

Gtaker發表於2018-07-09

前言

通常,我們獲取請求引數的方法為直接訪問超全域性變數:$_GET,$_POST,$_SERVER,$_ENV,$_COOKIE,而在 php5.2 中,內建了 filter 模組,用於變數的驗證和過濾等操作。過濾器函式簡化了程式碼結構,相對於直接訪問超全域性變數來也更加的高效和安全。

過濾器函式列表:

  • filter_has_var() — 檢測是否存在指定型別的變數。
  • filter_id() — 返回與某個特定名稱的過濾器相關聯的 id。
  • filter_input_array() — 獲取一系列外部變數,並且可以通過過濾器處理它們。
  • filter_input() — 通過名稱獲取特定的外部變數,並且可以通過過濾器處理它。
  • filter_list() — 返回所支援的過濾器列表。
  • filter_var_array() — 獲取多個變數並且過濾它們。
  • filter_var() — 使用特定的過濾器過濾一個變數。

使用過濾器函式檢測是否存在指定型別的變數:

bool filter_has_var(int $type ,string $variable_name);

其中,type 包含五個常量:

  • INPUT_GET
  • INPUT_POST
  • INPUT_COOKIE
  • INPUT_SERVER
  • INPUT_ENV

分別對應

  • $_GET
  • $_POST
  • $_COOKIE
  • $_SERVER
  • $_ENV

這些常量在其他過濾器函式中同樣適用。

filter_has_var() 函式在成功時返回 TRUE,或者在失敗時返回 FALSE

使用該函式,可以把此段程式碼

if (isset($_GET['name'])){
    echo htmlentities($_GET['name']);
} else {
    echo '引數不存在';
}
複製程式碼

改寫為:

echo filter_has_var(INPUT_GET, 'name') ? $_GET['name'] : '引數不存在';
複製程式碼

但是使用此函式,只解決了判斷變數是否存在的問題,如果 $name 的值是

'jo<br>ne'

那麼在 echo 的時候會出現顯示錯誤,更嚴重的是如果對方傳過來的 name 中包含了 <script> 標籤,就可以對網站進行注入攻擊,那麼有什麼辦法來解決這個問題呢?

通過名稱獲取特定的外部變數,並且可以通過過濾器處理它:

mixed filter_input ( int $type , string $variable_name [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

這個函式可以說是 filter_has_var() 的加強版,它在檢測輸入是否存在的同時,還可以傳入第三個引數(過濾器)來檢測該輸入是否符合規範,如果變數不存在返回NULL,不符合則返回 FALSE
其中第三個引數需要填一個過濾器型別(預設為 FILTER_SANITIZE_STRING),PHP 有兩種過濾器:

  • Validating 過濾器:

    • 用於驗證使用者輸入
    • 嚴格的格式規則(比如 URL 或 E-Mail 驗證)
    • 如果成功則返回預期的型別,如果失敗則返回 FALSE
  • Sanitizing 過濾器:

    • 用於允許或禁止字串中指定的字元
    • 無資料格式規則
    • 始終返回字串

例:

$email_1 = filter_input(INPUT_GET,'email', FILTER_VALIDATE_EMAIL);
$email_2 = filter_input(INPUT_GET,'email', FILTER_SANITIZE_EMAIL);

echo 'VALIDATE:';
var_dump($email_1);

echo "<br>";

echo 'SANITIZE:';
var_dump($email_2);
複製程式碼

當我們使用

localhost/test.php?email=1234578<br>qq.com

訪問包含這段程式碼的指令碼時(顯然這是一個非法的email地址),輸出如下:

VALIDATE:bool(false)
SANITIZE:string(15) "1234578brqq.com"

當使用

localhost/test.php?email=1234578@qq.com

訪問時(合法url),輸出如下:

VALIDATE:string(14) "1234578@qq.com"
SANITIZE:string(14) "1234578@qq.com"

很明顯,當引數的值合法時,兩個過濾器會返回相同的值,但是當引數非法時,VALIDATE 會返回一個布林值 FALSE,而 SANITIZE 返回了一個過濾掉特殊字元的字串。

值得一提的是,可以傳入 FILTER_VALIDATE_REGEXP 引數,從而根據 regexp,相容 Perl 的正規表示式來驗證值:

$options = ['options'=>['default'=>'', 'regexp'=>"/^\w*$/"]];
$xxx = filter_input(INPUT_GET,'xxx', FILTER_VALIDATE_REGEXP, $options);
複製程式碼

所以,VALIDATE 型別的過濾器適合用來判斷格式是否正確,而 SANITIZE 能夠對給定的字串進行過濾(具體過濾規則請參考:php.net/manual/zh/f…)。

同時我們也發現了一個問題,雖然 SANITIZE 可以對字串進行過濾,但是這個過濾非常的簡單粗暴,直接把它看不順眼字元從字串中刪掉了,這在其他場景中或許大有用處,但在處理輸入上,通常我們應該返回給使用者一個‘你不能這麼做!’的提示,而不應該擅自替使用者做其他操作,或者想將那些字元轉義並保留下來,如何達到這個目的呢?

使用回撥函式:FILTER_CALLBACK

如果你看系統自帶的過濾器都不順眼,那你可以將第三個引數指定為 FILTER_CALLBACK ,並在第四個引數的位置指定一個回撥函式,來實現自己的過濾器,格式如下:

filter_input(INPUT_POST, 'email',FILTER_CALLBACK,array('options' => 'my_filter'));
複製程式碼

回撥函式格式如下:

function my_filter($str)
{
	// 需要一個形參(本例中為 $str)來接受字串
	// 拿到目標字串後就可以對它為所欲為了
	$str .= 'f*ck';
	// 最後記得 return 加工後的字串
	return $str;
}
複製程式碼

擴充套件應用

相信你也發現了,這只是 PHP 的過濾器函式中的一部分,如果你的目標字串不是來源於前端輸入,而是後端自己加工(或是從資料庫直接取出)出來的,同樣也可以使用類似的過濾器函式,如 filter_var(),該函式的語法為:

filter_var(variable, filter, options); 可以看到,和我們上面使用的函式大同小異,你只需要在傳入輸入型別的地方改為傳入一個要過濾的變數就可以了。

相關文章