1、php提交資料過濾的基本原則
1)提交變數進資料庫時,我們必須使用addslashes()進行過濾,像我們的注入問題,一個addslashes()也就搞定了。其實在涉及到變數取值時,intval()函數對字串的過濾也是個不錯的選擇。
2)在php.ini中開啟magic_quotes_gpc和magic_quotes_runtime。magic_quotes_gpc可以把get,post,cookie裡的引號變為斜杠。magic_quotes_runtime對於進出資料庫的資料可以起到格式話的作用。其實,早在以前注入很瘋狂時,這個參數就很流行了。
3)在使用系統函數時,必須使用escapeshellarg(),escapeshellcmd()參數去過濾,這樣你也就可以放心的使用系統函數。
4)對於跨站,strip_tags(),htmlspecialchars()兩個參數都不錯,對於使用者提交的的帶有html和php的標記都將進行轉換。比如角括弧"<"就將轉化為 "<"這樣無害的字元。
代碼如下 |
複製代碼 |
$new = htmlspecialchars("<a href='test'>Test</a>", ENT_QUOTES); strip_tags($text,); |
5)對於相關函數的過濾,就像先前的include(),unlink,fopen()等等,只要你把你所要執行操作的變數指定好或者對相關字元過濾嚴密,我想這樣也就無懈可擊了。
2、PHP簡單的資料過濾
1)入庫: trim($str),addslashes($str)
2)出庫: stripslashes($str)
3)顯示: htmlspecialchars(nl2br($str))
看下面的例子以便進一步討論dispatch.php指令碼:
代碼如下 |
複製代碼 |
<?php /* 全域安全處理 */ switch ($_GET['task']) { case 'print_form': include '/inc/presentation/form.inc'; break; case 'process_form': $form_valid = false; include '/inc/logic/process.inc'; if ($form_valid) { include '/inc/presentation/end.inc'; } else { include '/inc/presentation/form.inc'; } break; default: include '/inc/presentation/index.inc'; break; } ?> |
如果這是唯一的可公開訪問到的 PHP 指令碼,則可以確信的一點是這個程式的設計可以確保在最開始的全域安全處理無法被繞過。同時也讓開發人員容易看到特定任務的控制流程程。例如,不需要瀏覽整個代碼就可以容易的知道:當$form_valid為true時,end.inc是唯一顯示給使用者的;由於它在process.inc被包含之前,並剛剛初始化為false,可以確定的是process.inc的內部邏輯會將設定它為true;否則表單將再次顯示(可能會顯示相關的錯誤資訊)。
注意
如果你使用目錄定向檔案,如index.php(代替dispatch.php),你可以像這樣使用 URL 地址:http://example.org/?task=print_form。
你還可以使用 ApacheForceType重新導向或者mod_rewrite來調整 URL 地址:http://example.org/app/print-form。
包含方法
另外一種方式是使用單獨一個模組,這個模組負責所有的安全處理。這個模組被包含在所有公開的 PHP 指令碼的最前端(或者非常靠前的部分)。參考下面的指令碼security.inc
代碼如下 |
複製代碼 |
<?php switch ($_POST['form']) { case 'login': $allowed = array(); $allowed[] = 'form'; $allowed[] = 'username'; $allowed[] = 'password'; $sent = array_keys($_POST); if ($allowed == $sent) { include '/inc/logic/process.inc'; } break; } ?> |
在本例中,每個提交過來的表單都認為應當含有form這個唯一驗證值,並且security.inc獨立處理表單中0需要過濾的資料。實現這個要求的 HTML 表單如下所示:
代碼如下 |
複製代碼 |
<form action="/receive.php" method="POST"> <input type="hidden" name="form" value="login" /> <p>Username: <input type="text" name="username" /></p> <p>Password: <input type="password" name="password" /></p> <input type="submit" /> </form>
|
叫做$allowed的數組用來檢驗哪個表單變數是允許的, 這個列表在表單被處理前應當是一致的。流程式控制制決定要執行什麼,而process.inc是真正過濾後的資料到達的地方。
注意
確保security.inc總是被包含在每個指令碼的最開始的位置比較好的方法是使用auto_prepend_file設定。
過濾的例子
建立白名單對於資料過濾是非常重要的。由於不可能對每一種可能遇到的表單資料都給出例子,部分例子可以協助你對此有一個大體的瞭解。
下面的代碼對郵件地址進行了驗證:
代碼如下 |
複製代碼 |
<?php $clean = array(); $email_pattern = '/^[^@s<&>]+@([-a-z0-9]+.)+[a-z]{2,}$/i'; if (preg_match($email_pattern, $_POST['email'])) { $clean['email'] = $_POST['email']; } ?> |
下面的代碼確保了$_POST['color']的內容是red,green,或者blue:
代碼如下 |
複製代碼 |
<?php $clean = array(); switch ($_POST['color']) { case 'red': case 'green': case 'blue': $clean['color'] = $_POST['color']; break; } ?> |
下面的代碼確保$_POST['num']是一個整數(integer):
代碼如下 |
複製代碼 |
<?php $clean = array(); if ($_POST['num'] == strval(intval($_POST['num']))) { $clean['num'] = $_POST['num']; } ?> |
下面的代碼確保$_POST['num']是一個浮點數(float):
代碼如下 |
複製代碼 |
<?php $clean = array(); if ($_POST['num'] == strval(floatval($_POST['num']))) { $clean['num'] = $_POST['num']; } ?> |
名字轉換
之前每個例子都使用了數組$clean。對於開發人員判斷資料是否有潛在的威脅這是一個很好的習慣。 永遠不要在對資料驗證後還將其儲存在$_POST或者$_GET中,作為開發人員對超級全域數組中儲存的資料總是應當保持充分的懷疑。
需要補充的是,使用$clean可以協助思考還有什麼沒有被過濾,這更類似一個白名單的作用。可以提升安全的等級。
如果僅僅將驗證過的資料儲存在$clean,在資料驗證上僅存的風險是你所引用的數組元素不存在,而不是未過濾的危險資料。
時機
一旦 PHP 指令碼開始執行,則意味著 HTTP 要求已經全部結束。此時,使用者便沒有機會向指令碼發送資料。因此,沒有資料可以被輸入到指令碼中(甚至register_globals被開啟的情況下)。這就是為什麼初始設定變數是非常好的習慣。
防注入
代碼如下 |
複製代碼 |
<?PHP //PHP整站防注入程式,需要在公用檔案中require_once本檔案 //判斷magic_quotes_gpc狀態 if (@get_magic_quotes_gpc ()) { $_GET = sec ( $_GET ); $_POST = sec ( $_POST ); $_COOKIE = sec ( $_COOKIE ); $_FILES = sec ( $_FILES ); } $_SERVER = sec ( $_SERVER ); function sec(&$array) { //如果是數組,遍曆數組,遞迴調用 if (is_array ( $array )) { foreach ( $array as $k => $v ) { $array [$k] = sec ( $v ); } } else if (is_string ( $array )) { //使用addslashes函數來處理 $array = addslashes ( $array ); } else if (is_numeric ( $array )) { $array = intval ( $array ); } return $array; } //整型過濾函數 function num_check($id) { if (! $id) { die ( '參數不可為空!' ); } //是否為空白的判斷 else if (inject_check ( $id )) { die ( '非法參數' ); } //注入判斷 else if (! is_numetic ( $id )) { die ( '非法參數' ); } //數字判斷 $id = intval ( $id ); //整型化 return $id; } //字元過濾函數 function str_check($str) { if (inject_check ( $str )) { die ( '非法參數' ); } //注入判斷 $str = htmlspecialchars ( $str ); //轉換html return $str; } function search_check($str) { $str = str_replace ( "_", "_", $str ); //把"_"過濾掉 $str = str_replace ( "%", "%", $str ); //把"%"過濾掉 $str = htmlspecialchars ( $str ); //轉換html return $str; } //表單過濾函數 function post_check($str, $min, $max) { if (isset ( $min ) && strlen ( $str ) < $min) { die ( '最少$min位元組' ); } else if (isset ( $max ) && strlen ( $str ) > $max) { die ( '最多$max位元組' ); } return stripslashes_array ( $str ); } //防注入函數 function inject_check($sql_str) { return eregi ( 'select|inert|update|delete|'|/*|*|../|./|UNION|into|load_file|outfile', $sql_str ); // www.111cn.net 進行過濾,防注入 } function stripslashes_array(&$array) { if (is_array ( $array )) { foreach ( $array as $k => $v ) { $array [$k] = stripslashes_array ( $v ); } } else if (is_string ( $array )) { $array = stripslashes ( $array ); } return $array; } ?> |