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 危害

如果使用模拟预编译模式 ,则可能会导致出现宽字节注入漏洞,二次注入漏洞(之前文章写到)

1、sql二次注入 - wjlin0

2、sql宽字节注入 - wjlin0

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.