php 安全過濾函數代碼,防止用戶惡意輸入內容。javascript
//安全過濾輸入[jb]
function check_str($string, $isurl = false)
{
$string =
preg_replace('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/','',$string);
$string =
str_replace(array("\0","%00","\r"),'',$string);
empty($isurl) &&
$string = preg_replace("/&(?!(#[0-9]+|[a-z]+);)/si",'&',$string);
$string = str_replace(array("%3C",'<'),'<',$string);
$string =
str_replace(array("%3E",'>'),'>',$string);
$string =
str_replace(array('"',"'","\t",' '),array('「','‘',' ',' '),$string);
return
trim($string);
}php
/** * 安全過濾類-過濾javascript,css,iframes,object等不安全參數 過濾級別高 * Controller中使用方法:$this->controller->fliter_script($value) * @param string $value 須要過濾的值 * @return string */ function fliter_script($value) { $value = preg_replace("/(javascript:)?on(click|load|key|mouse|error|abort|move|unload|change|dblclick|move|reset|resize|submit)/i","&111n\\2",$value); $value = preg_replace("/(.*?)<\/script>/si","",$value); $value = preg_replace("/(.*?)<\/iframe>/si","",$value); $value = preg_replace ("//iesU", '', $value); return $value; } /** * 安全過濾類-過濾HTML標籤 * Controller中使用方法:$this->controller->fliter_html($value) * @param string $value 須要過濾的值 * @return string */ function fliter_html($value) { if (function_exists('htmlspecialchars')) return htmlspecialchars($value); return str_replace(array("&", '"', "'", "<", ">"), array("&", "\"", "'", "<", ">"), $value); } /** * 安全過濾類-對進入的數據加下劃線 防止SQL注入 * Controller中使用方法:$this->controller->fliter_sql($value) * @param string $value 須要過濾的值 * @return string */ function fliter_sql($value) { $sql = array("select", 'insert', "update", "delete", "\'", "\/\*", "\.\.\/", "\.\/", "union", "into", "load_file", "outfile"); $sql_re = array("","","","","","","","","","","",""); return str_replace($sql, $sql_re, $value); } /** * 安全過濾類-通用數據過濾 * Controller中使用方法:$this->controller->fliter_escape($value) * @param string $value 須要過濾的變量 * @return string|array */ function fliter_escape($value) { if (is_array($value)) { foreach ($value as $k => $v) { $value[$k] = self::fliter_str($v); } } else { $value = self::fliter_str($value); } return $value; } /** * 安全過濾類-字符串過濾 過濾特殊有危害字符 * Controller中使用方法:$this->controller->fliter_str($value) * @param string $value 須要過濾的值 * @return string */ function fliter_str($value) { $badstr = array("\0", "%00", "\r", '&', ' ', '"', "'", "<", ">", " ", "%3C", "%3E"); $newstr = array('', '', '', '&', ' ', '"', ''', "<", ">", " ", "<", ">"); $value = str_replace($badstr, $newstr, $value); $value = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $value); return $value; } /** * 私有路勁安全轉化 * Controller中使用方法:$this->controller->filter_dir($fileName) * @param string $fileName * @return string */ function filter_dir($fileName) { $tmpname = strtolower($fileName); $temp = array(':/',"\0", ".."); if (str_replace($temp, '', $tmpname) !== $tmpname) { return false; } return $fileName; } /** * 過濾目錄 * Controller中使用方法:$this->controller->filter_path($path) * @param string $path * @return array */ public function filter_path($path) { $path = str_replace(array("'",'#','=','`','$','%','&',';'), '', $path); return rtrim(preg_replace('/(\/){2,}|(\\\){1,}/', '/', $path), '/'); } /** * 過濾PHP標籤 * Controller中使用方法:$this->controller->filter_phptag($string) * @param string $string * @return string */ public function filter_phptag($string) { return str_replace(array(''), array('<?', '?>'), $string); } /** * 安全過濾類-返回函數 * Controller中使用方法:$this->controller->str_out($value) * @param string $value 須要過濾的值 * @return string */ public function str_out($value) { $badstr = array("<", ">", "%3C", "%3E"); $newstr = array("<", ">", "<", ">"); $value = str_replace($newstr, $badstr, $value); return stripslashes($value); //下劃線 }
php使用正則過濾js腳本代碼實例css
<?php header("Content-type:text/html;charset=utf-8"); $str = '<script type="text/javascript" src="dd.js"></script> 測試php正則匹配掉js代碼測試php正則匹配掉js代碼測試php正則匹配掉js代碼測試php正則匹配掉js代碼測試php正則匹配掉js代碼測試php正則匹配掉js代碼 <script type="text/javascript" src="123.js"></script> <script type="text/javascript"> var aa = "sdsds"; alert(aa); </script> 測試php正則匹配掉js代碼'; $preg = "/<script[\s\S]*?<\/script>/i"; $newstr = preg_replace($preg,"",$str,3); //第四個參數中的3表示替換3次,默認是-1,替換所有 echo $newstr; ?>
一個完整成熟的站點,內容過濾,防注入,加密存儲和傳輸,敏感詞屏蔽都是必不可少功能,關於加密傳輸有cookie加密存儲和解密,客戶端js加密到php解密和php文件之間的加密傳輸,涉及的內容較多,若有可能將另開一貼,本貼主要分享一下內容過濾和敏感詞屏蔽的經驗html
一,內容過濾java
提及過濾,大概立刻會想到addslashes函數,這個經常使用於防止sql注入,但其實光這些還遠遠不夠,咱們要作的經常是將全部不相關的sql關鍵詞所有替換掉,保證發上來的東西放進sql語句裏是沒法改變sql行爲的,因此通常來講,安全起見這兩點要求都須要達到mysql
addslashes函數爲了避免重複轉義,通常會須要判斷下服務器是否開啓了自動轉義,而後再進行轉義程序員
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
function
add_slashes(
$string
,
$force
= 0)
{
if
(!get_magic_quotes_gpc() ||
$force
)
{
if
(
is_array
(
$string
))
{
foreach
(
$string
as
$key
=>
$val
)
{
$string
[
$key
] = daddslashes(
$val
,
$force
);
}
}
else
{
$string
=
addslashes
(
$string
);
}
}
return
$string
;
}
|
爲了讓全部的sql關鍵詞都安全存儲,咱們須要將它們轉義,顯示的時候再翻譯回來正則表達式
function sql_encode($str) { if(empty($str)) return ""; $str=trim($str); $str=str_replace("_","\_",$str); $str=str_replace("%","\%",$str); $str=str_replace(chr(39),"'",$str); $str=str_replace("'","''",$str); $str=str_replace("select","select",$str); $str=str_replace("join","join",$str); $str=str_replace("union","union",$str); $str=str_replace("where","where",$str); $str=str_replace("insert","insert",$str); $str=str_replace("delete","delete",$str); $str=str_replace("update","update",$str); $str=str_replace("like","like",$str); $str=str_replace("drop","drop",$str); $str=str_replace("create","create",$str); $str=str_replace("modify","modify",$str); $str=str_replace("rename","rename",$str); $str=str_replace("alter","alter",$str); $str=str_replace("cast","cas",$str); return $str; }
function sql_decode($str) { if(empty($str)) return ""; $str=str_replace("'",chr(39),$str); $str=str_replace("''","'",$str); $str=str_replace("select","select",$str); $str=str_replace("join","join",$str); $str=str_replace("union","union",$str); $str=str_replace("where","where",$str); $str=str_replace("insert","insert",$str); $str=str_replace("delete","delete",$str); $str=str_replace("update","update",$str); $str=str_replace("like","like",$str); $str=str_replace("drop","drop",$str); $str=str_replace("create","create",$str); $str=str_replace("modify","modify",$str); $str=str_replace("rename","rename",$str); $str=str_replace("alter","alter",$str); $str=str_replace("cas","cast",$str); return $str; }
有了以上的函數,對於sql語句涉及的內容就能夠放心使用了,但這還不夠,頁面顯示內容也須要過濾,頁面內容過濾也分不少狀況,好比標題是不容許html標籤的,用戶名是除了限制的數字字母,下劃線外,其餘都不容許使用的,而內容是能夠有限制性的使用一些html標籤,這就須要咱們針對不一樣狀況作不一樣的過濾處理sql
1,標題過濾數據庫
function filters_title($text) { $text = trim($text); $text = str_replace("'","",$text); $text = strip_tags($text); $text = stripslashes($text); return $text; }
2,用戶名過濾
function filters_username($string) { $length=strlen($string); if($length<2 || $length>18){return false;} for($n=0; $n<$length; $n++) { $t = ord($string[$n]); if( (47<$t && $t<58) || (64<$t && $t<91) || (96<$t && $t<123) || $t==45 || $t==95 || $t>126){} else{return false;} } return true; }
3,內容過濾
function filters_outcontent($str) { $str = stripslashes($str); $str = preg_replace("/<div[^>]*?>/is","",$str); $str = str_replace("aaaaa","\r\n",$str); $str = str_replace("bbbbb","\n",$str); $str = str_replace("ccccc","\r",$str); $str = str_replace('\"','"',$str); $str = str_replace(array('<HTML', '<BODY', '<INPUT', '<SCRIPT', '<FORM', '<IFRAME'), array('<html', '<body', '<input', '<script', '<form', '<iframe'), $str); $str = str_replace(array('<html', '<body', '<input', '<script', '<form', '<iframe', '<textarea','</textarea>'), array('<html', '<body', '<input', '<script', '<form', '<iframe', '<textarea', '</textarea>'), $str); return $str; }
能夠根據以上思路添加本身的過濾場景,作到符合項目要求
二,敏感詞屏蔽
敏感詞屏蔽是如今國內站點必須使用的功能之一了,國情如此,辛辛苦苦作的站,由於這個被封,實在很不划算。根據應用場景,敏感詞過濾能夠分爲替換和禁止兩種,替換就是不提示,直接替換敏感詞爲**等,禁止一半而言須要提示用戶,有敏感詞,須要修改,例如在用戶註冊時候,替換通常用在文章中。
敏感詞須要數據庫支持,將敏感詞存入數據庫時候,能夠按照分類,那些是要替換的,那些是要禁止的,按照固定格式存儲,例如 abc=**,這是替換。cba={banned},這是禁止,或者在函數中制定第二個參數,replace或者banned來制定函數執行替換或者精緻操做均可以,我採用的是第一種,若是有須要,你們能夠根據原函數修改成第二種,最核心的其實很簡單就是個正則查找的過程
function censor($string) { global $dblink, $tablepre; $censoraray = $banned = $banwords = array(); $query = $dblink->query("SELECT * FROM censor WHERE var='censor' "); if($value = $dblink->fetch_array($query)) { $censorstr = is_array($value)?$value['datavalue']:$value; } else { $censorstr = ''; } if (strlen(trim($censorstr)) > 0) { //有值就屏蔽 $censorarr = explode("\n", $censorstr);//按輸入時候的回車分割爲數組 foreach($censorarr as $censor) { $censor = trim($censor); if(empty($censor)) continue; list($find, $replace) = explode('=', $censor); $findword = $find; $find = preg_replace("/\\\{(\d+)\\\}/", ".{0,\\1}", preg_quote($find, '/'));//匹配屏蔽語法中的"a{1}s{2}s" switch($replace) { case '{BANNED}': $banwords[] = preg_replace("/\\\{(\d+)\\\}/", "*", preg_quote($findword, '/')); $banned[] = $find; break; default: $censoraray['filter']['find'][] = '/'.$find.'/i'; $censoraray['filter']['replace'][] = $replace; break; } } if($banned) { $censoraray['banned'] = '/('.implode('|', $banned).')/i'; $censoraray['banword'] = implode(', ', $banwords); } if($censoraray['banned'] && preg_match($censoraray['banned'], $string)) { return $censoraray['banned'];//有敏感詞彙不予顯示 } else { $string = empty($censoraray['filter']) ? $string : @preg_replace($censoraray['filter']['find'], $censoraray['filter']['replace'], $string); } } return $string; }
由於我採用了第一種,因此須要將敏感詞從數據庫中取出後,作替換或者禁止的判斷,其實核心代碼不多,算是給你們一些思路吧
三,cookie加密
cookie加密存儲是站點安全的很重要選擇步驟,一些敏感的數據存儲在cookie裏方便了頁面之間的傳輸,或者用於身份認定,來源判斷,這些數據若是不加密,對有經驗的人來講,很容易就會分析出一些站點的內部機制,或者將cookie用於跨站攻擊。
設置cookie的語法很簡單
setcookie(name,value,expire,path,domain,secure)
name 必需。規定 cookie 的名稱。
value 必需。規定 cookie 的值。
expire 可選。規定 cookie 的有效期。
path 可選。規定 cookie 的服務器路徑。
domain 可選。規定 cookie 的域名。
secure 可選。規定是否經過安全的 HTTPS 鏈接來傳輸 cookie。
設置cookie的函數能夠這樣寫
function set_cookie($var, $value, $life = 0, $prefix = 1) { global $cookiepre, $cookiedomain, $cookiepath, $timestamp, $_SERVER; setcookie(($prefix ? $cookiepre : '').$var, $value, $life ? $timestamp + $life : 0, $cookiepath,$cookiedomain, $_SERVER['SERVER_PORT'] == 443 ? 1 : 0); }
解釋一下
1,$prefix = 1 因此當($prefix ? $cookiepre : '').$var時候就會加上前綴,也能夠選擇不加 2,$lift=0 因此當$life ? $timestamp + $life : 0時候,若是傳值了且不是0,就會設置有效期
這裏最重要的是加密函數,我這裏用的是下面這個
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) { $ckey_length = 4; //note 隨機密鑰長度 取值 0-32; //note 加入隨機密鑰,能夠令密文無任何規律,即使是原文和密鑰徹底相同,加密結果也會每次不一樣,增大破解難度。 //note 取值越大,密文變更規律越大,密文變化 = 16 的 $ckey_length 次方 //note 當此值爲 0 時,則不產生隨機密鑰 $key = md5($key ? $key : UC_KEY); $keya = md5(substr($key, 0, 16)); $keyb = md5(substr($key, 16, 16)); $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : ''; $cryptkey = $keya.md5($keya.$keyc); $key_length = strlen($cryptkey); $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string; $string_length = strlen($string); $result = ''; $box = range(0, 255); $rndkey = array(); for($i = 0; $i <= 255; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } for($j = $i = 0; $i < 256; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for($a = $j = $i = 0; $i < $string_length; $i++) { $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); } if($operation == 'DECODE') { if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) { return substr($result, 26); } else { return ''; } } else { return $keyc.str_replace('=', '', base64_encode($result)); } }
第二個參數可設置爲加密encode,或者解密decode。
這樣一來,加密的設置cookie就能夠這樣寫
set_cookie('compound', authcode("$uid\t$uname\t$pw", 'ENCODE', $key));
讀取的時候,將cookie值讀入變量,而後 $cookiearray=explode("\t", authcod($cookieval, 'DECODE', $key))就能夠讀入到數組裏了。
部分來源:http://www.cnblogs.com/phpinfo/p/3592873.html
php過濾全部惡意字符:
//php 批量過濾post,get敏感數據 if (get_magic_quotes_gpc()) { $_GET = stripslashes_array($_GET); $_POST = stripslashes_array($_POST); } function stripslashes_array(&$array) { while(list($key,$var) = each($array)) { if ($key != 'argc' && $key != 'argv' && (strtoupper($key) != $key || ''.intval($key) == "$key")) { if (is_string($var)) { $array[$key] = stripslashes($var); } if (is_array($var)) { $array[$key] = stripslashes_array($var); } } } return $array; } //過濾 function htmlencode($str){ if(empty($str)) return; if($str=="") return $str; $str=trim($str); $str=str_replace("&","&",$str); $str=str_replace(">",">",$str); $str=str_replace("<","<",$str); $str=str_replace(chr(32)," ",$str); $str=str_replace(chr(9)," ",$str); $str=str_replace(chr(9)," ",$str); $str=str_replace(chr(34),"&",$str); $str=str_replace(chr(39),"'",$str); $str=str_replace(chr(13)," ",$str); $str=str_replace("'","''",$str); $str=str_replace("select","select",$str); $str=str_replace("SCRIPT","SCRIPT",$str); $str=str_replace("script","script",$str); $str=str_replace("join","join",$str); $str=str_replace("union","union",$str); $str=str_replace("where","where",$str); $str=str_replace("insert","insert",$str); $str=str_replace("delete","delete",$str); $str=str_replace("update","update",$str); $str=str_replace("like","like",$str); $str=str_replace("drop","drop",$str); $str=str_replace("create","create",$str); $str=str_replace("modify","modify",$str); $str=str_replace("rename","rename",$str); $str=str_replace("alter","alter",$str); $str=str_replace("cast","cas",$str); return $str; } //解碼 function htmldecode($str){ if(empty($str)) return; if($str=="") return $str; $str=str_replace("select","select",$str); $str=str_replace("join","join",$str); $str=str_replace("union","union",$str); $str=str_replace("where","where",$str); $str=str_replace("insert","insert",$str); $str=str_replace("delete","delete",$str); $str=str_replace("update","update",$str); $str=str_replace("like","like",$str); $str=str_replace("drop","drop",$str); $str=str_replace("create","create",$str); $str=str_replace("modify","modify",$str); $str=str_replace("rename","rename",$str); $str=str_replace("alter","alter",$str); $str=str_replace("cas","cast",$str); $str=str_replace("&","&",$str); $str=str_replace(">",">",$str); $str=str_replace("<","<",$str); $str=str_replace(" ",chr(32),$str); $str=str_replace(" ",chr(9),$str); $str=str_replace(" ",chr(9),$str); $str=str_replace("&",chr(34),$str); $str=str_replace("'",chr(39),$str); $str=str_replace(" ",chr(13),$str); $str=str_replace("''","'",$str); return $str; } // 函數:string_filter($string, $match_type=1) // 功能:過濾非法內容 // 參數: // $string 須要檢查的字符串 // $match_type 匹配類型,1爲精確匹配, 2爲模糊匹配,默認爲1 // // 返回:有非法內容返回True,無非法內容返回False // 其餘:非法關鍵字列表保存在txt文件裏, 分爲普通非法關鍵字和嚴重非法關鍵字兩個列表 // 做者:heiyeluren // 時間:2006-1-18 // //====================================================================== function lib_lawless_string_filter($string, $match_type=1) { //字符串空直接返回爲非法 $string = trim($string); if (empty($string)) { return false; } //獲取重要關鍵字列表和普通關鍵字列表 $common_file = "common_list.txt"; //通用過濾關鍵字列表 $signify_file = "signify_list.txt"; //重要過濾關鍵字列表 //若是任何列表文件不存在直接返回false,不然把兩個文件列表讀取到兩個數組裏 if (!file_exists($common_file) || !file_exists($signify_file)) { return false; } $common_list = file($common_file); $signify_list = file($signify_file); //精確匹配 if ($match_type == 1) { $is_lawless = exact_match($string, $common_list); } //模糊匹配 if ($match_type == 2) { $is_lawless = blur_match($string, $common_list, $signify_list); } //判斷檢索結果數組中是否有數據,若是有,證實是非法的 if (is_array($is_lawless) && !empty($is_lawless)) { return true; } else { return false; } } //--------------------- // 精確匹配,爲過濾服務 //--------------------- function exact_match($string, $common_list) { $string = trim($string); $string = lib_replace_end_tag($string); //檢索普經過濾關鍵字列表 foreach($common_list as $block) { $block = trim($block); if (preg_match("/^$string$/i", $block)) { $blist[] = $block; } } //判斷有沒有過濾內容在數組裏 if (!empty($blist)) { return array_unique($blist); } return false; } //---------------------- // 模糊匹配,爲過濾服務 //---------------------- function blur_match($string, $common_list, $signify_list) { $string = trim($string); $s_len = strlen($string); $string = lib_replace_end_tag($string); //檢索普經過濾關鍵字列表 foreach($common_list as $block) { $block = trim($block); if (preg_match("/^$string$/i", $block)) { $blist[] = $block; } } //檢索嚴重過濾關鍵字列表 foreach($signify_list as $block) { $block = trim($block); if ($s_len>=strlen($block) && preg_match("/$block/i", $string)) { $blist[] = $block; } } //判斷有沒有過濾內容在數組裏 if (!empty($blist)) { return array_unique($blist); } return false; } //-------------------------- // 替換HTML尾標籤,爲過濾服務 //-------------------------- function lib_replace_end_tag($str) { if (empty($str)) return false; $str = htmlspecialchars($str); $str = str_replace( '/', "", $str); $str = str_replace("\\", "", $str); $str = str_replace(">", "", $str); $str = str_replace("<", "", $str); $str = str_replace("", "", $str); $str = str_replace("", "", $str); $str=str_replace("select","select",$str); $str=str_replace("join","join",$str); $str=str_replace("union","union",$str); $str=str_replace("where","where",$str); $str=str_replace("insert","insert",$str); $str=str_replace("delete","delete",$str); $str=str_replace("update","update",$str); $str=str_replace("like","like",$str); $str=str_replace("drop","drop",$str); $str=str_replace("create","create",$str); $str=str_replace("modify","modify",$str); $str=str_replace("rename","rename",$str); $str=str_replace("alter","alter",$str); $str=str_replace("cas","cast",$str); $str=str_replace("&","&",$str); $str=str_replace(">",">",$str); $str=str_replace("<","<",$str); $str=str_replace(" ",chr(32),$str); $str=str_replace(" ",chr(9),$str); $str=str_replace(" ",chr(9),$str); $str=str_replace("&",chr(34),$str); $str=str_replace("'",chr(39),$str); $str=str_replace(" ",chr(13),$str); $str=str_replace("''","'",$str); $str=str_replace("css","'",$str); $str=str_replace("CSS","'",$str); return $str; //HTML標籤,能夠做爲擴展過濾 /* $tags = array("/html", "/head", "/body", "/div", "/span", "/DOCTYPE", "/title", "/link", "/meta", "/style", "/p", "/h1,", "/h2,", "/h3,", "/h4,", "/h5,", "/h6", "/strong", "/em", "/abbr", "/acronym", "/address", "/bdo", "/blockquote", "/cite", "/q", "/code", "/ins", "/del", "/dfn", "/kbd", "/pre", "/samp", "/var", "/br", "/a", "/img", "/area", "/map", "/object", "/param", "/ul", "/ol", "/li", "/dl", "/dt", "/dd", "/table", "/tr", "/td", "/th", "/tbody", "/thead", "/tfoot", "/col", "/colgroup", "/caption", "/form", "/input", "/textarea", "/select", "/option", "/optgroup", "/button", "/label", "/fieldset", "/legend", "/script", "/noscript", "/b", "/i", "/tt", "/sub", "/sup", "/big", "/small", "/hr" ); */ }
代碼:
引用是直接這樣:
$xxx = htmlspecialchars($_POST['xxx']);
或者
$xxx = htmlspecialchars($_GET['xxx']);
做爲PHP程序員,特別是新手,對於互聯網的險惡老是知道的太少,對於外部的入侵有不少時候是素手無策的,他們根本不知道黑客是如何入侵的、提交入侵、上傳漏洞、sql 注入、跨腳本攻擊等等。
做爲最基本的防範你須要注意你的外部提交,作好第一面安全機制處理防火牆。
規則 1:毫不要信任外部數據或輸入
關於Web應用程序安全性,必須認識到的第一件事是不該該信任外部數據。外部數據(outside data) 包括不是由程序員在PHP代碼中直接輸入的任何數據。在採起措施確保安全以前,來自任何其餘來源(好比 GET 變量、表單 POST、數據庫、配置文件、會話變量或 cookie)的任何數據都是不可信任的。 例如,下面的數據元素能夠被認爲是安全的,由於它們是在PHP中設置的。
可是,下面的數據元素都是有瑕疵的。
清單 2. 不安全、有瑕疵的代碼
爲 什麼第一個變量 $myUsername 是有瑕疵的?由於它直接來自表單 POST。用戶能夠在這個輸入域中輸入任何字符串,包括用來清除文件或運行之前上傳的文件的惡意命令。您可能會問,「難道不能使用只接受字母 A-Z 的客戶端(Javascrīpt)表單檢驗腳原本避免這種危險嗎?」是的,這老是一個有好處的步驟,可是正如在後面會看到的,任何人均可以將任何表單下載 到本身的機器上,修改它,而後從新提交他們須要的任何內容。 解決方案很簡單:必須對 $_POST['username'] 運行清理代碼。若是不這麼作,那麼在使用 $myUsername 的任何其餘時候(好比在數組或常量中),就可能污染這些對象。
對用戶輸入進行清理的一個簡單方法是,使用正則表達式來處理它。在這個示例中,只但願接受字母。將字符串限制爲特定數量的字符,或者要求全部字母都是小寫的,這可能也是個好主意。
清單 3. 使用戶輸入變得安全
規則 2:禁用那些使安全性難以實施的 PHP 設置 已經知道了不能信任用戶輸入,還應該知道不該該信任機器上配置 PHP 的方式。例如,要確保禁用 register_globals。若是啓用了 register_globals,就可能作一些粗心的事情,好比使用 $variable 替換同名的 GET 或 POST 字符串。經過禁用這個設置,PHP 強迫您在正確的名稱空間中引用正確的變量。要使用來自表單 POST 的變量,應該引用 $_POST['variable']。這樣就不會將這個特定變量誤會成 cookie、會話或 GET 變量。
規則 3:若是不能理解它,就不能保護它
一些開發人員使用奇怪的語法,或者將語句組織得很緊湊,造成簡短可是含義模糊的代碼。這種方式可能效率高,可是若是您不理解代碼正在作什麼,那麼就沒法決定如何保護它。
例如,您喜歡下面兩段代碼中的哪一段?
清單 4. 使代碼容易獲得保護
在第二個比較清晰的代碼段中,很容易看出 $input 是有瑕疵的,須要進行清理,而後才能安全地處理。 規則 4:「縱深防護」 是新的法寶 本教程將用示例來講明如何保護在線表單,同時在處理表單的 PHP 代碼中採用必要的措施。一樣,即便使用 PHP regex 來確保 GET 變量徹底是數字的,仍然能夠採起措施確保 SQL 查詢使用轉義的用戶輸入。
縱深防護不僅是一種好思想,它能夠確保您不會陷入嚴重的麻煩。 既然已經討論了基本規則,如今就來研究第一種威脅:SQL 注入攻擊。 防止 SQL 注入攻擊 在 SQL 注入攻擊 中,用戶經過操縱表單或 GET 查詢字符串,將信息添加到數據庫查詢中。例如,假設有一個簡單的登陸數據庫。這個數據庫中的每一個記錄都有一個用戶名字段和一個密碼字段。構建一個登陸表單,讓用戶可以登陸。
清單 5. 簡單的登陸表單
這個表單接受用戶輸入的用戶名和密碼,並將用戶輸入提交給名爲 verify.php 的文件。在這個文件中,PHP 處理來自登陸表單的數據,以下所示:
清單 6. 不安全的 PHP 表單處理代碼
這 段代碼看起來沒問題,對嗎?世界各地成百(甚至成千)的 PHP/MySQL 站點都在使用這樣的代碼。它錯在哪裏?好,記住 「不能信任用戶輸入」。這裏沒有對來自用戶的任何信息進行轉義,所以使應用程序容易受到攻擊。具體來講,可能會出現任何類型的 SQL 注入攻擊。
例如,若是用戶輸入 foo 做爲用戶名,輸入 ' or '1′='1 做爲密碼,那麼實際上會將如下字符串傳遞給 PHP,而後將查詢傳遞給 MySQL:
這個查詢老是返回計數值 1,所以 PHP 會容許進行訪問。經過在密碼字符串的末尾註入某些惡意 SQL,黑客就能裝扮成合法的用戶。
解 決這個問題的辦法是,將 PHP 的內置 mysql_real_escape_string() 函數用做任何用戶輸入的包裝器。這個函數對字符串中的字符進行轉義,使字符串不可能傳遞撇號等特殊字符並讓 MySQL 根據特殊字符進行操做。清單 7 展現了帶轉義處理的代碼。
清單 7. 安全的 PHP 表單處理代碼
使用 mysql_real_escape_string() 做爲用戶輸入的包裝器,就能夠避免用戶輸入中的任何惡意 SQL 注入。若是用戶嘗試經過 SQL 注入傳遞畸形的密碼,那麼會將如下查詢傳遞給數據庫: select count(*) as ctr from users where username='foo' and password='\' or \'1\'=\'1′ limit 1″
數據庫中沒有任何東西與這樣的密碼匹配。僅僅採用一個簡單的步驟,就堵住了 Web 應用程序中的一個大漏洞。這裏得出的經驗是,老是應該對 SQL 查詢的用戶輸入進行轉義。 可是,還有幾個安全漏洞須要堵住。下一項是操縱 GET 變量。 防止用戶操縱 GET 變量
在前一節中,防止了用戶使用畸形的密碼進行登陸。若是您很聰明,應該應用您學到的方法,確保對 SQL 語句的全部用戶輸入進行轉義。 但 是,用戶如今已經安全地登陸了。用戶擁有有效的密碼,並不意味着他將按照規則行事 —— 他有不少機會可以形成損害。例如,應用程序可能容許用戶查看特殊的內容。全部連接指向 template.php?pid=33 或 template.php?pid=321 這樣的位置。URL 中問號後面的部分稱爲查詢字符串。由於查詢字符串直接放在 URL 中,因此也稱爲 GET 查詢字符串。 在 PHP 中,若是禁用了 register_globals,那麼能夠用 $_GET['pid'] 訪問這個字符串。在 template.php 頁面中,可能會執行與清單 8 類似的操做。 清單 8. 示例 template.php
代碼以下:
這 裏有什麼錯嗎?首先,這裏隱含地相信來自瀏覽器的 GET 變量 pid 是安全的。這會怎麼樣呢?大多數用戶沒那麼聰明,沒法構造出語義攻擊。可是,若是他們注意到瀏覽器的 URL 位置域中的 pid=33,就可能開始搗亂。若是他們輸入另外一個數字,那麼可能沒問題;可是若是輸入別的東西,好比輸入 SQL 命令或某個文件的名稱(好比 /etc/passwd),或者搞別的惡做劇,好比輸入長達 3,000 個字符的數值,那麼會發生什麼呢?
在這種狀況下,要記住基本規則,不要信任用戶輸入。應用程序開發人員知道 template.php 接受的我的標識符(PID)應該是數字,因此可使用 PHP 的 is_numeric() 函數確保不接受非數字的 PID,以下所示:
清單 9. 使用 is_numeric() 來限制 GET 變量
這個方法彷佛是有效的,可是如下這些輸入都可以輕鬆地經過 is_numeric() 的檢查: 100 (有效)
100.1 (不該該有小數位) +0123.45e6 (科學計數法 —— 很差) 0xff33669f (十六進制 —— 危險!危險!)
那麼,有安全意識的 PHP 開發人員應該怎麼作呢?多年的經驗代表,最好的作法是使用正則表達式來確保整個 GET 變量由數字組成,以下所示:
清單 10. 使用正則表達式限制 GET 變量
需 要作的只是使用 strlen() 檢查變量的長度是否非零;若是是,就使用一個全數字正則表達式來確保數據元素是有效的。若是 PID 包含字母、斜線、點號或任何與十六進制類似的內容,那麼這個例程捕獲它並將頁面從用戶活動中屏蔽。若是看一下 Page 類幕後的狀況,就會看到有安全意識的 PHP 開發人員已經對用戶輸入 $pid 進行了轉義,從而保護了 fetchPage() 方法,以下所示:
清單 11. 對 fetchPage() 方法進行轉義
您可能會問,「既然已經確保 PID 是數字,那麼爲何還要進行轉義?」 由於不知道在多少不一樣的上下文和狀況中會使用 fetchPage() 方法。必須在調用這個方法的全部地方進行保護,而方法中的轉義體現了縱深防護的意義。 如 果用戶嘗試輸入很是長的數值,好比長達 1000 個字符,試圖發起緩衝區溢出攻擊,那麼會發生什麼呢?下一節更詳細地討論這個問題,可是目前能夠添加另外一個檢查,確保輸入的 PID 具備正確的長度。您知道數據庫的 pid 字段的最大長度是 5 位,因此能夠添加下面的檢查。
清單 12. 使用正則表達式和長度檢查來限制 GET 變量
如今,任何人都沒法在數據庫應用程序中塞進一個 5,000 位的數值 —— 至少在涉及 GET 字符串的地方不會有這種狀況。想像一下黑客在試圖突破您的應用程序而遭到挫折時咬牙切齒的樣子吧!並且由於關閉了錯誤報告,黑客更難進行偵察。 緩衝區溢出攻擊
緩衝區溢出攻擊 試圖使 PHP 應用程序中(或者更精確地說,在 Apache 或底層操做系統中)的內存分配緩衝區發生溢出。請記住,您多是使用 PHP 這樣的高級語言來編寫 Web 應用程序,可是最終仍是要調用 C(在 Apache 的狀況下)。與大多數低級語言同樣,C 對於內存分配有嚴格的規則。
緩衝區溢出攻擊向緩衝區發送大量數據,使部分數據溢出到相鄰的內存緩衝區,從而破壞緩衝區或者重寫邏輯。這樣就可以形成拒絕服務、破壞數據或者在遠程服務器上執行惡意代碼。
防止緩衝區溢出攻擊的唯一方法是檢查全部用戶輸入的長度。例如,若是有一個表單元素要求輸入用戶的名字,那麼在這個域上添加值爲 40 的 maxlength 屬性,並在後端使用 substr() 進行檢查。清單 13 給出表單和 PHP 代碼的簡短示例。
清單 13. 檢查用戶輸入的長度
爲 什麼既提供 maxlength 屬性,又在後端進行 substr() 檢查?由於縱深防護老是好的。瀏覽器防止用戶輸入 PHP 或 MySQL 不能安全地處理的超長字符串(想像一下有人試圖輸入長達 1,000 個字符的名稱),然後端 PHP 檢查會確保沒有人遠程地或者在瀏覽器中操縱表單數據。 正如您看到的,這種方式與前一節中使用 strlen() 檢查 GET 變量 pid 的長度類似。在這個示例中,忽略長度超過 5 位的任何輸入值,可是也能夠很容易地將值截短到適當的長度,以下所示:
清單 14. 改變輸入的 GET 變量的長度
注 意,緩衝區溢出攻擊並不限於長的數字串或字母串。也可能會看到長的十六進制字符串(每每看起來像 \xA3 或 \xFF)。記住,任何緩衝區溢出攻擊的目的都是淹沒特定的緩衝區,並將惡意代碼或指令放到下一個緩衝區中,從而破壞數據或執行惡意代碼。對付十六進制緩 衝區溢出最簡單的方法也是不容許輸入超過特定的長度。
若是您處理的是容許在數據庫中輸入較長條目的表單文本區,那麼沒法在客戶端輕鬆地限制數據的長度。在數據到達 PHP 以後,可使用正則表達式清除任何像十六進制的字符串。
清單 15. 防止十六進制字符串
您 可能會發現這一系列操做有點兒太嚴格了。畢竟,十六進制串有合法的用途,好比輸出外語中的字符。如何部署十六進制 regex 由您本身決定。比較好的策略是,只有在一行中包含過多十六進制串時,或者字符串的字符超過特定數量(好比 128 或 255)時,才刪除十六進制串。
跨站點腳本攻擊 在跨站點腳本(XSS)攻擊中,每每有一個惡意用戶在表單中(或經過其餘用戶輸入方式)輸入信息,這些輸入將惡 意的客戶端標記插入過程或數據庫中。例如,假設站點上有一個簡單的來客登記簿程序,讓訪問者可以留下姓名、電子郵件地址和簡短的消息。惡意用戶能夠利用這 個機會插入簡短消息以外的東西,好比對於其餘用戶不合適的圖片或將用戶重定向到另外一個站點的 Javascrīpt,或者竊取 cookie 信息。
幸運的是,PHP 提供了 strip_tags() 函數,這個函數能夠清除任何包圍在 HTML 標記中的內容。strip_tags() 函數還容許提供容許標記的列表,好比 <b> 或 <i>。 瀏覽器內的數據操縱
有一類瀏覽器插件容許用戶篡改頁面上的頭部元素和表單元素。使用 Tamper Data(一個 Mozilla 插件),能夠很容易地操縱包含許多隱藏文本字段的簡單表單,從而向 PHP 和 MySQL 發送指令。 用戶在點擊表單上的 Submit 以前,他能夠啓動 Tamper Data。在提交表單時,他會看到表單數據字段的列表。Tamper Data 容許用戶篡改這些數據,而後瀏覽器完成表單提交。
讓咱們回到前面創建的示例。已經檢查了字符串長度、清除了 HTML 標記並刪除了十六進制字符。可是,添加了一些隱藏的文本字段,以下所示:
清單 17. 隱藏變量
注意,隱藏變量之一暴露了表名:users。還會看到一個值爲 create 的 action 字段。只要有基本的 SQL 經驗,就可以看出這些命令可能控制着中間件中的一個 SQL 引擎。想搞大破壞的人只需改變表名或提供另外一個選項,好比 delete。
如今還剩下什麼問題呢?遠程表單提交。 遠程表單提交 Web 的好處是能夠分享信息和服務。壞處也是能夠分享信息和服務,由於有些人作事毫無顧忌。 以 表單爲例。任何人都可以訪問一個 Web 站點,並使用瀏覽器上的 File > Save As 創建表單的本地副本。而後,他能夠修改 action 參數來指向一個徹底限定的 URL(不指向 formHandler.php,而是指向 http://www.yoursite.com/formHandler.php,由於表單在這個站點上),作他但願的任何修改,點擊 Submit,服務器會把這個表單數據做爲合法通訊流接收。 首先可能考慮檢查 $_SERVER['HTTP_REFERER'],從而判斷請求是否來自本身的服務器,這種方法能夠擋住大多數惡意用戶,可是擋不住最高明的黑客。這些人足夠聰明,可以篡改頭部中的引用者信息,使表單的遠程副本看起來像是從您的服務器提交的。
處理遠程表單提交更好的方式是,根據一個唯一的字符串或時間戳生成一個令牌,並將這個令牌放在會話變量和表單中。提交表單以後,檢查兩個令牌是否匹配。若是不匹配,就知道有人試圖從表單的遠程副本發送數據。
要建立隨機的令牌,可使用 PHP 內置的 md5()、uniqid() 和 rand() 函數,以下所示:
清單 18. 防護遠程表單提交;可限制IP;
這種技術是有效的,這是由於在 PHP 中會話數據沒法在服務器之間遷移。即便有人得到了您的 PHP 源代碼,將它轉移到本身的服務器上,並向您的服務器提交信息,您的服務器接收的也只是空的或畸形的會話令牌和原來提供的表單令牌。它們不匹配,遠程表單提交就失敗了。