PHP安全之使用PDO防SQL注入

汉王 PHP 2018年06月21日 收藏
PDO是PHP的一个扩展,使用PDO扩展可以连接不同类型的数据库系统,但是我们还是需要自己编写SQL语句,这就意味着SQL安全由开发人员掌控。传统的mysql_connect 、mysql_query方法存在很多注入风险,而使用PDO预处理机制可以有效的防止SQL注入风险。

连接数据库

现在我们需要连接到一个名为testdb的MySQL数据库,这个数据库的IP地址是127.0.0.1,监听端口默认3306,数据库的用户名是yueguang,密码是hii12356,连接使用字符集是utf8。以下是连接代码:
<?php 
$dsn = 'mysql:dbname=testdb;host=127.0.0.1;port=3306;charset=utf8'; 
$user = 'yueguang'; 
$password = 'hii12356'; 
try { 
    $pdo = new PDO($dsn, $user, $password); 
} catch (PDOException $e) { 
    echo 'Connection failed: ' . $e->getMessage(); 
}
PDO类构造方法的第一个参数是DSN。这个DSN以Mysql:开头,因此PDO会使用PDO扩展中的MySQL驱动器链接MySQL数据库。在:符号之后,我们指定了使用分号隔开的键值对,设置host,dbname,prot和charset。如果数据库连接失败,PDO会抛出异常。

以上代码如果参数都正确的话,就可以正常连接MySQL数据库了,但是从安全的角度上讲,这么做并不安全,我们不能将数据库连接凭据编写在可以公开访问的PHP文件中,因为这样的话如果服务器某些配置出错或者其他原因,HTTP客户端可以看到PHP代码,那么数据库凭证就暴露了,所有人都能看到。所以我们要做的是把数据库凭证配置在文档根目录之外的配置文件中,使用时导入这个配置文件即可。如:

database.php文件在文档根目录之外:
<?php 
$config = [ 
    'host' => '127.0.0.1', 
    'port' => '3306', 
    'dbname' => 'testdb', 
    'dbuser' => 'yueguang', 
    'dbpass' => 'hii12356', 
    'charset' => 'utf8' 
];
然后我们在index.php文件中导入database.php文件即可建立一个PDO数据库连接。
<?php 
$pdo = new PDO( 
    sprintf( 
        'mysql:dbname=%s;host=%s;port=%s;charset=%s', 
        $config['dbname'], 
        $config['host'], 
        $config['port'], 
        $config['charset'] 
    ), 
    $config['dbuser'], 
    $config['dbpass'] 
);
这样做就更安全了,如果index.php文件泄露了,数据库凭证database.php仍然安全。

预处理语句

现在我们已经建立了一个数据库的PDO连接,通过这个连接可以操作数据库了,如读取、写入、更新和删除操作。我们经常会构建如下的sql语句:
$email = filter_input(INPUT_GET, 'email'); 
$sql = "SELECT id FROM user WHERE email='".$email."'";
这样做并不好,因为SQL语句中使用了HTTP请求查询字符串中的原始输入数据,我们在前面《PHP安全之数据过滤和验证》一文中强调“永远不要信任外部输入”。因为这么做等于为黑客敞开大门,让他们对你的PHP应用做坏事。幸运的是PDO扩展通过预处理语句和参数绑定把过滤输入这项操作变得特别简单。

我们来把上面的SQL语句再变更下:
$sql = "SELECT id FROM user WHERE email=:email"; 
$stmt = $pdo->prepare($sql); 
 
$email = filter_input(INPUT_GET, 'email'); 
$stmt->bindValue(':email', $email);
上述代码中,:email是具名占位符,可以安全的绑定任意值。预处理语句会自动过滤$email的值,防止数据库收到SQL注入攻击。一个sql语句字符串中可以绑定多个具名占位符,然后在预处理语句中通过bindValue()方法绑定各个占位符的值。

关于使用PDO查询数据库,以及对数据库的CURD操作请查阅PHP手册。