为什么预编译可以防止sql注入,sql预编译防sql注入

  为什么预编译可以防止sql注入,sql预编译防sql注入

  写在前面

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

  然后绑定集合中的参数,如下图所示。

  最后使用execute stmt _ name的语法[使用@ var _ name [,@var_name]。]选择编译好的stmt_test模板,如下图所示。

  查看mysql日志,可以看到这里使用了prepare和execute命令,与执行常规sql语句时使用的query命令不同。请参考下图。

  使用同一模板的不同参数值(不同名称值)执行查询时,如下图所示:

  在此检查名称值为“othername”的列。因为这里还在使用prepare的stmt_test模板,所以程序会使用缓冲区中预存的编译好的模板进行分析,而不会再次使用prepare。请参考下图。

  从上图可以看出,预编译可以实现一次编译多次执行,可以省去分析优化的过程。

  实际中,客户端与mysql数据库通信时,会发送一条命令请求消息,指明当前请求消息的类型,如下图所示。

  通常,简单地执行sql语句会导致数据包使用类型值为0x03的COM_QUERY消息。请参考下图。

  使用预编译函数时,用类型值为0x16的COM_STMT_PREPARE预编译,用0x17执行,如下图所示。

  图中的22对应于十六进制0x 16 COM _ STMT _准备阶段。

  23对应于十六进制的0x 17 COM _ STMT _执行阶段。

  模拟预编译

  对一些不支持预编译的数据库设置模拟预编译,比如sqllite和mysql的早期版本。如果启用模拟预处理,客户端程序将在mysql数据库中模拟一个名为参数绑定的过程。这意味着程序会在内部模拟prepare过程,在执行execute时将拼接完成的SQL语句发送到mysql数据库执行。

  这些情况包括:这里,PDO接口用于数据库操作。

  如上面的代码所示,sql模板用prepare预编译,参数在bindParam中绑定,最后在execute中执行。这是真正的sql预编译吗?

  让我们来看看mysql日志的事实记录。这将是:

  您可以看到数据库日志中没有准备和执行阶段。相反,一个简单的查询执行从PDO传来的sql语句,就像普通的sql查询一样。

  为什么会这样?

  如上所述,PDO默认使用伪预编译而不是mysql数据库的预处理(本地预处理),因为有些数据库(如sqllite和mysql的早期版本)已经设置为不支持预编译。如果模拟预处理已打开,则它是一个客户端进程。

  该序列将在内部模拟mysql数据库中参数绑定的过程。也就是说,程序会在内部模拟prepare的过程,在执行execute方法时,将拼接完成的SQL语句发送到mysql数据库进行查询。

  在PDO中,参数PDO:ATTR _仿真_准备控制使用的预编译模式,默认情况下使用模拟预处理。详见下图官网给出的描述:

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

  接口的默认用法

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

  PHP-PDO

  从数据库日志中可以看出,数据库通过query命令执行了一个简单的sql语句。显然,PDO默认使用模拟预处理。

  我们将PDO: attr _ emulate _ preparations设置为false,如下图所示。

  从日志中可以看出,显然有两个过程:准备和执行。显然,PDO: attr _ simulate _ prepare设置为false后,mysql数据库是预编译的。

  PHP-Mysqli

  从上图可以看出,很明显这是一个sql数据库的预编译过程。这说明mysqli不同于PDO,

  Mysqli默认使用sql数据库预编译,而不是模拟预编译。

  Python-MySQLdb

  从数据库日志中可以看出,数据库日志中只有query命令,MySQLdb默认使用模拟预处理。

  Python-Pymysql

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

  Python-Oursql

  从日志中可以看出有一个Prepare进程,这是一个sql预编译进程,Oursql使用sql数据库预处理。

  然而奇怪的是,日志中只有准备过程,但程序也可以查询数据。让我们检查一下流量,如下图所示。

  从上图可以看出,这里其实有一个execute进程,但是为什么数据库日志中没有Execute的记录?

  首先,让我们看看这个执行包,如下图所示

  在上图的红框中,Flags是只读光标。

  通常,这里的值是默认值。根据作者的猜测,数据库中没有这个执行日志可能和这个领域有关,有兴趣的同学可以自己研究一下。

  数据库预编译和转义

  查看数据库日志时可以发现,mysql数据库预编译时,传入参数的特殊字符会在执行阶段进行转义。见下面的红框。

  这个转义是在对应的客户端执行的?

  还是mysql数据库收到参数后自己转义?

  通过捕捉流量,见下图。

  客户端传递的参数不会被转义。因此,转义操作是在mysql数据库上执行的。

  预编译能完全消除注入攻击吗?

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

  很久以前的ThinkPHP5

  有一个SQL注入的弱点。简单来说,这个漏洞就是在预编译阶段,也就是准备阶段,sql语句的模板中的可控参数名导致的sql注入。详见本文。

  这次不是通过参数注入有效载荷,而是在生成sql模板时,在参数名处拼接有效载荷,在准备阶段进行注入攻击。虽然可以在准备阶段注入有效载荷,但是这样的sql模板会导致mysql数据库出错,所以在执行阶段无法成功执行。

  然而,在准备阶段,仍然可以执行部分有效负载,比如下面的演示。

  最后,sql注入攻击仍然可以通过报错来进行。

  由此可见,如果准备阶段的sql模板是可控的,那么仍然存在注入的风险。

  参考链接

为什么预编译可以防止sql注入,sql预编译防sql注入