Featured image of post SQL注入预防:现代数据库安全指南Featured image of post SQL注入预防:现代数据库安全指南

SQL注入预防:现代数据库安全指南

SQL 注入预防综合指南,涵盖参数化查询、ORM 保护、存储过程、WAF 绕过、NoSQL 变体和自动扫描。

尽管人们已经意识到 SQL 注入数十年,但它仍然位居 OWASP 前 10 名之列。 2023 年至 2024 年期间,医疗保健、电子商务和政府部门发生了引人注目的涉及 SQLi 的违规事件。虽然经典的 ' OR 1=1 -- 攻击众所周知,但现代变体包括二阶注入、盲目 SQLi(基于时间和基于布尔)和带外渗漏。预防措施很容易理解,但由于遗留代码、ORM 滥用和测试自动化不足而执行不力。

参数化查询和准备语句

预准备语句是预防 SQL 注入的黄金标准。它们在数据库引擎级别将 SQL 逻辑与数据分开,从而使用户输入无法更改查询结构。

// Node.js with pg — SAFE
const result = await client.query(
  "SELECT * FROM users WHERE email = $1 AND status = $2",
  [userEmail, "active"]
);

// Python with psycopg2 — SAFE
cursor.execute(
  "INSERT INTO orders (user_id, amount) VALUES (%s, %s)",
  [user_id, amount]
);

即使使用转义输入,字符串插值也永远不安全。参数化查询不会损害查询计划缓存 — 大多数数据库对参数化查询的缓存计划比临时 SQL 更有效。

语言安全API不安全模式
Node.js(页)占位符_0使用 $ 进行字符串插值
Python(psycopg2)占位符_0f 字符串或 % 格式
Java (JDBC)占位符_0Statement 与字符串连接
C#(SqlClient)SqlCommandParameters带有内联 SQL 的 SqlCommand
PHP(PDO)PDO::prepare() + execute()mysqli::query() 带连接

诸如动​​态表名称或具有可变长度列表的 IN 子句之类的边缘情况需要仔细处理。对表名使用允许列表标识符,对动态列表使用 ANY 数组语法。


ORM 保护和陷阱

Prisma、TypeORM、Sequelize、Django ORM 和 Hibernate 等 ORM 通常默认会防止注入。但是,原始查询、LIKE 子句和 ORDER BY 子句会重新引入风险。

// Prisma — raw query pitfall
const users = await prisma.$queryRawUnsafe(
  `SELECT * FROM users WHERE name LIKE '%${searchTerm}%'` // VULNERABLE
);

// Safe alternative with parameter binding
const users = await prisma.$queryRaw`
  SELECT * FROM users WHERE name LIKE ${"%" + searchTerm + "%"}
`;

ORDER BY 不能使用参数化列。始终根据允许列表验证列名称:

const allowedColumns = ["name", "email", "created_at"];
if (!allowedColumns.includes(sortBy)) {
  throw new Error("Invalid sort column");
}
const result = await client.query(
  `SELECT * FROM users ORDER BY ${sortBy} ${sortDir}`,
);

ORM 不是灵丹妙药——开发人员必须了解底层 SQL 行为。


存储过程和数据库强化

编写良好的带有参数的存储过程是安全的。但是,使用 EXECEXECUTE IMMEDIATE 的过程中的动态 SQL 会重新引入注入风险。

-- SAFE
CREATE PROCEDURE get_user(IN user_id INT)
BEGIN
  SELECT * FROM users WHERE id = user_id;
END;

-- VULNERABLE
CREATE PROCEDURE get_user(IN table_name VARCHAR(64))
BEGIN
  SET @sql = CONCAT('SELECT * FROM ', table_name);
  PREPARE stmt FROM @sql;  -- Dynamic SQL inside procedure
  EXECUTE stmt;
END;

应用最小权限原则:应用程序数据库用户应该只拥有他们需要的权限 - 没有 DROPALTERCREATE。使用 PostgreSQL 行级安全性、SQL Server EXECUTE AS 或 MySQL SQL SECURITY 定义者/调用者模式来强制在数据库层进行访问。


输入验证和 WAF 作为纵深防御

输入验证单独存在是不够的,但作为第二层是必不可少的。优先选择基于白名单的验证而不是黑名单过滤。类型强制(使用 parseInt 解析整数)、长度限制和 Unicode 规范化 (NFC/NFD) 可防止许多绕过尝试。

Web 应用程序防火墙是一种延迟策略,而不是解决方案。攻击者使用注释混淆 (/**/)、编码变体(URL、Unicode、十六进制、双 URL 编码)、HTTP 参数污染以及不太常见的 SQL 语法(例如 BENCHMARK() 而不是 SLEEP())来绕过 WAF。

-- WAF bypass: comment obfuscation
' UNION/**/SELECT/**/password/**/FROM/**/users--

-- WAF bypass: alternative functions
1 AND BENCHMARK(5000000, MD5('test'))  -- time-based blind

NoSQL注入和自动扫描

NoSQL 数据库也容易受到攻击。 MongoDB $where 子句注入、盲提取的 $regex 注入以及 JSON 查询对象操作都会带来风险。该原则是通用的:任何连接用户输入的数据库语言都容易受到攻击。

数据库注射矢量防御
MongoDB$where$regex$ne使用 $eq,清理运算符
沙发床N1QL 字符串连接参数化 N1QL
卡桑德拉CQL 字符串连接准备好的声明
RedisEVAL/EVALSHA 与 Lua参数化EVAL

通过自动扫描左移。 Semgrep、ESLint 安全插件和 CodeQL 等 SAST 工具可检测拉取请求中的注入模式。 OWASP ZAP 等 DAST 工具可对正在运行的应用程序进行动态扫描。

# GitHub Actions workflow
name: Security Scan
on: pull_request
jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npx @neutralinojs/semgrep --config=auto
  dast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker run --network host owasp/zap2docker-stable zap-baseline.py -t http://localhost:3000

如果预防失败,请通过异常查询模式、意外错误和出站数据量峰值来检测注入。立即轮换凭证,通过查询日志分析识别注入点,遏制漏洞并保留取证证据。

通过一致应用已建立的模式,可以防止 SQL 注入。参数化查询是主要防御,具有原始查询感知的 ORM 提供结构保护、输入验证和 WAF 添加辅助层,CI/CD 中的自动扫描可验证合规性。一致地应用这些层,您就可以消除一整类漏洞。