本文将从sql注入风险说起,并且比较addslashes、mysql_escape_string、mysql_real_escape_string、mysqli和pdo的预处理的区别。
当一个变量从表单传入到php,需要查询mysql的话,需要进行处理。
$unsafe_variable = $_POST['user_input'];
用户可以输入诸如 : value'); DROP TABLE table; ,SQL语句就变成这样了:
执行的结果就是table表被删掉了。
1.魔术引用 (推荐指数3)
addslashes()用于对变量中的' " \和NULL添加\斜杠,用于避免传入sql语句的参数格式错误,同时如果有人注入子查询,通过加\可以将参数解释为内容,而非执行语句,避免被mysql执行。
那么,tripslashes()的作用是将加了\的php变量\去掉,由于\不会写入mysql中,所以从mysql查询出来的内容不需要再tripslashes()。
如果从网页表单、php、mysql都使用utf8编码,则没有这个问题。
参考:https://segmentfault.com/q/1010000005994443
由于addslashes()不检测字符集,所以有宽字节注入风险,所以php中添加了这个函数。
gbk宽字符漏洞导致的sql注入
参考:http://www.cnblogs.com/suihui/archive/2012/09/20/2694751.html
与addslashes()相比,不仅会将' " \ NOL(ascii的0)转义,还会把\r \n进行转义。同时会检测数据编码。
此函数在使用时会使用于数据库连接(因为要检测字符集),并根据不同的字符集做不同的操作。如果当前连接不存在,刚会使用上一次的连接。
此方法在php5.5后不被建议使用,在php7中废除。
3.预处理查询 (Prepared Statements) (推荐指数5)
为什么预处理和参数化查询可以防止sql注入呢?
在mysql5.1后,提供了类似于jdbc的预处理参数化查询。它的查询方法是:
b. 再向mysql发送需要查询的参数
参考:
http://blog.csdn.net/sky_zhe/article/details/9702489
http://www.cnblogs.com/LoveJenny/archive/2013/01/15/2860553.html
预处理分为两种:
看一个完整的用法:
$stmt = $mysqli>prepare("SELECT id, label FROM test WHERE id = ?");
$stmt>execute();
$row = $res>fetch_assoc();
b.替换变量
d.得到一个二进制结果集,从二进制结果中取出php结果集
使用预处理,一条查询分两步,先是,所以很安全。也是php5.5及php7推荐方法。
http://www.cnblogs.com/liuzhang/p/4753467.html
pdo是一个php官方推荐的数据库抽象层,提供了很多实用的工具。
使用方法跟上面差不多,区别在于pdo提供了更多样的方法。
我们在上面预处理参数化查询是在mysql中进行防注入操作的,其实pdo也内置了一个预处理的模拟器,叫做ATTR_EMULATE_PREPARES。
这种情况下,PDO驱动能否正确转义输入参数,是拦截SQL注入的关键。然而PHP 5.3.6及老版本,并不支持在DSN中定义charset属性(会忽略之),这时如果使用PDO的本地转义,仍然可能导致SQL注入,
如果ATTR_EMULATE_PREPARES=false,sql会分两次把参数给送给mysql,mysql根据自身的字符集(set names )进行处理,完成查询。
但由于各版本差异,pdo在各版本中的实现程度也不一样,有些版本还有bug,我们以php5.3.6做为分界线来进行说明:
php5.3.6以下版本
$pdo = new PDO("mysql:host=localhost;dbname=test;",'root','pwd');
$pdo>setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$pdo>exec('set names utf8');
$id = '0 or 1 =1 order by id desc';
$sql = "select from article where id = ?";
$statement = $pdo>prepare($sql);
$statement>bindParam(1, $id);
$statement>execute();
如上,我们关闭了本地预处理模拟器,参数会直接分批发送给mysql,由mysql根据set name utf8字符集进行检测,完成sql注入处理。以上代码不会产生注入。
php5.3.6以上版本
$pdo = new PDO("mysql:host=localhost;dbname=test;charset=utf8",'root','pwd');
$pdo>exec('set names utf8');
$id = '0 or 1 =1 order by id desc';
$sql = "select from article where id = ?";
$statement = $pdo>prepare($sql);
$statement>bindParam(1, $id);
$statement>execute();
在php5.3.6以上版本中,默认情况下ATTR_EMULATE_PREPARES开启,模拟器会根据new PDO()中的charset=utf8进行检测,在模拟器上完成防注入操作。如果把模拟器关闭,也会像低版本一样送交mysql进行防注入处理。
参考:
PDO防注入原理分析以及使用PDO的注意事项
http://zhangxugg163com.iteye.com/blog/1835721
PHP 5.3.6及以前版本的PDO的bindParam,bindValue潜在的安全隐患
http://zhangxugg163com.iteye.com/blog/1855088
再论php 5.3.6以前版本中的PDO SQL注入漏洞问题
http://my.oschina.net/zxu/blog/163135
segmentfault讨论
https://segmentfault.com/q/1010000000723496
4. html输出与防止xss注入
特殊字符输出
比如' " < >有着特殊的意义,如果直接写到html中输出,会引起dom格式的错乱,那么就需要用到特殊的输出方法。
$unsafe_variable = $_POST['user_input'];
用户可以输入诸如 : value'); DROP TABLE table; ,SQL语句就变成这样了:
执行的结果就是table表被删掉了。
1.魔术引用 (推荐指数3)
addslashes()用于对变量中的' " \和NULL添加\斜杠,用于避免传入sql语句的参数格式错误,同时如果有人注入子查询,通过加\可以将参数解释为内容,而非执行语句,避免被mysql执行。
那么,tripslashes()的作用是将加了\的php变量\去掉,由于\不会写入mysql中,所以从mysql查询出来的内容不需要再tripslashes()。
如果从网页表单、php、mysql都使用utf8编码,则没有这个问题。
参考:https://segmentfault.com/q/1010000005994443
由于addslashes()不检测字符集,所以有宽字节注入风险,所以php中添加了这个函数。
gbk宽字符漏洞导致的sql注入
参考:http://www.cnblogs.com/suihui/archive/2012/09/20/2694751.html
与addslashes()相比,不仅会将' " \ NOL(ascii的0)转义,还会把\r \n进行转义。同时会检测数据编码。
此函数在使用时会使用于数据库连接(因为要检测字符集),并根据不同的字符集做不同的操作。如果当前连接不存在,刚会使用上一次的连接。
此方法在php5.5后不被建议使用,在php7中废除。
3.预处理查询 (Prepared Statements) (推荐指数5)
为什么预处理和参数化查询可以防止sql注入呢?
在mysql5.1后,提供了类似于jdbc的预处理参数化查询。它的查询方法是:
b. 再向mysql发送需要查询的参数
参考:
http://blog.csdn.net/sky_zhe/article/details/9702489
http://www.cnblogs.com/LoveJenny/archive/2013/01/15/2860553.html
预处理分为两种:
看一个完整的用法:
$stmt = $mysqli>prepare("SELECT id, label FROM test WHERE id = ?");
$stmt>execute();
$row = $res>fetch_assoc();
b.替换变量
d.得到一个二进制结果集,从二进制结果中取出php结果集
使用预处理,一条查询分两步,先是,所以很安全。也是php5.5及php7推荐方法。
http://www.cnblogs.com/liuzhang/p/4753467.html
pdo是一个php官方推荐的数据库抽象层,提供了很多实用的工具。
使用方法跟上面差不多,区别在于pdo提供了更多样的方法。
我们在上面预处理参数化查询是在mysql中进行防注入操作的,其实pdo也内置了一个预处理的模拟器,叫做ATTR_EMULATE_PREPARES。
这种情况下,PDO驱动能否正确转义输入参数,是拦截SQL注入的关键。然而PHP 5.3.6及老版本,并不支持在DSN中定义charset属性(会忽略之),这时如果使用PDO的本地转义,仍然可能导致SQL注入,
如果ATTR_EMULATE_PREPARES=false,sql会分两次把参数给送给mysql,mysql根据自身的字符集(set names )进行处理,完成查询。
但由于各版本差异,pdo在各版本中的实现程度也不一样,有些版本还有bug,我们以php5.3.6做为分界线来进行说明:
php5.3.6以下版本
$pdo = new PDO("mysql:host=localhost;dbname=test;",'root','pwd');
$pdo>setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$pdo>exec('set names utf8');
$id = '0 or 1 =1 order by id desc';
$sql = "select from article where id = ?";
$statement = $pdo>prepare($sql);
$statement>bindParam(1, $id);
$statement>execute();
如上,我们关闭了本地预处理模拟器,参数会直接分批发送给mysql,由mysql根据set name utf8字符集进行检测,完成sql注入处理。以上代码不会产生注入。
php5.3.6以上版本
$pdo = new PDO("mysql:host=localhost;dbname=test;charset=utf8",'root','pwd');
$pdo>exec('set names utf8');
$id = '0 or 1 =1 order by id desc';
$sql = "select from article where id = ?";
$statement = $pdo>prepare($sql);
$statement>bindParam(1, $id);
$statement>execute();
在php5.3.6以上版本中,默认情况下ATTR_EMULATE_PREPARES开启,模拟器会根据new PDO()中的charset=utf8进行检测,在模拟器上完成防注入操作。如果把模拟器关闭,也会像低版本一样送交mysql进行防注入处理。
参考:
PDO防注入原理分析以及使用PDO的注意事项
http://zhangxugg163com.iteye.com/blog/1835721
PHP 5.3.6及以前版本的PDO的bindParam,bindValue潜在的安全隐患
http://zhangxugg163com.iteye.com/blog/1855088
再论php 5.3.6以前版本中的PDO SQL注入漏洞问题
http://my.oschina.net/zxu/blog/163135
segmentfault讨论
https://segmentfault.com/q/1010000000723496
4. html输出与防止xss注入
特殊字符输出
比如' " < >有着特殊的意义,如果直接写到html中输出,会引起dom格式的错乱,那么就需要用到特殊的输出方法。