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 $dsn = 'mysql:dbname=数据库名;host=localhost'; $usr = 'root'; $passwd = 'root';
try{ $pdo = new PDO($dsn,$usr,$passwd); $pdo -> setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
}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
| var_dump($affected); var_dump($stmt);
|
查看数据库进行检查
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(?,?,?)"; $stmt = $pdo -> prepare($sql); $stmt -> bindParam(1,$usernmame); $stmt -> bindParam(2,$password); $stmt -> bindParam(3,$email); $username = $usrname; $password = $pwd; $email = $mail; $stmt -> execute(); }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)"; $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(); }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); $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
| UPDATE cash SET money = money - 50 WHERE username='Tom';
|
此时,Tom的账户中会减少50,但是系统检测后Dan并没有收到50,进行回滚操作!
此时Tom账户中恢复为1000,并提示转账失败。
转账成功示例:
1 2 3 4 5 6 7 8
| begin;
UPDATE cash SET money = money - 50 WHERE username='Tom';
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注入漏洞现在越来越难挖掘,也一定说明了现在开发人员安全意识的提高以及代码编写的规范性。同时安全狗也是功不可没。