0x01 预编译以及原理
预编译是指在创建数据库对象时就将指定的SQL语句编译完成,这时SQL语句已经被解析、审查
,所以相对传统的执行方式(每处理一个SQL语句就要解析SQL语句、检查语法和语义),预编译方式在执行数据插入、更新或者删除操作的时候,执行效率更高。
为什么预编译能让传入的数据只能是数据,它的底层原理是怎样的?
通常来说,一条SQL语句从传入到运行经历了生成语法树、执行计划优化、执行
这几个阶段。在预编译过程中,数据库首先接收到带有预编译占位符?
的SQL语句,解析生成语法树(Lex)
,并缓存在cache
中,然后接收对应的参数信息,从cache
中取出语法树设置参数,然后再进行优化和执行。由于参数信息传入前语法树就已生成,执行的语法结构也就无法因参数而改变,自然也就杜绝了SQL注入的出现.
0x02 PDO
PDO针对预编译提供了两种模式:本地预编译和模拟预编译。
模拟预编译:本身不是一种真正的预编译,它是为了兼容不支持预编译的数据库,将用户输入的参数进行转义,拼接到数据库,由数据库正常解析执行
。所以它的‘预编译‘ 操作是由pdo 完成的 数据库本身没有参与预编译 并且这是PDO的默认模式
0x03 演示
1、开启MySQL日志,查看日志
<?php
$hostname ='127.0.0.1';
$dbname='ctf';
$conn = new PDO( "mysql:host=$hostname;dbname=$dbname",'root','root123456');
//$sql = 'select * from admin where id =\''.$id.'\'';
$sql ="select * from admin where id =?";
$pdos = $conn ->prepare($sql);
$pdos->bindParam(1,$_GET['id']);
$pdos->execute();
var_dump($pdos->fetchAll());
2021-11-28T05:29:59.198823Z 9 Connect root@localhost on ctf using TCP/IP
2021-11-28T05:29:59.199095Z 9 Query select * from admin where id ='\''
2021-11-28T05:29:59.199806Z 9 Quit
如日志所示,mysql本身没有预编译操作,只有连接
,查询
,关闭
三个操作。
2、开启本地预编译模式
<?php
$hostname ='127.0.0.1';
$dbname='ctf';
$conn = new PDO( "mysql:host=$hostname;dbname=$dbname",'root','root123456');
$conn -> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
//$sql = 'select * from admin where id =\''.$id.'\'';
$sql ="select * from admin where id =?";
$pdos = $conn ->prepare($sql);
$pdos->bindParam(1,$_GET['id']);
$pdos->execute();
var_dump($pdos->fetchAll());
2021-11-28T05:36:24.438190Z 10 Connect root@localhost on ctf using TCP/IP
2021-11-28T05:36:24.439940Z 10 Prepare select * from admin where id =?
2021-11-28T05:36:24.440013Z 10 Execute select * from admin where id ='\''
2021-11-28T05:36:24.440433Z 10 Close stmt
2021-11-28T05:36:24.440474Z 10 Quit
如日志所示,整个流程分为五部 连接
,预编译
,传入参数执行
,关闭预编译语句
,关闭数据库
0x04 危害
如果使用模拟预编译模式 ,则可能会导致出现宽字节注入漏洞
,二次注入漏洞
(之前文章写到)
0x05 总结
1、本次了解mysql的预编译处理能够使用户传入的数据只能是数据,而预编译是提前生成语法树 使其用户不能改变原有的语法结构,从而预防了SQL注入的产生。
2、了解了PHP的PDO两种预编译模式 本地预编译
,模拟预编译
后者只是由pdo代执行‘预编译操作‘ ,不能算真正意义上的预编译,就会出现宽字节注入
和二次注入
的风险。而PDO默认模式就是模拟预编译,所以再执行时,最好设置
$PDO -> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
3、同样的JAVA也存在类似的问题,以后学习后再补充。
0x06 参考
1、深入理解SQL注入与预编译(下)_牛客博客 (nowcoder.net)
Q.E.D.