← 中文资源

简体中文版本

NoSQL 注入

NoSQL 注入把操作符对象混入由用户输入构造的查询,使文档数据库执行开发者并未预期的条件,例如绕过登录校验。

阅读英文原文

概念概述

NoSQL 注入(NoSQL Injection)指的是:NoSQL 注入把操作符对象混入由用户输入构造的查询,使文档数据库执行开发者并未预期的条件,例如绕过登录校验。

从更严格的定义看,NoSQL injection is the class of attacks that exploit query construction in document and key-value stores, typically by injecting query operators or manipulating types when input is passed through unchecked. 该概念在工程实践中的价值,在于它能帮助团队用统一的语言描述一个具体的质量属性或设计约束,从而在评审与协作中减少歧义。

为什么需要它

It appears wherever a request body or query string is merged directly into a database filter without validation. 它与 LDAP Injection、HTTP Response Splitting、Rainbow Tables and Lookup Attacks 等概念经常一起出现,用于解决相近的工程问题。理解它的适用场景,有助于在多种备选方案之间做出有依据的取舍,而不是凭直觉选择技术方案。

常见误解

  • document databases are immune because they do not use SQL
  • only string concatenation enables injection

需要避免的反模式

  • passing a request body object straight into a query filter
  • trusting that a value is a string because the client usually sends one

实践要点

  1. An attacker can send an operator such as a not-equal condition in place of a value, turning a login check into a match against almost every record.
  2. The fix is to validate and coerce input types before building a query and to reject objects where a scalar is expected, rather than relying on the driver to sanitise.
  3. Same class of risk applies to sorting, projection, and update operators, not only to the filter, so every field that reaches the query layer needs a schema.

参考来源

https://owasp.org/www-project-web-security-testing-guide/

代码示例

-- 危险示范:拼接用户输入(永远不要这样做)
-- SELECT * FROM products WHERE name = '克" OR "1"="1'
-- ORDER BY 攻击同理:排序字段直接内插

-- 安全形态:值全部走绑定参数
SELECT id, name, price_cents
FROM products
WHERE category = $1
  AND price_cents BETWEEN $2 AND $3
ORDER BY price_cents
LIMIT $4;
-- 绑定:$1='electronics', $2=1000, $3=5000, $4=20

-- IN 列表的动态占位符:结构生成、值绑定
SELECT id, name
FROM products
WHERE id IN ($1, $2, $3);
-- 占位符数量由应用层数组长度生成,值逐个绑定

-- ORDER BY 无法参数化:白名单映射是唯一出路
-- 应用层伪代码:
--   allowed = { 'price': 'price_cents',
--               'name': 'name',
--               'created': 'created_at DESC' }
--   order_sql = allowed[user_input] or fail()
SELECT id, name, price_cents
FROM products
WHERE category = $1
ORDER BY price_cents DESC NULLS LAST;  -- order_sql 来自白名单

-- 标识符必须动态时用格式化函数加引号
SELECT quote_ident('user_input_column');  -- 带引号的安全标识符
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class JdbcDatabaseQuery {

    record User(int id, String name, String email) {}

    public static List<User> findActiveUsers(String url, String user, String pass) {
        String sql = "SELECT id, name, email FROM users WHERE active = ? ORDER BY name";

        List<User> users = new ArrayList<>();
        try (Connection conn = DriverManager.getConnection(url, user, pass);
             PreparedStatement stmt = conn.prepareStatement(sql)) {

            stmt.setBoolean(1, true);
            try (ResultSet rs = stmt.executeQuery()) {
                while (rs.next()) {
                    users.add(new User(
                        rs.getInt("id"),
                        rs.getString("name"),
                        rs.getString("email")
                    ));
                }
            }
        } catch (SQLException e) {
            System.err.println("Database error: " + e.getMessage());
        }
        return users;
    }

    public static void main(String[] args) {
        System.out.println("JDBC query example ready to connect");
    }
}