在
Web
程序中,一般都会有后台根据用户输入内容查找或者执行相关动作的场景,如登录时查询
用户是否存在。后台在处理的时候可能是根据用户输入的用户名,拼接
SQL
之后到数据库查询来
判断,这时,如果用户恶意输入不正确内容或内容本身存在问题,会导致应用程序崩溃,甚至是
丢失数据等导致相关损失。即通过把
SQL
命令插入到
Web
表单提交或输入域名或页面请求的查询
字符串,最终达到欺骗服务器执行恶意的
SQL
命令。这种场景就称为
SQL
注入。
SQL
注入攻击是通过将恶意的
SQL
语句如添加、删除等插入到应用的输入参数中,经过后台解析
后发送到数据库服务器上解析执行进行的攻击。本文以
mysql
为例,讨论
SQL
注入以及在
Django
中如何防止
SQL
注入。
一个SQL注入的简单例子
如执行一条
SQL
语句
:
select * from tb_user where username = 'stone';
其中 stone 是用户输入的值,此时可以得到正确的值:
mysql> select * -> from tb_user -> where username = 'stone';
+------+-----------+----------+------+
| id | username | password | age |
+------+-----------+----------+------+
| 1 | stone | 123456 | 29 |
+------+-----------+----------+------+
1 row in set (0.00 sec)
但是,当用户输入错误的值,如
stone';drop table tb_test;
,如果使用的是字符串拼接的方式
去执行,
SQL
语句就变成了:
select * from tb_user where username = 'stone';drop table tb_test;';
执行结果为:
+------+-----------+----------+------+
| id | username | password | age |
+------+-----------+----------+------+
| 1 | jacobzhou | 123456 | 29 |
+------+-----------+----------+------+
1 row in set (0.00 sec) ERROR 1051 (42S02): Unknown table 'db-platform.tb_test' '>
以上就是一个
SQL
注入的例子,可以看到,如果
db-platform.tb_test
表存在,那就会被恶意删
除。例子相对较极端,对于
Django
自带的
connection
来说,使用
execute
函数执行
SQL
语句的时
候,每次只执行一条语句,后一条语句不会执行。因此上面的例子在
Django
中是不成立的,但是
足以说明
SQL
注入所带来的安全风险。那
SQL
注入都有哪些方式,如何才能防止
SQL注入呢?
SQL注入原理
Web
应用程序对于用户输入的数据和合法性没有严谨的判断,前端用户的输入直接传输给后端,
攻击者通过构造不同的参数,形成不同的
SQL
语句来实现对数据库的任意操作。
SQL
注入产生需要满足两个条件:
- 参数用户可控:前端传给后端的参数内容是用户可以控制的
- 参数带入数据库查询:传入的参数直接拼接到SQL语句,且带入数据库查询
SQL注入类型
SQL
注入的分类有很多,如
POST注入、Cookie
注入、延时注入、搜索注入等,但是归根结底也
是数字型和字符型注入的不同展现形式或者是注入的位置不同。
数字型
用户输入为整数,假设
SQL
语句为:
select * from home_application_database where id = 3;
其中
3
为数字,是用户正常输入。
当满足如下条件,则可能存在数字型注入:
- 输入3' 页面报错(SQL语法错误)
- 输入3 and 1 = 1 页面正常返回结果
- 输入3 and 1=2 页面返回错误(SQL语句返回空数据)
如果后台使用的是
select * from home_application_database where id =
和未经验证的用户输入做
拼接后去数据库查询,就满足了上面的三个条件,存在数字型注入。
这里可以看到用户输入必须是整数,后端验证用户输入必须是整数才会继续执行即可解决。
如何防止SQL注入
在开发时应该秉持一种
外部参数皆不可信
的原则来进行开发。
- 加强参数验证:开发时,验证所有来自前端的输入,必须是符合要求的数据类型,符合指定规则的数据才允许继续往下执行。
- SQL语句参数化处理 :减少使用或不使用字符串拼接的方式执行SQL,而是将用户输入当着参数传给执行SQL的方法, 如Django中的cursor.execute()函数就支持在SQL语句中使用占位符,将输入作为参数传递给方 法执行。
- 存储过程:使用存储过程也可以有效防止SQL注入,不过在存储过程中,需使用占位符,并且使用输入参数来预编译SQL语句后再执行。
Django中防止SQL注入
Django
中使用
ORM
可以有效防止
SQL
注入,所以应该尽可能使用
ORM
。但是
ORM
对于复杂查询
就无能为力了,这时就需要执行原生
SQL
时,可以使用如下方式:
- 使用extra(不建议使用这种方式执行SQL)
- 使用raw
- 使用django.db执行自定义SQL
- 直接使用pymysql
在使用原生
SQL
语句时,应避免直接使用用户输入拼接
SQL
语句,上面三种执行原生
SQL
的方式
均提供了占位符来进行参数替换,防止
SQL
注入。我们比较常用的是
3
、和
4
,两种方法都是使用
cursor.execue()
方法,具体如下:
- django.db
from django.db import connection
cursor = connection.cursor()
cursor.execute(sql)
result = cursor.fetchall()
- pymysql
connection = pymysql.connect(**mysql_server)
cursor = connection.cursor()
cursor.execute(sql)
result = cursor.fetchall()
execute
中的
sql
语句使用占位符,并传入相应参数即可防止
SQL
注入:
从上面执行结果看出,对于整数型注入,使用
execute
中带参数执行的方式,并不满足注入条
件,当使用
3'
,
3 and 1 = 1
,
3 and 1 = 2
作为输入传给
execute
执行时,程序报错。
同理对于字符型注入也一样。
注意:在使用 execute 函数执行时, SQL 语句中的占位符,不管是字符还是整型,都使用 %s ,且对于字符型的数据,在 SQL 语句里面不能使用 '%s' ,否则会报错。使用参数替换本质上是对输入的参数进行转义处理,防止输入中的引号。