CodeQL 文档

从用户控制的来源构建的数据库查询

ID: js/sql-injection
Kind: path-problem
Security severity: 8.8
Severity: error
Precision: high
Tags:
   - security
   - external/cwe/cwe-089
   - external/cwe/cwe-090
   - external/cwe/cwe-943
Query suites:
   - javascript-code-scanning.qls
   - javascript-security-extended.qls
   - javascript-security-and-quality.qls

点击查看 CodeQL 仓库中的查询

如果数据库查询(例如 SQL 或 NoSQL 查询)是从用户提供的数据构建的,而没有进行足够的清理,恶意用户可能能够运行恶意数据库查询。

建议

大多数数据库连接器库提供了一种通过查询参数或预处理语句将不受信任的数据安全地嵌入查询中的方法。

对于 NoSQL 查询,请使用像 MongoDB 的 $eq 这样的运算符来确保不受信任的数据被解释为字面值而不是查询对象。或者,在查询中使用不受信任的数据之前,请检查它是否为字面值而不是查询对象。

对于 SQL 查询,请使用查询参数或预处理语句将不受信任的数据嵌入到查询字符串中,或者使用像 sqlstring 这样的库来转义不受信任的数据。

示例

在以下示例中,假设函数 handler 是 Web 应用程序中的 HTTP 请求处理程序,其参数 req 包含请求对象。

处理程序从用户输入构建 SQL 查询字符串,并使用 pg 库将其作为数据库查询执行。用户输入可能包含引号字符,因此此代码容易受到 SQL 注入攻击。

const app = require("express")(),
      pg = require("pg"),
      pool = new pg.Pool(config);

app.get("search", function handler(req, res) {
  // BAD: the category might have SQL special characters in it
  var query1 =
    "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" +
    req.params.category +
    "' ORDER BY PRICE";
  pool.query(query1, [], function(err, results) {
    // process results
  });
});

要修复此漏洞,我们可以使用查询参数将用户输入嵌入到查询字符串中。在此示例中,我们使用 pg Postgres 数据库连接器库提供的 API,但其他库也提供类似的功能。此版本不受注入攻击的影响。

const app = require("express")(),
      pg = require("pg"),
      pool = new pg.Pool(config);

app.get("search", function handler(req, res) {
  // GOOD: use parameters
  var query2 =
    "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1 ORDER BY PRICE";
  pool.query(query2, [req.params.category], function(err, results) {
    // process results
  });
});

或者,我们可以使用像 sqlstring 这样的库在将用户输入嵌入到查询字符串之前对其进行转义

const app = require("express")(),
      pg = require("pg"),
      SqlString = require('sqlstring'),
      pool = new pg.Pool(config);

app.get("search", function handler(req, res) {
  // GOOD: the category is escaped using mysql.escape
  var query1 =
    "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" +
    SqlString.escape(req.params.category) +
    "' ORDER BY PRICE";
  pool.query(query1, [], function(err, results) {
    // process results
  });
});

示例

在以下示例中,一个 express 处理程序尝试从 MongoDB 集合中删除单个文档。要删除的文档由其 _id 字段标识,该字段是从用户输入构建的。用户输入可能包含查询对象,因此此代码容易受到 NoSQL 注入攻击。

const express = require("express");
const mongoose = require("mongoose");
const Todo = mongoose.model(
  "Todo",
  new mongoose.Schema({ text: { type: String } }, { timestamps: true })
);

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.delete("/api/delete", async (req, res) => {
  let id = req.body.id;

  await Todo.deleteOne({ _id: id }); // BAD: id might be an object with special properties

  res.json({ status: "ok" });
});

要修复此漏洞,我们可以使用 $eq 运算符来确保用户输入被解释为字面值而不是查询对象

app.delete("/api/delete", async (req, res) => {
  let id = req.body.id;
  await Todo.deleteOne({ _id: { $eq: id } }); // GOOD: using $eq operator for the comparison

  res.json({ status: "ok" });
});

或者,在使用之前检查用户输入是否为字面值而不是查询对象

app.delete("/api/delete", async (req, res) => {
  let id = req.body.id;
  if (typeof id !== "string") {
    res.status(400).json({ status: "error" });
    return;
  }
  await Todo.deleteOne({ _id: id }); // GOOD: id is guaranteed to be a string

  res.json({ status: "ok" });
});

参考资料

  • ©GitHub, Inc.
  • 条款
  • 隐私