php mysql 注入漏洞_PHP安全:SQL注入漏洞防护

本文介绍了如何在PHP中防范SQL注入,强调了预编译处理在MySQL中的重要性,提供PDO和MySQLi的预编译处理示例,并讨论了数据校验、过滤以及宽字节注入防护策略,以提升PHP应用的安全性。
摘要由CSDN通过智能技术生成

原标题:PHP安全:SQL注入漏洞防护

SQL注入是最危险的漏洞之一,但也是最好防护的漏洞之一。本文介绍在PHP的编码中合理地使用MySQL提供的预编译进行SQL注入防护,在PHP中使用PHP数据对象扩展或MySQLi扩展连接数据库,并且对SQL语句进行预编译处理。

如果在一些项目中无法使用预编译来防止SQL注入,可以采用传统方法来验证用户的输入是否合法,严格控制输入参数的数据类型,过滤非法字符,拦截带有SQL语法的参数传入应用程序,在一定程度上提高恶意攻击者的攻击成本,但是往往容易被绕过。

1、MySQL预编译处理

一个完整的MySQL预编译处理分为编译、执行、释放三步,预编译遵循指令和数据分离的原则,可以有效地防止SQL注入的发生。

首先是编译,通过PREPARE stmt_name FROM preparable_stm来预编译一条SQL语句。

mysql>prepare test from 'insert into hacker select ?,?,?,?';

Query OK, 0 rows affected(0.00 sec)

statement prepared

通过EXECUTE stmt_name [USING @var_name [,@var_name]…]的语法来执行预编译语句。

mysql> set @name='hacker',@email='hello@ptpress.com.cn',@password='asdfghjkl',@status=1;

Query OK, 0 rows affected(0.00 sec)

mysql> execute test using @name,@email,@password,@status;

Query OK, 1 rows affected(0.01 sec)

Records:1 Duplicates: 0 Warnings: 0

mysql> select * from hacker;

+------+------+------+------+------+

|id|name|email|password|status

+------+------+------+------+------+

|1|hacker|hello@ptpress.com.cn|asdfghjkl|1

+------+------+------+------+------+

1 row in set(0.00 sec)

可以看到,数据已经被成功地插入表中。

MySQL中的预编译语句作用域是会话级,但可以通过max_prepared_stmt_count变量来控制全局最大存储的预编译语句。

mysql> set @global.max_perpared_stmt_count=1;

Query OK, 0 rows affected(0.00 sec)

mysql> perpare selecttest from 'select * from t';

ERROR 1461(42000): Can't create more than max_prepared_stmt_count statements(current value: 1)

当预编译条数达到阈值时,可以看到MySQL会报出如上所示的错误。

如果要释放一条预编译语句,则可以使用{DEALLOCATE | DROP} PREPARE stmt_name的语法进行操作。

mysql> deallocate prepare test;

Query OK, 0 rows affected(0.00 sec)

使用Wireshark抓包工具可捕获到MySQL预编译的执行过程,如图1所示。

94a883c7d8917ac69ec7026e4330b901.png

图1 MySQL抓包

从捕获到的流量中可以看到,每次SQL执行会分两次进行。第一次先将需要编译的SQL语句发送给数据库进行编译,数据部分用占位符代替。第二次将用户数据提交给数据库执行。SQL语句不会再次进行编译,即使用户数据中包含SQL字符也会被当成数据处理,不会改变原语句的结构。

2、PHP使用MySQL的预编译处理

SQL之所以能被注入,主要原因在于它的数据和代码指令是混合的。使用数据库预编译方式进行数据库查询,不仅可以增强系统安全性,而且可以提高系统的执行效率。当一个SQL语句需要执行多次时,使用预编译语句可以减少处理时间,提高执行效率。在PHP系统中可以通过PDO模块或MySQLi模块进行SQL预编译处理,下面依次举例说明使用方式。

(1)PDO的预编译处理举例

$dns='mysql:dbname=safe;host=127.0.0.1';

$user='root';

try {

$pdo=new PDO($dns,$user,$password);

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);

$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);

}

catch(PDOException $e)

{

echo $e->getMessage;

}

$pdo->query("set names utf8");

$sql='insert into hacker(name,email) values(:name,:email)';

// 编译SQL

$pdo_stmt=$pdo->prepare($sql);

$name="hacker attack";

$email="safe@ptpress.com.cn";

// 绑定参数

$pdo_stmt->bindParam(':name',$name);

$pdo_stmt->bindParam(':email',$email);

$pdo_stmt->execute;

if($pdo_stmt->errorCode==0)

{

echo "数据插入成功";

}

else

{

print_r($pdo_stmt->errorInfo);

}

在默认情况下,使用PDO并没有让MySQL数据库执行真正的预处理语句。为了解决这个问题,应该禁止PDO模拟预处理语句,添加PDO::ATTR_EMULATE_PREPARES、PDO::ATTR_ERRMODE属性。

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);

$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);

(2)MySQLi的预编译处理举例

$mysqli=new mysqli("localhost","root","","safe");

if(mysqli_connect_errno)

{

printf("Connect failed: %sn",mysqli_connect_error);

exit;

}

$mysqli->query("set names utf8");

$sql='insert into author(name,email)values(?,?)';

$mysqli_stmt=$mysqli->prepare($sql);

$mysqli_stmt->bind_param('ss',$name,$email);

$name="hacker attack";

$email="safe@ptpress.com.cn";

$res=$mysqli_stmt->execute;

if(!$res)

{

echo '错误:'.$mysqli_stmt->error;

}

else

{

echo "数据插入成功";

}

$mysqli_stmt->close;

$mysqli->close;

由于预处理是先提交SQL语句到MySQL服务端,执行预编译,客户端需要执行SQL语句时只需上传输入参数,分离了参数与SQL语句,因此不会导致恶意参数的执行,从根本上保障了数据库的安全。

3、校验和过滤

为了有效防止SQL注入,应尽量使用MySQL的预编译处理,不要使用动态拼装SQL。如果既有的系统中已经存在一些历史代码动态拼装SQL的情况,并且业务逻辑复杂,不能及时地更改为预编译处理形式,或者存在PHP版本较低、数据库版本比较老的情况,不支持预编译处理,为了防止前文提到的普通注入、隐式类型注入、盲注、二次解码注入,需要对输入的数据进行有效的校验和过滤。

通常使用的校验方式是判断传入的数据类型是否合法,如果不是所需要的要及时中断程序,防止继续执行。下面的示例中对传入的数据类型进行判定。

$id=$_GET['id'];

if(empty($id))

{

die('参数不能为空,请重新输入!');

}

if(gettype($id)!='integer')

{

die('非法的数据类型,请重新输入!');

}

if($id<=0)

{

die('输入的数据超出范围内,请重新输入!');

}

表1所列是一些常用的校验变量函数,这些函数通常用于校验用户传入的参数。

4bffb239f271957ba229e58a1e40c1ff.png

表1 常用的校验变量函数

除了上面的函数以外,也可以使用正则过滤SQL语句中的非法字符防止发生部分SQL注入方式,下面是代码示例。

function removeSpecialChar($param)

{

$regex="//|~|!|@|#|$|%|^|&|*|(|)|_/+|{|}|:||?|[|]|,|.|/|;|'|'|-|=|||/";

return preg_replace($regex,"",$param);

}

$name="name' OR 'a'='a'";

$name=removeSpecialChar($name);

?>

同时还可以检查参数中是否包含SQL关键字,下面是示例代码。

eregi('select|insert|update|delete|drop|truncte|'|/*|*|../|./|union|into|load_file|outfile|union',$name);

?>

这些过滤方式都需要在特定的业务场景下使用,使用不当可能会影响到现有业务。要从根本上杜绝SQL注入漏洞,建议使用SQL预编译处理进行系统研发。

4、宽字节注入防护

要防止这类整型的宽字节注入,可以在进行SQL查询前使用intval对变量进行强制转换。

可以使用mysql_real_escape_string进行防御,在使用前需要mysql_set_charset指定当前所使用的字符集格式才能生效。

header("Content-Type: text/html;charset=UTF-8");

$conn=mysql_connect('localhost','root','') or die('数据库连接失败');

mysql_query("SET NAMES 'gbk'"); // GBK编码

mysql_select_db('safe',$conn);

mysql_set_charset('gbk',$conn);

$id=isset($_GET['id']) ? mysql_real_escape_string($_GET['id']) :1;

$sql="SELECT * FROM hacker WHERE id='{$id}'";

$result=mysql_query($sql,$conn) or die(mysql_error); // SQL出错会报错,方便观察

$row=mysql_fetch_array($result,MYSQL_ASSOC);

print_r($row);

mysql_free_result($result);

?>

还有一种方式就是将character_set_client设置为binary,在执行SQL前先执行以下代码。

mysql_query("SET character_set_connection=gbk, character_set_results= gbk,character_set_client=binary", $conn);

将character_set_client设置成二进制格式,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效地避免宽字符注入。

5、禁用魔术引号

PHP中的魔术引号选项magic_quotes_gpc推荐关闭,它并不能有效地防止SQL注入,已知已经有若干种方法可以绕过它,甚至由于它的存在反而衍生出一些新的安全问题。XSS、SQL注入等漏洞,都应该由应用在正确的方法中解决,同时关闭魔术引号还能提高性能。

magic_quotes_gpc=Off ; 关闭魔术引号选项返回搜狐,查看更多

责任编辑:

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值