注入预防 预编译_Sql预编译与模拟预编译研究

更多全球网络安全资讯尽在邑安全

www.eansec.com

写在前面

众所周知,预编译是解决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语句模板,如下图:

02b4f1e8babd46da484aaaa3c3ce111e.png

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

b9da7948d8061353aa22a58405c06669.png

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

556a0f0d2bc662b27f2139032998b688.png

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

70fbd8d2ab66c58180020f24a2c36db0.png

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

28b2ca11c49d4b51b6f82e00e9f99571.png

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

f86daa77cf072119b0142ae061012909.png

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

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

000457952ae093089fc63ead27e0bd8f.png

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

e9ab3f39ae6a0c22c8f56e22dbec0578.png

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

e215a554a7042c108b279405d0523016.png

6d69c94779c807b46b847a0493369986.png

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

1be5fc9fed176150fa87fd06d194906f.png

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

模拟预编译

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

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

b099b1ff3ddad487a5930624b29626c8.png

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

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

5398876a5ef249e51416820b3f331118.png

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

这是为什么呢?

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

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

759ee93d6ccb25abcd64904ef276593a.png

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

接口默认使用方式

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

PHP-PDO

5e6d108409c3c2044ea1ded5a148749c.png

16219d80a4fbc37290c01c122b89dc3b.png

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

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

ef2ac9bb3a973bea4d2f89981aaee438.png

55c252db9544f77480d30ab33f1bff02.png

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

PHP-Mysqli

1080ef020848eacfec896cb200550df4.png

18918731aa5211cdbb7c1224f1822c3f.png

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

Python-MySQLdb

736736187f8e29228b706f2692e40dc5.png

d4379ae274a4b21342e9411ad96db95f.png

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

Python-Pymysql

2addb654304d8cdbdeb521b0e170329f.png

7548ed48d39a09e5e598639fc325f279.png

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

Python-Oursql

948522beeab45a38a88e73bdc1af5040.png

74b6024b9d4b30ea15d1cca7337c2ee1.png

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

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

e18d5c79c8a2a5ca3e993aacfd75cb37.png

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

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

2315d4607b8e10ebe35bdfd1ed87c22d.png

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

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

Sql数据库预编译与转义

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

4f87647dff3558f9bdc8ba3ff849b50b.png

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

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

472c77112be44d901a9d7c062f229fa0.png

客户端传递的参数,并没有进行转义处理。因此可知,转义操作是在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

e6216ac3e3697e9e8c6f37f482a677d2.png

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

890128cf17c00dbe22ea58ea8d123514.png

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

转自先知社区

欢迎收藏并分享朋友圈,让五邑人网络更安全

cd408fbcd20ed768fc89cd641baa4bfd.png

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!

推荐文章

1

Django入门之旅-启程

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值