为什么SQL预编译可以防止SQL注入攻击

前言

防范SQL注入攻击是每一位做后端开发的程序员必须会的基本功。本文介绍其中一种防范攻击的方法:SQL预编译。

本文大部分内容引用自这篇文章,部分内容有修改。

注入例子

先简单回顾下SQL注入攻击的过程,假设有一个SQL语句:

SELECT * FROM users WHERE id = '{$p}';

{$p}是用户传递过来的查询参数,假设用户传递的参数是123,则SQL会是:

SELECT * FROM users WHERE id = '123';

但如果用户传递的参数是';DROP TABLE users;-- ,SQL就会变成:

SELECT * FROM users WHERE id = '';DROP TABLE users;-- ';

原本一个简单的SELECT语句,通过精心构造查询参数,就让它变成了一个删除users表的语句!

从注入的过程可以发现,如果用户传递过来的参数,我们不做任何处理就拼接到SQL语句中,很容易就会让黑客改变我们SQL语句的语法结构,引发严重事故。

解决方法

一、对用户参数进行转义

对一些有特殊意义的字符,例如单引号,进行转义,转义后再去数据库查询,相信很多人都知道这种做法,本文就不详细讲了。

二、SQL预编译

本文说的预编译,是指在数据库端进行的预编译。有部分代码库的预编译是在客户端本地进行的,这种是虚假的“预编译”。

通过SQL预编译,可以防止语法结构被改变。先了解下SQL的执行过程:

  1. 词法分析:将SQL语句分解成一个个token(关键字、标识符、运算符),然后对token进行分类和解析,生成相应的数据结构。
  2. 语法分析:根据SQL语法检测规则检查语法是否正确,并成成语法树。
  3. 语义分析:遍历语法树,确定表和列等信息,同时检查语义的正确性。
  4. 优化处理:使用优化器对SQL语句进行处理和优化,比如执行计划、索引等。
  5. 执行计划:使用执行计划生成器生成SQL语句的执行计划,比如数据的访问方式,索引的使用方式等。
  6. 引擎执行:将执行计划发送给相应的数据库引擎进行处理,执行计划被翻译成底层的操作指令,执行数据扫描、索引查找、排序、分组等操作。
  7. 返回数据:将执行结果返回给客户端,比如查询结果集或操作结果。

在这里,我们粗暴的把执行过程理解成两步,即:先编译SQL语法结构(1~3步),再执行SQL语句(4~7步)。

正常情况下,用户输入的参数会直接参与SQL语法的编译,而预编译则是先构建语法树,确定SQL语法结构以后,再拼接用户的参数。

2.1 预编译原理

预编译最初的目的是提高SQL语句的执行效率,因为有很多语法结构相同,但只有参数值不同的SQL,比如:

SELECT * FROM users WHERE id = '1';
SELECT * FROM users WHERE id = '2';

这些SQL的语法树相同,但每次都要进行重复的编译,很浪费时间。

而预编译可以将SQL语句模板化,值的位置用占位符替代,这样数据库就会事先编译好SQL语法结构,等真正调用的时候,再传入参数值执行,省掉了重复建立语法树的时间。

SELECT * FROM users WHERE id = {占位符}

因为语法树已经建立好了,要查询什么表、查询字段是哪些、有多少个查询条件等等这些全都已经确定好了,用户传入的参数不参与语法树的构建,就改不了SQL的语法结构,也就避免了注入。

2.2 预编译的局限性

预编译的机制是先编译,再传值,用户传递的参数无法改变SQL语法结构,从根本上解决了SQL注入的问题。

但并不是所有参数都可以使用预编译,比如动态表名和列名的场景,因为语义分析时,会解析语法树,检查表名和列名是否存在,所以表名和列名不能被占位符替代,也就无法使用预编译。

同理,排序场景的ASC/DESC也不能使用预编译。

参阅

  • 预编译为什么能防止SQL注入?一看你就明白了。预编译原理详解
  • 预编译SQL为什么能够防止SQL注入

你可能感兴趣的:(数据库,数据库)