PHP中SQL注入与跨站攻击的防范

SQL injection即SQL注入是我们每个WEB程序员都需要面对的问题,一个WEB应用假如没有起码的安全性,那么其它的一切就可以免谈了。注入问题在 ASP上可谓是闹得沸沸扬扬,当然还有不少PHP程序“遇难”。至于SQL injection的详情,网上的文章很多,在此就不作赘述。追其罪恶之源,就是我们误以为用户提交的数据是可靠的。 

    无论你是否有足够的PHP安全开发经验,本文的目的就是用来帮助你构建更为安全的在线应用程序。针对不同的情况,我们可以使用下面的一种或几种方法来对SQL注入的风险进行预防。 

    1、在书写SQL语句时不要省略单引号,即使是整型字段也应该加上单引号。 
    首先,从技术上讲,引号对于数字值来说是不需要使用的。但是,假如你不使用引号把例如书籍数量这样的一个值括起来,并且假如你的用户把一个空值输入到你的表单中,那么,你将会看到一个类似下面的查询: 
SELECT * FROM books WHERE num = 
    当然,这个查询从语法上讲是无效的;但是,下面的语法却是有效的: 
SELECT * FROM books WHERE num = '' 
    第二个查询虽然也不会返回任何结果,但是至少它不会返回一个错误消息。 
    其次,单引号可以增加注入者的难度,第二句由于把变量放在一对单引号中,这样使得我们所提交的变量都变成了字符串,即使包含了正确的SQL语句,也不会正 常执行,而第一句不同,由于没有把变量放进单引号中,那我们所提交的一切,只要包含空格,那空格后的变量都会作为SQL语句执行,因此,我们要养成给 SQL语句中变量加引号的习惯。 

    2、检查用户提交的值的类型,对接收到的整型参数使用intval()强制转换成整形。 
    我们知道SQL注入的主要来源往往出在一个意料之外的表单提交或URL参数中,所以当你接受一个由用户提交的参数时,你应该有相当的权利来确定你想取得什 么样的输入内容。在以前的学习中我们已经讨论过很多这样或那样的校验问题。因此我们只要简单的总结当时我们讨论的要点即可比较轻易的检查用户提交数据的有 效性。假如你期望得到的是一个数值,那么你可以使用下面这些技术之一来确保你得到的参数的安全性。 
使用is_int()函数(或is_integer()或is_long())。 
使用gettype()函数。 
使用intval()函数。 
使用settype()函数。 
    我们通常把传送过来的整型参数使用intval()函数强制转换成整形,因为假如不这样做,接收到的查询子句很可能会附带着其它一些我们并不愿看到的语 句,比如原本应该是“nid=17”可能会成为“nid=17 or 1=1”,这会使我们预计的SQL语句变成这样: 
SELECT * FROM news WHERE nid=17 or 1=1 
这对于一个新闻表中的信息可能不会造成什么大的危害,但假如是在显示某个用户的信息时呢? 
    此外,为了检查用户输入内容的长度,你可以使用strlen()函数。为了检查一个期望的时间或日期是否有效,你可以使用strtotime()函数。它 似乎一定能够确保一位用户的提交参数中没有包含分号字符(除非标点符号可以被合法地包括在内)。你可以借助于strpos()函数轻易地实现这一点,如下 所示: 
if( strpos( $variety, ';' ) ) exit ( "$variety is an invalid value for variety!" ); 
正如我们在前面所提到的,只要你仔细分析你的用户输入期望,那么,你应该能够很轻易地检查出其中存在的许多问题。 

    3、使用mysql_real_escape_string()函数从查询字符串中过滤掉危险字符 
    尽管有很多文章已经讨论过如何过滤掉危险字符的问题,但是在本文中还是让我们再次简单的强调并归纳一下这个问题: 
    不要使用magic_quotes_gpc指令或它的搭挡addslashes()函数,此函数在程序开发中应该是被限制使用的,在PHP的下一个版本 PHP6中已经取消了对此函数的支持,并且此函数还要求使用额外的步骤stripslashes()函数。相比之 下,mysql_real_escape_string()函数更为适合,受此函数影响的字符包括:\x00,\n,\r,\,',",\x1a。这二个 函数的功能类似,但addslashes()函数无法转换以十六进制形式提交的字符,另外需要注重的 是,mysqli_real_escape_string()函数需要先建立数据库连接,因为需要考虑到连接的当前字符集,通常防止数据库被攻击的使用方 法如下: 

function check_input($value) 

// 去除斜杠 
if (get_magic_quotes_gpc()) 
  { 
  $value = stripslashes($value); 
  } 
// 假如不是数字则加引号 
if (!is_numeric($value)) 
  { 
  $value = "'" . mysqli_real_escape_string($value) . "'"; 
  } 
return $value; 

?> 

    4、对用户输入的字符进行HTML编码以防止跨站攻击 
    对于数据库防止SQL注入的问题,通过前面三点所述综合的运用我们似乎全部解决,但假如用户提交的内容中有一些不良的HTML标签则可能会导致页面变形,严重的还可能会出现跨站攻击之类的安全问题,所以对带有HTML的内容增加过滤检查是很有必要的。 
    例如一段接收用户的输入并显示的功能代码,一但恶意用户输入: 这样的一条数据,网页在显示该条数据时,用户的浏览器将会跳转到恶意代码指定的地址,这就是最简单的跨站攻击,我们试想一下,假如跳转的地址是一个伪造原 网站来骗取用户密码或银行信息的页面,那么会是什么样的后果? 
    对于这类攻击行为,就需要我们在接收用户提交的数据时在服务器端进行过滤,我们可以编写一个过滤函数用来查询提交数据中有害字符并将之替换掉,还有一种较 简便的方式就把用户所提交的内容中“<”,“>”,“&”等符号转换成正确的HTML编码,而函数 htmlspecialchars()正是干这个活的。 
htmlspecialchars() 
功能:将“'"&<>”五个字符转换成HTML字符串。 
语法:string htmlspecialchars(string,quotestyle,character-set); 
参数:ENT_NOQUOTES,不对任何引号进行格式化;ENT_QUOTES,对单引号及双引号进行格式化;默认为ENT_COMPAT,仅编码双引号。 
返回值:字符串 

因此,用户提交的数据经过格式化后为: 
<script type='text/javascript'>location.href='http://xxx.com';</script> 
这样代码就失去了原有的攻击功能,可以基本预防脚本攻击的潜在危险,假如不明白这之间的区别,可以测试下面这段代码: 

$str="PHP3C技术分享社区"; 
echo $str; 
echo "
"; 

echo htmlspecialchars($str); 
?> 
    另外,转换非凡字符为HTML字符串还可以使用函数htmlentities(),使用方法相同,它与htmlspecialchars在格式化带有英文 字符的html代码的时候没有什么区别,但是htmlentities对中文字符也不放过,所以同样的调用方法得出来的结果是由于没有指定字符集而默认由 ISO-8859-1 代替,中文字符部分变为一堆乱码,因此我们在使用的时候,需要指定参数character-set为我们页面使用的字符集,例如'UTF-8'或 'GB2312'。 

    SQL注入攻击是针对服务器端的数据库实行的,而跨站脚本攻击是把恶意javascript代码在用户的客户端运行,但他们的相同之处在于攻击者必须通过 互联网的输入区域插入恶意的数据,因此我们只要做好用户提交数据的过滤以及合法性验证就能从源头上制止这二类攻击,但这种过滤及验证被很多程序员所忽视 的,希望本文能能引起大家的重视。

你可能感兴趣的:(Web,security)