关于PDO的一些实践

关于PDO的一些实践

前言

关于pdo学习一直只是留在代码层面,很少去去抓包,对比,分析。借这个机会稍微对这个机制做一点更加深入的了解。

一些概念

预处理语句

什么是预处理语句?可以把它看作是想要运行的 SQL 的一种编译过的模板,它可以使用变量参数进行定制。

  • 查询仅需解析(或预处理)一次,但可以用相同或不同的参数执行多次。当查询准备好后,数据库将分析、编译和优化执行该查询的计划。对于复杂的查询,此过程要花费较长的时间,如果需要以不同参数多次重复相同的查询,那么该过程将大大降低应用程序的速度。通过使用预处理语句,可以避免重复分析/编译/优化周 期。简言之,预处理语句占用更少的资源,因而运行得更快。
  • 提供给预处理语句的参数不需要用引号括起来,驱动程序会自动处理。如果应用程序只使用预处理语句,可以确保不会发生SQL 注入。(然而,如果查询的其他部分是由未转义的输入来构建的,则仍存在 SQL 注入的风险)。
  • 预处理语句如此有用,以至于它们唯一的特性是在驱动程序不支持的时PDO 将模拟处理。这样可以确保不管数据库是否具有这样的功能,都可以确保应用程序可以用相同的数据访问模式。

pdo

感觉有的时候看英文原汁原味更加容易让人理解。

a PHP extension that can be used as a database abstraction layer

PDO is an acronym for PHP Data Objects. PDO is a lean, consistent way to access databases.

pdo就是php访问mysql的方式之一

关于为什么正确使用pdo可以防止sql注入,可以看看PDO防sql注入原理分析

实践

1.0 配置环境

这个地方确实踩了很多的坑。比如wireshark不能抓本地的mysql的流量包(但是好像还是有办法)。npacp不能喝winpacp共存,不然wireshark会报错找不到接口,然后就是mysql要开启远程链接。以及桥接虚拟机方面的问题。感觉真的有点坎坷。踩坑之王

我的环境是:

  1. wireshark
  2. win10 虚拟机架设mysql服务端
  3. 本地win10 客户端,对mysql进行查询。
  4. 各种版本的php

搭好环境之后就可以利用wireshark设置filter为mysql就可以看到包了。

wireshark

2.0 宽字节导致的PDO SQL注入

先上测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

$pdo = new PDO("mysql:host=192.168.21.43;dbname=test;charset=utf8", "root","root");

$st = $pdo->prepare("select * from user where username=? ");

$id = "Mrkaixin";

$st->bindParam(1, $id);

$st->execute();
$ret = $st->fetchAll();

print_r($ret);

这个是最基础的pdo的与处理了,其实这个就相当于mysql中的先prepare xxx from "select * from user where username=?" ,set @name="Mrkaixin",excute xxx usering @name

看一下发包的内容

本地预处理

这里发现其实是和我们自己构造sql语句然后发过去没有设么特别的。

但是如果你在绑定的变量中增加了一些特殊的字符比如:' \之类的就会被转义之后发过去。

这里的数据很明显发生了转义

这里其实给人的感觉就是pdo封装了一个php中的addcslashes

根据知识积累,当字符集设置为gbk的时候可以导致宽字节注入从而绕过这个addcslashes函数。这里也可以尝试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$pdo = new PDO("mysql:host=192.168.21.43;dbname=test;charset=utf8", "root","root");

$pdo->query('SET NAMES GBK');

$var = chr(0xbf) . chr(0x27) . " OR 1=1 #";

$query = "SELECT * FROM user WHERE username = ?";

$stmt = $pdo->prepare($query);

$stmt->execute(array($var));

抓包看一下结果

成功返回

从图中可以看出来成功利用宽字节绕过了,而且经过验证和php版本也是没有什么关系的。

这个的原因是由于未正确设置PDO造成的,在PDO的默认设置中,PDO::ATTR_EMULATE_PREPARES和PDO::MYSQL_ATTR_MULTI_STATEMENTS都是true,意味着模拟预编译和多句执行是默认开启的。

所以之后这里设置了PDO::ATTR_EMULATE_PREPARES => false。

这个选项涉及到PDO的“预处理”机制:因为不是所有数据库驱动都支持SQL预编译,所以PDO存在“模拟预处理机制”。如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行;如果我这里设置了PDO::ATTR_EMULATE_PREPARES => false,那么PDO不会模拟预处理,参数化绑定的整个过程都是和Mysql交互进行的。

如果我们关闭PDO::ATTR_EMULATE_PREPARES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

$pdo = new PDO("mysql:host=192.168.21.43;dbname=test;charset=utf8", "root", "root");

$pdo->setAttribute(pdo::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');

$var = chr(0xbf) . chr(0x27) . " OR 1=1 #";

$query = "SELECT * FROM user WHERE username = ?";

$stmt = $pdo->prepare($query);

$stmt->execute(array($var));

那么发包就变成了

包

prepare然后再执行。

它对每一句sql语句都进行了预编译和执行两个操作,在执行select balabala from table1 where 1=?这句时,如果是GBK编码,那么它将会把?绑定的参数转化成16进制,这样无论输入什么样的东西都无法再进行注入了。

3.0 多行匹配导致的SQL 注入

和本地模拟预编译一样,多行sql语句也是默认开启的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

$dbms='mysql';
$host='192.168.21.43';
$dbName='test';
$user='root';
$pass='root';
$dsn="$dbms:host=$host;dbname=$dbName";
try {
$pdo = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
echo $e;
}
$sql = "select * from user ;";
$sql .= "create table test2 like user";
$stmt = $pdo->query($sql);
while($row=$stmt->fetch(PDO::FETCH_ASSOC))
{
var_dump($row);
echo "<br>";
}

包

执行完之后,就会创建一个和user一样的test2

执行成功

如果吧PDO::MYSQL_ATTR_MULTI_STATEMENTS设为false,那么就可以组织多语句执行

多语句执行失败

但是如果使用setAttribute来设置的false也是可以多语句执行的。

多语句执行成功

所以还是要注意一下,一定是要在对象创建的时候设置才会生效。


其实还有一种直接拼接语句的,那样感觉根本没用到预处理。聊胜于无罢了。就不展开讨论了。

# 推荐文章

评论


:D 一言句子获取中...

加载中,最新评论有1分钟延迟...