一、开始前的一些建议

1.不要相信任何用户输入或第三方数据来源,包括$_GET、$_POST($_FILES)、$_COOKIE、$_SERVER的部分参数等。

2.HTML、PHP、MYSQL等使用统一的UTF-8编码。

3.数据库SQL构造时尽量使用’包裹参数。

4.参数对比时正确使用 === 和 == 。

5.尽量选择白名单而非黑名单式过滤。

二、关于SQL注入

1.SQL注入的产生原因

SQL注入的产生在于外界的输入改变了原本定义的SQL语意,譬如:

原本定义的SQL语意,

$_GET[‘name’] = ‘abc’;

$name = $_GET[‘name’];

SELECT * FROM admin where user_name = ‘$name’;

最终生成的SQL为,SELECT * FROM admin where user_name = ‘abc’;

外界输入改变后的语意,

$_GET[‘name’] = ‘abc\’ or \’a\’=\’a’;

$name = $_GET[‘name’];

SELECT * FROM admin where user_name = ‘$name’;

最终生成的SQL为,SELECT * FROM admin where user_name = ‘abc’ or ‘a’=’a’;

此时SQL的语意多加了原定之外的 or ‘a’=’a’ ,于是注入产生。

2.SQL注入的防御

(1)使用PDO或mysqli预编译处理SQL

使用预处理语句时,PHP请求MySQL将SQL进行预编译,然后再发送参数,因此无论参数是何内容MySQL均将参数当作普通的字符串(或整型、浮点型)处理。

例如上一例子最终生成的SQL为,SELECT * FROM admin where user_name = ‘abc\’ or \’a\’=\’a’;

此时无法发生SQL语意的改变,故能防止SQL注入。

注意:PDO兼容大部分数据库,但需要PHP版本5.0+;mysqli只支持MySQL数据库并且需要MySQL数据库版本4.1.13+(服务端版本5.0.7+)。

(2)无法使用PDO或mysqli的预编译处理SQL时,使用addslashes而非mysql_real_escape_string作为临时安全过滤函数,对于参数类型为整型时使用intval将变量转换成整型。

使用addslashes能有效作为安全过滤的前提条件是PHP请求数据库时的字符集为UTF-8参数位于 ” 内,其他的诸如GBK等因其字符集的特殊性,部分特殊字符转义后仍能形成 ‘ 从而绕过转义。譬如:

在GBK字符集的环境下,0xbf27经过addslashes转义后得到0xbf5c27,数据库将0xbf5c当作单字节字符而剩余的27则被当作 ‘ 解析,故而依旧能造成SQL注入。

注 意:在旧版的PHP中(具体版本未知)mysql_real_escape_string是MySQL扩展中的函数,并非PHP原有函数,此函数在调用时 先判断是否已经连接上数据库,这就意味着mysql_real_escape_string必须是连接数据库之后才能使用,新版的PHP虽然调用时不再需 要判断是否已经连接上数据库,但会抛出一个警告(mysql_real_escape_string(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead)。

 

三、关于XSS

1.XSS产生的原因

正如《白帽子讲web安全》中所说:XSS本质还是一直“HTML注入”,用户的数据被当成了HTML代码的一部分来执行,从而混淆了原本语意,譬如:

原语意,

$_GET[‘page’] = 1;

$page = $_GET[‘page’];

<a href=”http://www.xxx.com/?page=$page”>xss</a>

最终生成 <a href=”http://www.xxx.com/?page=1“>xss</a>

外界输入改变后的语意,

$_GET[‘page’] = ‘1″ onlick=”alert(1)”>’;

$page = $_GET[‘page’];

<a href=”http://www.xxx.com/?page=$page“>xss</a>

最终生成 <a href=”http://www.xxx.com/?page=1” onclick=”alert(1)”>xss</a>

2.XSS的防御

(1)针对普通HTML的输出,使用htmlspecialchars进行安全过滤。

(2)针对链接类型(如图片、超链等)的输出也可使用htmlspecialchars进行安全过滤,但如果变量是整个URL则应检查这个变量是否以http或https开头,如果不是则自动补齐避免出现伪协议类的XSS攻击。(例子:<a href=”data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTs8L3NjcmlwdD4=”>xss</a>)

注意:htmlentities只有使用ASCII或LATIN-1等HTML字符编码时才需要使用,因为htmlentities会将很多安全的字符也转换成HTML编码。由于htmlspecialchars并不会将 ‘ 转换成HTML编码,所以只有标签属性是以 “” 包裹变量而非 ” 时使用htmlspecialchars才是安全的。

(3)针对富文本,采用白名单模式放行安全的标签及属性,再针对可能产生XSS的标签和属性做针对的安全过滤。

四、关于CSRF

1.CSRF产生的原因

攻击源以用户的身份伪造请求,譬如:

a.com 站下存在着这样的一个HTML标签,<img src=”http://b.com?action=del&id=1″>

用户请求a.com时将会产生一条请求 http://b.com?action=del&id=1 (假设这个请求的作用是删除id为1的一篇文章),浏览器会以用户当前浏览器状态在b.com的身份(主要为cookie信息)请求http://b.com?action=del&id=1执行删除id为1的文章操作,这个操作并不是用户主观操作而是攻击源以用户身份伪造的操作,并且这个请求是能够被攻击源预知的。

2.CSRF的防御

(1)Anti CSRF token,对于敏感的操作(譬如增删改以及敏感信息的查看)使用随机的token进行验证。

注意:Token必须足够随机,且Token验证后应立即删除。Token应尽量放在表单中采用POST的方式提交避免Token泄露。

五、关于文件上传

1.文件上传漏洞产生的原因

正常情况下上传文件是一个合理的功能,漏洞的产生在于用户上传了一个逻辑上并不允许上传的文件类型,这个文件能够被Web容器解析并且用户能够通过Web访问。譬如:

$allowExt = array(‘image/jpeg’,’image/x-png‘,’image/gif’);

if(in_array($_FILES[‘file’][‘type’],$allowExt)){

move_uploaded_file($_FILES[‘file’][‘tmp_name’],$_FILES[‘file’][‘name’])

}

2.文件上传漏洞的防御

(1)允许上传的文件类型应采用白名单方式,结合MIME Type和后缀检查文件类型,上传文件中$_FILES[‘file’][‘type’]所示类型不可信。如上例,

$_FILES[‘file’][‘type’]为客户端浏览器检测所选上传文件后提供,但是此参数可被人为抓包修改,因此参数不可信。

(2)使用随机文件名保存上传的文件,避免因终止符造成的文件名中断。如上例,

当$_FILES[‘file’][‘name’]为xxx.php[\0].jpg时,对于低版本PHP(详细版本未知)而言,PHP保存文件的过程中遇到终止符[\0](0x00的16进制)时误认为文件名已结束,故最终保存在服务器的文件名将是xxx.php。

六、关于代码执行漏洞

1.代码执行漏洞的产生

危险函数执行了我们原定计划之外的代码,而计划之外的代码可由用户控制,譬如:

$_GET[‘test’] = ‘phpinfo()”;

$test = $_GET[‘test’];

eval($test);

最终代码等价:phpinfo();

2.代码执行漏洞的防御

(1)能够执行PHP代码的函数均有可能成为危险函数,危险函数的输入如为用户可控变量时使用白名单模式限制。

(2)使用 preg_replace_callback代替preg_replace的/e模式并且严格控制正则匹配的内容是否可能产生不安全变量。

注意:eval是zend提供的函数而并非PHP的原生函数,因此通过php.ini disable_functions无法禁用(需通过第三方插件禁用)。

七、关于重放攻击

1.重放攻击的产生

攻击源通过构造请求使目标服务器相信当前请求来源安全并正确响应请求。譬如:

一个修改原始密码的链接,

http://www.abc.com/index.php?action=alterPassword&uid=123456&token=123

目标服务器仅验证token是否有效,有效则允许修改uid为123456的用户密码

2.重放攻击的防御

(1)对请求的所有参数进行签名防止参数被篡改,并且请求参数必须带有当前请求的时间戳用于目标服务器判断当前的请求是否过期。