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 源代码,将它转移到本身的服务器上,并向您的服务器提交信息,您的服务器接收的也只是空的或畸形的会话令牌和原来提供的表单令牌。它们不匹配,远程表单提交就失败了。