0x00简介

PDO(PHP Data Object)扩展类库为php访问数据库定义的轻量级的、一致性的接口,它提供了一个数据库访问抽象层。它可以通过一致的函数执行查询和获取数据,大大简化了数据库操作,并能够屏蔽不同数据库之间的差异。同时可以很好的预防SQL注入。

0x01

前期准备

PHP5.4以上版本默认存在PDO扩展。

MYSQL数据库

1
2
3
4
5
6
7
CREATE TABLE IF NOT EXISTS `user`(
`id` INT UNSIGNED AUTO_INCREMENT,
`username` VARCHAR(20) NOT NULL,
`password` VARCHAR(32) NOT NULL,
`email` VARCHAR(40) NOT NULL,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入一条数据

1
INSERT INTO user(id,username,password,email) VALUE(1,'user1',md5(123456),'user1@shawn.com');

HTML前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta charset="utf-8" />
<title>Test</title>
</head>
<body>
<div align="center">
<form action="1.php" methon="get">
用户名<input type="text" name="username" >
<br />
<br />
密码<input type="password" name="password" >
<br />
<br />
email<input type="text" name="email" >
<br />
<br />
<input type="submit" value="提交" name="submit" >
</form>
</div>
</body>
</html>

创建PDO对象

以下以mysql为例

数据库配置文件database.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
//连接Mysql数据库的驱动
$dsn = 'mysql:dbname=数据库名;host=localhost';
$usr = 'root'; //数据库登陆名
$passwd = 'root'; //登陆密码

try{
//实例化PDO对象
$pdo = new PDO($dsn,$usr,$passwd);
//设置错误处理模式,推荐为异常处理模式
$pdo -> setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
//设置是否关闭自动提交功能,0为关闭,1为打开
//$pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT,0);

//设置结果集返回的格式
//$pdo -> setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);

}catch(PDOException $e){
echo $e -> getMessage(); //返回错误信息
echo $e -> getFile(); //返回异常发生文件
echo $e -> getLine(); //返回异常发生的行
echo $e -> getCode(); //返回错误码
}

exec()函数与query()函数的区别

1
2
3
//均为执行函数
$pdo -> exec(); //执行有影响行数的语句(增删改)
$pdo -> query(); //执行有结果集的函数(查)

执行SQL语句

1.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
header('conotent-type:text/html;charset=utf-8');
include("database.php");

//接收数据
$usrname = trim($_GET['username']);
$pwd = md5(trim($_GET['password']));
$mail = trim($_GET['email']);

try{
//插入数据
$sql = "INSERT INTO user(username,password,email) VALUE('{$usrname}','{$pwd}','{$mail}')";
$affected = $pdo -> exec($sql);
var_dump($affected);
//查询数据
$sql1 = "SELECT * FROM user";
$stmt = $pdo -> query($sql1);
var_dump($stmt);
}catch(PDOException $e){
echo $e -> getMessage();
}

插入一条数据,user2,112233,user2@shawn com

1
2
3
##1.php
var_dump($affected); //影响行数(红色框)
var_dump($stmt); //执行的sql语句

查看数据库进行检查

0x02 PDO预处理

将SQL语句提前编译,等待用户传入参数。

数据插入数据库

2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
header('conotent-type:text/html;charset=utf-8');
include("database.php");

//接收数据
$usrname = trim($_GET['username']);
$pwd = md5(trim($_GET['password']));
$mail = trim($_GET['email']);

try{
//占位符方式预处理,问号是占位符
$sql = "INSERT INTO user(username,password,email) VALUE(?,?,?)";
//perpare()准备参数
$stmt = $pdo -> prepare($sql);
//绑定参数(与问号对应)
//bindParam绑定的参数
//第一个:第几个占位符
//第二个:要绑定的参数
//第三个:变量的数据类型(一般不写)
$stmt -> bindParam(1,$usernmame);
$stmt -> bindParam(2,$password);
$stmt -> bindParam(3,$email);

//将接收的数据赋值给绑定参数的变量
$username = $usrname;
$password = $pwd;
$email = $mail;

//执行预处理
$stmt -> execute();

//极简操作,不绑定参数
//$sql1 = "INSERT INTO user (username,password,email) VALUE(?,?,?)";
//$stmt1 = $pdo -> prepare($sql1);
//使用一个索引数组传入
//$stmt1 -> execute(array($usrname,$pwd,$mail));
}catch(PDOException $e){
echo $e -> getMessage();
}

插入一条user3的数据

造成SQL注入的原因是恶意的语句被拼接到了数据库,然而当对SQL语句进行了预编译后再对用户传入的参数进行数据库操作,可以很大程度上避免SQL注入问题。

3.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
header('conotent-type:text/html;charset=utf-8');
include("database.php");

try{
//别名方式
$sql = "INSERT INTO user (username,password,email) VALUE(:username,:pwd,:email)";
//perpare()准备参数
$stmt = $pdo -> prepare($sql);
//绑定别名参数,
$stmt -> bindParam(":username",$username);
$stmt -> bindParam(":pwd",$password);
$stmt -> bindParam(":email",$email);

//将接收的数据赋值给绑定参数的变量
$username = "admin";
$password = md5(123456);
$email = "123@shawn.com";

//执行预处理
$stmt -> execute();

//极简操作
//$sql2 = "INSERT INTO user (username,password,email) VALUE(:username,:pwd,:email)";
//perpare()准备参数
//$stmt2 = $pdo -> prepare($sql2);
//$pwd = md5(123456);
//使用关联数组
//$arr = array("username" => "admin123","pwd" => $pwd,"email" => "admin123@shawn.com");
//$stmt2 ->execute($arr);

}catch(PDOException $e){
echo $e -> getMessage();
}

数据查询操作

4.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
header('conotent-type:text/html;charset=utf-8');
include("database.php");

try{
$sql = "SELECT * FROM user WHERE id = :id";
$stmt = $pdo -> prepare($sql);
$stmt -> execute($_GET);

//提取一条数据使用的方法
//$users = $stmt -> fetch(PDO::FETCH_ASSOC);
//提取多条数据使用的方法
//$users = $stmt -> fetchALL(PDO::FETCH_ASSOC);
//获取增删改时或查询的结果集数
//$stmt -> rowCount();

//绑定数组
$stmt -> bindColumn(1,$id);
$stmt -> bindColumn(2,$username);
$stmt -> bindColumn("pwd",$pwd);
$stmt -> bindColumn("email",$email);

$stmt -> fetch(PDO::FETCH_ASSOC);
echo $id.'--'.$username.'--'.$pwd.'--'.$email;

}catch(PDOException $e){
echo $e -> getMessage();
}

当访问4.php?id=2时

0x03事务处理

mysql的事务处理

数据库

1
2
3
4
CREATE table cash(
`id` int not null AUTO_INCREMENT PRIMARY key,
`username` varchar(30) not null default '',
`money` int not null default 0);

插入数据

1
2
INSERT INTO cash(username,money) VALUE('Tom','1000');
INSERT INTO cash(username,money) VALUE('Dan','1000');

事务处理:

事务处理用于有效记录某机构感兴趣的业务活动(称为事务事务)的数据处理(例如销售)、供货的定购或货币传输)。通常,联机事务处 (OLTP) 系统执行大量的相对较小的事务。

模拟银行转账

mysql开启事务处理

1
2
3
start transaction;
或者
begin

从Tom的账户中转账50给Dan

1
2
--Tom账户余额减少50--
UPDATE cash SET money = money - 50 WHERE username='Tom';

此时,Tom的账户中会减少50,但是系统检测后Dan并没有收到50,进行回滚操作!

1
rollback;

此时Tom账户中恢复为1000,并提示转账失败。

转账成功示例:

1
2
3
4
5
6
7
8
--开启事务--
begin;
--Tom账户余额减少50--
UPDATE cash SET money = money - 50 WHERE username='Tom';
--Dan账户余额增加50--
UPDATE cash SET money = money + 50 WHERE username='Dan';
--结束事务--
commit;

PDO事务处理

cash.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
header('conotent-type:text/html;charset=utf-8');
include('database.php');

try{
//关闭自动提交事务
$pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT,0);
//开启事务
$pdo -> beginTransaction();

//一个子流程,则全部失败
$sql = "UPDATE cash SET money = money - 50 WHERE username = 'Tom'";
//影响行数
$affectedRow = $pdo -> exec($sql);
if(!$affectedRow){
throw new PDOException("转出失败");
}

$sql = "UPDATE cash SET money = money + 50 WHERE username = 'Dan'";
$affectedRow = $pdo -> exec($sql);
if(!$affectedRow){
throw new PDOException("转入失败");
}

//执行事务
$pdo -> commit();
echo "转账成功";
}catch(PDOException $e){
echo $e -> getMessage();
//失败回滚操作
$pdo -> rollback();
}
//开启自动提交
$pdo -> setAttribute(PDO::ATTR_AUTOCOMMIT,1);

0x04 总结

现在很多网站的防护还是比较依靠于安全狗,但是依然有风险被污染注射的风险。一些著名的框架例如Thinkphp等都在代码层面使用了PDO来预防SQL注入,但是依然还是被爆出过注入的漏洞,这些往往是没有对传入的参数没有进行严格的过滤进而导致注入问题。

PDO应对SQL注入还是相当的有成效的,对所有传入参数进行严格的过滤、可控,那么SQL注入问题也将不再是问题。

SQL注入漏洞现在越来越难挖掘,也一定说明了现在开发人员安全意识的提高以及代码编写的规范性。同时安全狗也是功不可没。