Sql预编译与模拟预编译研究

2020-02-01 约 110 字 预计阅读 1 分钟

声明:本文 【Sql预编译与模拟预编译研究】 由作者 熊本熊本熊 于 2020-02-01 15:51:50 首发 先知社区 曾经 浏览数 39 次

感谢 熊本熊本熊 的辛苦付出!

写在前面

众所周知,预编译是解决sql注入的一个很好的方案,但是预编译在现实使用中却有着很多有趣的细节需要研究下。在没有经过实验之前,针对如下问题我也比较模糊,例如:

1、Mysql预编译和模拟预编译有什么不同?哪种方式理论上更加安全呢?

2、PHP中链接数据库Mysqli接口与PDO接口默认采用哪种方式进行预编译?

3、Python中MySQLdb又是默认采用哪种方式进行预编译?

4、程序采用Mysql数据库预编译方式,转义环节是有客户端(PHP、Python、Java)完成的,还是由服务器端(Mysql数据库)完成?

本文将对上述这些问题进行分析

Mysql预编译和模拟预编译

首先介绍下sql预编译和模拟预编译的区别之

sql预编译

以mysql数据库举例:通常情况下,在数据库接收到一条普通的
SQL语句后,首先对其进行语义解析,随后对此条SQL
语句进行优化并制定执行计划并执行;当采用预编译操作时,首先将待执行的SQL
语句中的参数值用占位符替代。当带着占位符的SQL
语句模板被数据库编译、解析后,再通过向占位符绑定参数进行查询操作。

反观Sql注入的根源,是在本应该传递参数数据的地方,
传入了精心构造的sql语句。而经过预编译操作之后,无论后续向模板传入什么参数,这些参数仅仅被当成字符串进行查询处理,因此杜绝了sql注入的产生

接下来看一下预编译在mysql数据库中如何操作

首先,我们可以通过 PREPARE stmt_name FROM preparable_stm
语法来预编译一条sql语句模板,如下图:

接着通过set来绑定参数 ,如下图:

最后通过EXECUTE stmt_name [USING @var_name [, @var_name]...]的语法来选择编译好的stmt_test模板以接收name参数并执行查询,如下图:

通过查看mysql日志可以发现,与执行普通sql语句使用的query命令不同,这里使用了prepare命令与execute命令,见下图

当后续使用同一模板不同参数值(不同的name值)进行查询进行查询时,例如下图:

这里查询name值为”othername”的列,由于这里使用的仍是经过prepaer的stmt_test模板,程序将使用先前存储于缓冲区预编译后的模板进行解析,而不需要再次通过prepare,见下图

上图中可见,预编译可以实现一次编译、多次执行,省去了解析优化等过程。

在实际操作中,当客户端在与mysql数据库通信时,为了表明当前请求消息的类型,会发送命令请求报文,报文格式如下图所示:

通常情况下,如果简单的执行sql语句,数据包中会使用类型值为0x03的COM_QUERY消息报文,见下图

而在使用预编译功能时,则会使用类型值为0x16的COM_STMT_PREPARE进行预编译并使用0x17进行执行,见下图

上图中22对应十六进制的0x16 COM_STMT_PREPARE阶段

上图报文中23对应十六进制的0x17 COM_STMT_EXECUTE阶段

模拟预编译

模拟预编译是防止某些数据库不支持预编译而设置的(如sqllite与低版本mysql)。如果模拟预处理开启,那么客户端程序内部会模拟mysql数据库中的参数绑定这一过程。也就是说,程序会在内部模拟prepare的过程,当执行execute时,再将拼接后的完整SQL语句发送给mysql数据库执行

有如下案例,这里使用PDO接口进行数据库操作

从上图代码中可见,使用prepare预编译sql模板,并通过bindParam进行参数绑定,最终通过execute进行执行,但这是否是真正的sql预编译呢?

我们可以看下mysql日志事实记录,如下

可以看到数据库日志中并没有prepare阶段与execute阶段。反而和执行普通的sql查询一样,简简单单的Query了PDO传递过来的sql语句

这是为什么呢?

正如上文所说:为了防止某些数据库不支持预编译而设置的(如sqllite与低版本mysql),PDO默认使用的是模拟预编译而非mysql数据库预处理(本地预处理)。如果模拟预处理开启,那么客户端程序内部会模拟mysql数据库中的参数绑定这一过程。也就是说,程序会在内部模拟prepare这一过程,当execute方法执行时,再将拼接后的完整SQL语句发送给mysql数据库进行查询

PDO中通过PDO::ATTR_EMULATE_PREPARES参数控制所使用的的预编译模式,默认使用模拟预处理进行操作。详细的可见下图官网给出的说明:

模拟预处理并没有实现SQL模板与参数的分离,但的确可以防止sql注入。根据笔者查阅的资料显示:模拟预处理防止sql注入的本质是在参数绑定过程中对参数值进行转义与过滤,这一点与真正的sql数据库预处理是不一样的。理论上,sql数据库预编译更加安全一些。

接口默认使用方式

在介绍完模拟预编译与sql数据库预编译后,我们来看看哪些接口默认使用模拟预编译,而哪些接口不使用

PHP-PDO

从数据库日志中可见,数据库通过query命令执行了一条简单的sql语句。很显然,默认情况下PDO使用模拟预处理

我们将设置PDO::ATTR_EMULATE_PREPARES为false,见下图

从日志中可见,这里明显有prepare和execute两个过程,显然在将PDO::ATTR_EMULATE_PREPARES设置为false后,使用的是mysql数据库预编译

PHP-Mysqli

从上图可见:很显然这是一个sql数据库预编译过程。这说明mysqli与PDO不同,
mysqli默认使用的是sql数据库预编译而非模拟预编译

Python-MySQLdb

从数据库日志中可见,数据库日志中只有query命令,MySQLdb默认情况下使用模拟预处理

Python-Pymysql

同样,Pymysql也默认使用模拟预处理

Python-Oursql

从日志可见:存在Prepare过程,这是一个sql预编译过程,Oursql使用的是sql数据库预处理。

然而奇怪的是,日志中只有Prepare过程,但是程序以及可以查询到数据。我们查看下流量,见下图

从上图可见,这里其实是有Execute过程的,但为什么数据库日志中不存在execute这条记录呢?

首先我们来看下这条Execute数据包,如下图

可见上图红框处,Flags为Read-only cursor

通常情况下,这里值为Defaults。据笔者猜测,数据库中没有这条执行日志可能与这个字段有关,感兴趣的同学可以自己研究下。

Sql数据库预编译与转义

在查看数据库日志时可以发现:使用mysql数据库预编译时,在execute阶段,传入参数的特殊字符会被进行转义处理。见下图红框处

这个转义,是在对应的客户端中进行的?
还是在mysql数据库接收到参数后,自行进行转义的?

通过抓取流量可知,见下图

客户端传递的参数,并没有进行转义处理。因此可知,转义操作是在mysql数据库上进行的

预编译可以完全杜绝注入攻击吗

使用sql数据库预编译,理论上可以杜绝sql注入攻击,但是也会有例外。

很久之前ThinkPHP5
曾有一个SQL注入漏洞。该漏洞简单来说,就是在预编译阶段即prepare阶段,sql语句的模板中参数名可控,导致的sql注入。具体的可以参见这篇文章

https://www.leavesongs.com/penetration/thinkphp5-in-sqlinjection.html

这次并不是通过参数注入payload,而是在sql模板生成时在参数名处拼接payload,在prepare阶段进行注入攻击。虽然在prepare阶段可以注入payload,但是这样的sql模板会引起mysql数据库的报错从而无法顺利执行到execute阶段。

然而在prepare阶段,仍然是可以执行部分payload的,例如下图demo

最终仍然可以通过报错进行sql注入攻击

可见:prepare阶段的sql模板如果可控,仍然是有注入风险的

参考链接

https://blog.csdn.net/yanghuan313/article/details/70477360

https://www.leavesongs.com/penetration/thinkphp5-in-sqlinjection.html

关键词:[‘安全技术’, ‘WEB安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now