4.1.2 漏洞防范
SQL 注入漏洞虽然是目前最泛滥的漏洞,不过要解决 SQL 注入漏洞其实还比较简单。在 PHP 中可以利用魔术引号来解决,不过魔术引号在 PHP 5.4 后被取消,并且 gpc 在遇到 int 型的注入时也会显得不那么给力了,所以通常用得多的还是过滤函数和类,像 discuz、dedecms、phpcms 等程序里面都使用过滤类,不过如果单纯的过滤函数写得不够严谨,也会出现绕过的情况,像这三套程序就都存在绕过问题。当然最好的解决方案还是利用预编译的方式,下面就来看看这三种方式的使用方法。
4.1.2.1 gpc/rutime 魔术引号
通常数据污染有两种方式,一种是应用被动接收参数,类似于 GET、POST 等;还有一种是主动获取参数,类似于读取远程页面或者文件内容等。所以防止 SQL 注入的方法就是要守住这两条路。在本书第 2 章第 3 节介绍了 PHP 的核心配置,里面详细介绍了 GPC 等魔术引号配置的方法,magic_quotes_gpc 负责对 GET、POST、COOKIE 的值进行过滤,magic_quotes_runtime 对从数据库或者文件中获取的数据进行过滤。通常在开启这两个选项之后能防住部分 SQL 注入漏洞被利用。为什么说是部分,因为我们之前也介绍了,它们只对单引号(')、双引号(")、反斜杠(\)及空字符 NULL 进行过滤,在 int 型的注入上是没有多大作用的。
PHP 4.2.3 以及之前的版本可以在任何地方设置开启,即配置文件和代码中,之后的版本可以在 php.ini、httpd.conf 以及.htaccess 中开启。
4.1.2.2 过滤函数和类
过滤函数和类有两种使用场景,一种是程序入口统一过滤,像框架程序用这种方式比较多,另外一种是在程序进行 SQL 语句运行之前使用,除了 PHP 内置的一些过滤单引号等函数外,还有一些开源类过滤 union、select 等关键字。
1.addslashes 函数
addslashes 函数过滤的值范围和 GPC 是一样的,即单引号(')、双引号(")、反斜杠(\)及空字符 NULL,它只是一个简单的检查参数的函数,大多数程序使用它是在程序的入口,进行判断如果没有开启 GPC,则使用它对$_POST/$_GET 等变量进行过滤,不过它的参数必须是 string 类型,所以曾经某些程序使用这种方式对输入进行过滤时出现了绕过,比如只遍历$_GET 的值,当时并没有考虑到$_GET 的值也是一个数组。我们来看一个例子如下:
<?php $str?=?"phpsafe ’ " ; echo?addslashes ( $str );? >
上面的例子输出:phpsafe\'。
2.mysql_[real_]escape_string 函数
mysql_escape_string 和 mysql_real_escape_string 函数都是对字符串进行过滤,在 PHP4.0.3 以上版本才存在,如下字符受影响【\x00】【\n】【\r】【\】【'】【"】【\x1a】,两个函数唯一不一样的地方在于 mysql_real_escape_string 接受的是一个连接句柄并根据当前字符集转义字符串,所以推荐使用 mysql_real_escape_string。
使用举例:
<?php $con = mysql_connect ( "localhost" , "root" , "123456" ); $id = mysql_real_escape_string ( $_GET['id'] , $con ); $sql="select * from test where id='".$id."'" ; echo $sql ;
当请求该文件?id=1’时,上面代码输出:select*from test where id='1\''
3.intval 等字符转换
上面我们提到的过滤方式,在 int 类型注入时效果并不好,比如可以通过报错或者盲注等方式来绕过,这时候 intval 等函数就起作用了,intval 的作用是将变量转换成 int 类型,这里举例 intval 是要表达一种方式,一种利用参数类型白名单的方式来防止漏洞,对应的还有很多如 floatval 等。
应用举例如下:
<?php $id=intval ( "1 union select " ); echo $id ;
以上代码输出:1
4.1.2.3 PDO prepare 预编译
如果之前了解过.NET 的 SqlParameter 或者 java 里面的 prepareStatement,那么就很容易能够理解 PHP pdo 的 prepare,它们三个的作用是一样的,都是通过预编译的方式来处理数据库查询。
我们先来看一段代码:
<?php dbh = new PDO ( "mysql : host=localhost ; dbname=demo" , "user" , "pass" ); $dbh->exec ( "set names 'gbk'" ); $sql="select * from test where name =?and password =?" ; $stmt = $dbh->prepare ( $sql ); $exeres = $stmt->execute ( array ( $name , $pass ));
上面这段代码虽然使用了 pdo 的 prepare 方式来处理 sql 查询,但是当 PHP 版本<5.3.6 之前还是存在宽字节 SQL 注入漏洞,原因在于这样的查询方式是使用了 PHP 本地模拟 prepare,再把完整的 SQL 语句发送给 MySQL 服务器,并且有使用 set names'gbk'语句,所以会有 PHP 和 MySQL 编码不一致的原因导致 SQL 注入,正确的写法应该是使用 ATTR_EMULATE_PREPARES 来禁用 PHP 本地模拟 prepare,代码如下:
<?php dbh = new PDO ( "mysql : host=localhost ; dbname=demo" , "user" , "pass" ); $dbh->setAttribute ( PDO :: ATTR_EMULATE_PREPARES , false ); $dbh->exec ( "set names 'utf8'" ); $sql="select * from test where name =?and password =?" ; $stmt = $dbh->prepare ( $sql ); $exeres = $stmt->execute ( array ( $name , $pass ));
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论