0 前言

先来看一副很有意思的漫画:

相信大家对于学校们糟糕的网络环境和运维手段都早有体会,在此就不多做吐槽了。今天我们来聊一聊SQL注入相关的内容。

1 何谓SQL注入?

SQL注入是一种非常常见的数据库攻击手段,SQL注入漏洞也是网络世界中最普遍的漏洞之一。大家也许都听过某某学长通过攻击学校数据库修改自己成绩的事情,这些学长们一般用的就是SQL注入方法。

SQL注入其实就是恶意用户通过在表单中填写包含SQL关键字的数据来使数据库执行非常规代码的过程。简单来说,就是数据「越俎代庖」做了代码才能干的事情。

这个问题的来源是,SQL数据库的操作是通过SQL语句来执行的,而无论是执行代码还是数据项都必须写在SQL语句之中,这就导致如果我们在数据项中加入了某些SQL语句关键字(比如说SELECT、DROP等等),这些关键字就很可能在数据库写入或读取数据时得到执行。

多言无益,我们拿真实的案例来说话。下面我们先使用SQLite建立一个学生档案表。

SQL数据库操作示例:

import sqlite3

连接数据库:

conn = sqlite3.connect('test.db')

建立新的数据表:

conn.executescript('''DROP TABLE IF EXISTS students;CREATE TABLE students(id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL);''')

插入学生信息:

students = ['Paul','Tom','Tracy','Lily']for name in students:query = "INSERT INTO students (name) VALUES ('%s')" % (name)conn.executescript(query);

检视已有的学生信息:

cursor = conn.execute("SELECT id, name from students")
print('IDName')
for row in cursor:print('{0}{1}'.format(row[0], row[1]))conn.close()

点击运行按钮将会打印目前表中的内容。上述程序中我们建立了一个test.db数据库以及一个students数据表,并向表中写入了四条学生信息。

那么SQL注入又是怎么一回事呢?我们尝试再插入一条恶意数据,数据内容就是漫画中的"Robert');DROP TABLE students;--",看看会发生什么情况。

SQL数据库注入示例:

conn = sqlite3.connect('test.db')

插入包含注入代码的信息:

name = "Robert');DROP TABLE students;--"
query = "INSERT INTO students (name) VALUES ('%s')" % (name)conn.executescript(query)

检视已有的学生信息:

cursor = conn.execute("SELECT id, name from students")
print('IDName')
for row in cursor:print('{0}{1}'.format(row[0], row[1]))conn.close()

你将会发现,运行后,程序没有输出任何数据内容,而是返回一条错误信息:表单students无法找到!

这是为什么呢?问题就在于我们所插入的数据项中包含SQL关键字DROP TABLE,这两个关键字的意义是从数据库中清除一个表单。

而关键字之前的Robert');使得SQL执行器认为上一命令已经结束,从而使得危险指令DROP TABLE得到执行。

也就是说,这段包含DROP TABLE关键字的数据项使得原有的简单的插入姓名信息的SQL语句:

INSERT INTO students (name) VALUES ('Robert')

变为了同时包含另外一条清除表单命令的语句:

INSERT INTO students (name) VALUES ('Robert');DROP TABLE students;

而SQL数据库执行上述操作后,students表单被清除,因而表单无法找到,所有数据项丢失。

2 如何防止SQL注入问题呢?

大家也许都想到了,注入问题都是因为执行了数据项中的SQL关键字,那么,只要检查数据项中是否存在SQL关键字不就可以了么?

的确是这样,很多数据库管理系统都是采取了这种看似『方便快捷』的过滤手法,但是这并不是一种根本上的解决办法,如果有个美国人真的就叫做『Drop Table』呢?你总不能逼人家改名字吧。

合理的防护办法有很多。首先,尽量避免使用常见的数据库名和数据库结构。在上面的案例中,如果表单名字并不是students,则注入代码将会在执行过程中报错,也就不会发生数据丢失的情况——SQL注入并不像大家想象得那么简单,它需要攻击者本身对于数据库的结构有足够的了解才能成功,因而在构建数据库时尽量使用较为复杂的结构和命名方式将会极大地减少被成功攻击的概率。

使用正则表达式等字符串过滤手段限制数据项的格式、字符数目等也是一种很好的防护措施。理论上,只要避免数据项中存在引号、分号等特殊字符就能很大程度上避免SQL注入的发生。

另外,就是使用各类程序文档所推荐的数据库操作方式来执行数据项的查询与写入操作,比如在上述的案例中,如果我们稍加修改,首先使用execute()方法来保证每次执行仅能执行一条语句,然后将数据项以参数的方式与SQL执行语句分离开来,就可以完全避免SQL注入的问题,如下SQL数据库反注入示例。

conn = sqlite3.connect('test.db')

以安全方式插入包含注入代码的信息:

name = "Robert');DROP TABLE students;--"
query = "INSERT INTO students (name) VALUES (?)"
conn.execute(query, [name])

检视已有的学生信息:

cursor = conn.execute("SELECT id, name from students")
print('IDName')
for row in cursor:print('{0}{1}'.format(row[0], row[1]))
conn.close()

而对于PHP而言,则可以通过mysql_real_escape_string等方法对SQL关键字进行转义,必要时审查数据项目是否安全来防治SQL注入。

当然,做好数据库的备份,同时对敏感内容进行加密永远是最重要的。某些安全性问题可能永远不会有完美的解决方案,只有我们做好最基本的防护措施,才能在发生问题的时候亡羊补牢,保证最小程度的损失。

注意:但凡有SQL注入漏洞的程序,都是因为程序要接受来自客户端用户输入的变量或URL传递的参数,并且这个变量或参数是组成SQL语句的一部分,对于用户输入的内容或传递的参数,我们应该要时刻保持警惕,这是安全领域里的「外部数据不可信任」的原则,纵观Web安全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。

1、检查变量数据类型和格式

如果你的SQL语句是类似where id={$id}这种形式,数据库里所有的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型;如果是接受邮箱,那就应该检查并严格确保变量一定是邮箱的格式,其他的类型比如日期、时间等也是一个道理。总结起来:只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我们预想的格式,这样很大程度上可以避免SQL注入攻击。

比如,我们前面接受username参数例子中,我们的产品设计应该是在用户注册的一开始,就有一个用户名的规则,比如5-20个字符,只能由大小写字母、数字以及一些安全的符号组成,不包含特殊字符。此时我们应该有一个check_username的函数来进行统一的检查。不过,仍然有很多例外情况并不能应用到这一准则,比如文章发布系统,评论系统等必须要允许用户提交任意字符串的场景,这就需要采用过滤等其他方案了。

2、过滤特殊符号

对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。

3、绑定变量,使用预编译语句  

MySQL的mysqli驱动提供了预编译语句的支持,不同的程序语言,都分别有使用预编译语句的方法

实际上,绑定变量使用预编译语句是预防SQL注入的最佳方式,使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,黑客即使本事再大,也无法改变SQL语句的结构

什么是sql预编译

1.1:预编译语句是什么 

通常我们的一条sql在db接收到最终执行完毕返回可以分为下面三个过程:

词法和语义解析、优化sql语句,制定执行计划、执行并返回结果

我们把这种普通语句称作Immediate Statements。  

但是很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如query的where子句值不同,update的set子句值不同,insert的values值不同)。

如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。

所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化,一般称这类语句叫Prepared Statements或者Parameterized Statements

预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止sql注入。

当然就优化来说,很多时候最优的执行计划不是光靠知道sql语句的模板就能决定了,往往就是需要通过具体值来预估出成本代价。

1.2:MySQL的预编译功能

注意MySQL的老版本(4.1之前)是不支持服务端预编译的,但基于目前业界生产环境普遍情况,基本可以认为MySQL支持服务端预编译。

下面我们来看一下MySQL中预编译语句的使用。

(1)建表 首先我们有一张测试表t,结构如下所示:

mysql> show create table t\G
*************************** 1. row ***************************Table: t
Create Table: CREATE TABLE `t` (`a` int(11) DEFAULT NULL,`b` varchar(20) DEFAULT NULL,UNIQUE KEY `ab` (`a`,`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

(2)编译

我们接下来通过 PREPARE stmt_name FROM preparable_stm的语法来预编译一条sql语句

mysql> prepare ins from 'insert into t select ?,?';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

(3)执行

我们通过EXECUTE stmt_name [USING @var_name [, @var_name] ...]的语法来执行预编译语句

mysql> set @a=999,@b='hello';
Query OK, 0 rows affected (0.00 sec)mysql> execute ins using @a,@b;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0mysql> select * from t;
+------+-------+
| a    | b     |
+------+-------+
|  999 | hello |
+------+-------+
1 row in set (0.00 sec)

可以看到,数据已经被成功插入表中。

MySQL中的预编译语句作用域是session级,但我们可以通过max_prepared_stmt_count变量来控制全局最大的存储的预编译语句。

mysql> set @@global.max_prepared_stmt_count=1;
Query OK, 0 rows affected (0.00 sec)mysql> prepare sel from 'select * from t';
ERROR 1461 (42000): Can't create more than max_prepared_stmt_count statements (current value: 1)

当预编译条数已经达到阈值时可以看到MySQL会报如上所示的错误。

(4)释放

如果我们想要释放一条预编译语句,则可以使用{DEALLOCATE | DROP} PREPARE stmt_name的语法进行操作:

mysql> deallocate prepare ins;
Query OK, 0 rows affected (0.00 sec)

为什么PrepareStatement可以防止sql注入

原理是采用了预编译的方法,先将SQL语句中可被客户端控制的参数集进行编译,生成对应的临时变量集,再使用对应的设置方法,为临时变量集里面的元素进行赋值,赋值函数setString(),会对传入的参数进行强制类型检查和安全检查,所以就避免了SQL注入的产生。下面具体分析

(1):为什么Statement会被sql注入

因为Statement之所以会被sql注入是因为SQL语句结构发生了变化。比如:

"select*from tablename where username='"+uesrname+
"'and password='"+password+"'"

在用户输入'or true or'之后sql语句结构改变。

select*from tablename where username=''or true or'' and password=''

这样本来是判断用户名和密码都匹配时才会计数,但是经过改变后变成了或的逻辑关系,不管用户名和密码是否匹配该式的返回值永远为true;

(2)为什么Preparement可以防止SQL注入。

因为Preparement样式为

select*from tablename where username=? and password=?

该SQL语句会在得到用户的输入之前先用数据库进行预编译,这样的话不管用户输入什么用户名和密码的判断始终都是并的逻辑关系,防止了SQL注入

简单总结,参数化能防注入的原因在于,语句是语句,参数是参数,参数的值并不是语句的一部分,数据库只按语句的语义跑,至于跑的时候是带一个普通背包还是一个怪物,不会影响行进路线,无非跑的快点与慢点的区别。

mybatis是如何防止SQL注入的

1、首先看一下下面两个sql语句的区别:

<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = #{username,jdbcType=VARCHAR}
and password = #{password,jdbcType=VARCHAR}
</select><select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = ${username,jdbcType=VARCHAR}
and password = ${password,jdbcType=VARCHAR}
</select>

mybatis中的#和$的区别:

1、#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。

如:where username=#{username},如果传入的值是111,那么解析成sql时的值为where username="111", 如果传入的值是id,则解析成的sql为where username="id". 

2、$将传入的数据直接显示生成在sql中。

如:where username=${username},如果传入的值是111,那么解析成sql时的值为where username=111;

如果传入的值是;drop table user;,则解析成的sql为:select id, username, password, role from user where username=;drop table user;

3、#方式能够很大程度防止sql注入,$方式无法防止Sql注入。

4、$方式一般用于传入数据库对象,例如传入表名.

5、一般能用#的就别用$,若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。

6、在MyBatis中,“${xxx}”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“${xxx}”这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。

【结论】在编写MyBatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。

mybatis是如何做到防止sql注入的

MyBatis框架作为一款半自动化的持久层框架,其SQL语句都要我们自己手动编写,这个时候当然需要防止SQL注入。其实,MyBatis的SQL是一个具有“输入+输出”的功能,类似于函数的结构,参考上面的两个例子。其中,parameterType表示了输入的参数类型,resultType表示了输出的参数类型。回应上文,如果我们想防止SQL注入,理所当然地要在输入参数上下功夫。上面代码中使用#的即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的:

select id, username, password, role from user where username=? and password=?

不管输入什么参数,打印出的SQL都是这样的。这是因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。

【底层实现原理】MyBatis是如何做到SQL预编译的呢?其实在框架底层,是JDBC中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的SQL语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译

参考来源:jizhi.im/blog/post/sql_injection_intro
https://www.cnblogs.com/myseries/p/10821372.html

猜你喜欢

1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBoo发起和总结

2、如何才能成为优秀的架构师?

3、从零开始搭建创业公司后台技术栈

4、程序员一般可以从什么平台接私活?

5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6、滴滴业务中台构建实践,首次曝光

7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

8、15张图看懂瞎忙和高效的区别!

SQL注入详解,看这篇就够了相关推荐

  1. Java NIO全面详解(看这篇就够了)

    很多技术框架都使用NIO技术,学习和掌握Java NIO技术对于高性能.高并发网络的应用是非常关键的 NIO简介 NIO 中的 N 可以理解为 Non-blocking,不单纯是 New,是解决高并发 ...

  2. SQL注入详解及预防

    SQL注入详解及预防 1.1.1 摘要 日前,国内最大的程序员社区CSDN网站的用户数据库被黑客公开发布,600万用户的登录名及密码被公开泄露,随后又有多家网站的用户密码被流传于网络,连日来引发众多网 ...

  3. SQL 注入详解扫盲

    实习期间的主要工作还是研究 WEB 安全,编程语言是 Python,常用到正则表达式,对 HTTP 的协议也非常清晰. 刚过来的时候,研究的主要是 SQL 注入,因为之前没有搞过安全,所有费了好长一段 ...

  4. SQL注入详解和简单绕过原理

    1.什么是SQL 结构化查询语言(Structured Query Language)简称SQL SQL使我们有能力访问数据库 2.什么是SQL注入 用户提交的数据可以被数据库解析执行 如果用户随随便 ...

  5. sql注入详解 一文了解sql注入所有常见方法

    前言 刷完了sqli-labs 对sql注入有了些许认识 在此做个小结与记录 1.什么是sql注入 sql,Structured Query Language,叫做结构化查询语言,管理数据库时用到的一 ...

  6. benchmarksql测试mysql_web安全(一):sql注入详解

    SQL 注入分类方式: 提交方式:GET POST COOKIE参数注入:数字型/字符型/搜索型数据库类型:ACCESS/Mysql/MSSQL/Oracle手工注入方法:联合查询.报错注入.盲注(基 ...

  7. SQL注入详解(全网最全,万字长文)

    漏洞原因 一些概念: SQL:用于数据库中的标准数据查询语言. web分为前端和后端,前端负责进行展示,后端负责处理来自前端的请求并提供前端展示的资源. 而数据库就是存储资源的地方. 而服务器获取数据 ...

  8. SQL注入详解(第二章手工注入)

    二.MySQL手工注入 1.SQL注入之sqli-labs环境搭建 往往很多新手在刚学习SQL注入的时候,都需要拥有一个能SQL注入的网站,需要有SQL注入点 的.直接去互联网上找的话对新手未免有点太 ...

  9. php mysql防卡_php mysql防止sql注入详解

    引发 SQL 注入攻击的主要原因,是因为以下两点原因: 1. php 配置文件 php.ini 中的 magic_quotes_gpc选项没有打开,被置为 off 2. 开发者没有对数据类型进行检查和 ...

  10. sql 单引号_三种数据库的 SQL 注入详解

    SQL 注入原理 SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用 ...

最新文章

  1. red gate | sql CI
  2. git简介及使用方法
  3. WSP框架:WEB组件的原理
  4. max格式转obj小工具_PDF文件转JPG等图片格式的小工具
  5. 详细讲解python中的析构方法;
  6. 【源码类】开源项目汇总
  7. 看见到洞见之引子(二)机器学习算法
  8. 【渝粤题库】广东开放大学 外贸会计1 形成性考核
  9. 华为笔记本matebook13_华为引领“第三代移动办公”新纪元 华为MateBook开启“智慧化办公”新赛道...
  10. c#问题(按F1或F2键时触发事件)
  11. 使用JMeter测试WebSocket接口
  12. 单用户模式 启动 mysql_单用户模式连接以及故障排除
  13. tuxedo错误码6_TUXEDO启动常见错误和解决方法
  14. 一文搞懂软件测试,完整总结软件测试基础知识
  15. 电气工程计算机网络基础知识大全,2018年注册电气工程师考试(电气与信息技术公共基础)知识点复习:计算机基础[网络体系结构与协议]...
  16. kindle看pdf乱码,Kindle中文乱码问题解决办法
  17. Lamp部署于三台主机中
  18. uniapp 安卓快捷方式插件(桌面长按app图标) Ba-Shortcut
  19. 2021-02-18docker
  20. Unity开发弱数据多人联网游戏(一)

热门文章

  1. uvalive 6932
  2. 《MPLS在Cisco IOS上的配置》一2.3 配置命令参考
  3. 一种类型安全的Java HTTP客户端库Retrofit
  4. 做程序员的老婆应该注意的一些事情
  5. linux查看默认启动服务
  6. 转 如何通过ildasm/ilasm修改assembly的IL代码
  7. Windows Server 2008 启用公共文件夹共享
  8. ACS 4.2安装图解
  9. 程序员应知——我们不是客户
  10. exce小技巧,Mac Excel单元格内换行快捷键