使用 MySQL 查询执行计划检测 SQL 注入

Use MySQL Query Execution Plan for Detecting SQL Injections

我有一个项目要求我们允许用户创建自定义列、输入自定义值并使用这些自定义值来执行用户定义的函数。

Similar Functionality In Google Data Studio

我们已经用尽了所有我们能想到的实现策略(在前端执行公式,在隔离的执行环境中等)。

除了编写我们自己的解释器,我们能找到的唯一满足性能、功能和可扩展性要求的实现是直接在 MySQL 中执行这些功能。所以基本上采用用户输入的表达式,并在 MySQL.

中动态汇总计算结果服务器端的查询

这显然会打开一罐蠕虫病毒。

顺便说一句:我希望得到“你不应该那样做”的回应。相信我,我讨厌这是我们能找到的最佳解决方案。描述类似问题的在线资源非常稀少,所以如果有任何关于在哪里可以找到类似信息的建议 problems/solutions/implementations,我将不胜感激。

话虽如此,假设我们没有其他选择,我的问题是:我们如何安全地进行此操作?

我们目前设置了一些保护措施:

  1. 针对严格控制的子查询执行用户定义的表达式,该子查询限制查询的动态部分可以从中提取的“内部上下文”。
  2. 将永远不应使用的某些短语列入黑名单(SELECT、INSERT、UNION 等)。这引入了问题,因为用户应该能够输入如下内容: CASE WHEN {{var}} = "union pacific railroad" THEN... 但这是我们愿意做出的权衡。
  3. 限制 MySQL 连接的访问​​,使查询只能访问该功能所需的 tables/functionality。

这让我们走得很远。但我还是不适应。我在网上找不到任何信息的另一个选项是使用查询执行计划作为检测查询是否超出其范围的方法。

因此,在实际执行 query/getting 结果之前,您可以将其包装在 EXPLAIN 语句中以查看动态查询在做什么。从 EXPLAIN 查询的结果中,您 应该 能够检测到任何超出查询允许范围的操作(子查询、键引用、UNION 等) .

这是一种有用的验证方法吗?在我看来,这将是防止一系列 SQL 注入的强大工具,但我似乎无法在网上找到任何信息。

提前致谢!

(来自评论)

一些 Examples 显示正在使用的实际自动生成的查询。有可视化和列表示例显示恶意和有效自定义函数的查询执行计划。

  • GRANTSELECT 在 table(s) 上允许他们操纵。这允许任意复杂的 SELECT 查询成为 运行。 (一个缺陷:这样的查询可能 运行 很长一段时间 and/or 占用大量资源。MariaDB 有更多的设施来防止 运行-away selects。)

  • 通过具有扩展权限的存储例程提供有限的“写入”访问,但将任意值传递给它们。请参阅 SQL SECURITYDEFINER 具有创建例程的人的权限。 (与上面提到的 table 上的 INVOKER 限于 SELECT 不同。)

另一种可能有用也可能没用的技术是创建具有 select 权限的 VIEWs。例如,这可以让用户在隐藏薪水的同时看到有关员工的大部分信息。

与此相关的是对不同列的 GRANT 不同权限的能力,即使在相同的 table.

中也是如此

(我已经实现了一个类似的web app,并发布给了公司的每个人。我可以'sleep at night'。)

我不认为子查询和联合是问题。我没有看到 EXPLAIN 的实用性,除了提供更多信息以防用户是尝试查询的程序员。

EXPLAIN 可以帮助发现 long-运行ning 查询,但它并不完美。 LIMIT.

同上

更多

我认为“UDF”要么是“规范化”,要么是“EAV”;很难说是哪个。请提供 SHOW CREATE TABLE.

这是低效的,因为它会在删除 'NULL' 项之前构建临时 table:

FROM ( SELECT ...
        FROM  ...
        LEFT JOIN ...
     ) AS context
WHERE ... IS NULL

这样更好,因为它可以更快地进行过滤:

FROM ( SELECT ...
        FROM  ...
        LEFT JOIN ...
        WHERE ... IS NULL
     ) AS context

我想与以后遇到此问题的任何人分享我找到的解决方案。

为了防止有人在“自定义表达式”中输入一些恶意 SQL 注入,我们决定在将 SQL 发送到 MySQL 数据库之前对其进行预处理和分析。

我们的服务器是 运行ning NodeJS,所以我们使用 parsing library 从他们的自定义 SQL 构造抽象语法树。从这里我们可以遍历树并识别任何不应该发生的操作。

模拟代码(在本例中不会 运行)看起来像这样:

const valid_types  = [ "case", "when", "else", "column_ref", "binary_expr", "single_quote_string", "number"];
const valid_tables = [ "context" ];

// Create a mock sql expressions and parse the AST
var exp   = YOUR_CUSTOM_EXPRESSION;
var ast = parser.astify(exp);

// Check for attempted multi-statement injections
if(Array.isArray(ast) && ast.length > 1){
  this.error = throw Error("Multiple statements detected");
}

// Recursively check the AST for unallowed operations
this.recursive_ast_check([], "columns", ast.columns);


function recursive_ast_check(path, p_key, ast_node){

  // If parent key is the "type" of operation, check it against allowed values
  if(p_key === "type") {
    if(validator.valid_types.indexOf(ast_node) == -1){ 
      throw Error("Invalid type '" + ast_node + "' found at following path: " +  JSON.stringify(path));
    }
    return;
  }

  // If parent type is table, then the value should always be "context"
  if(p_key === "table") {
    if(validator.valid_tables.indexOf(ast_node) == -1){
      throw Error("Invalid table reference '" + ast_node + "' found at following path: " +  JSON.stringify(path));
    }
    return;
  }

  // Ignore null or empty nodes
  if(!ast_node || ast_node==null) { return; }

  // Recursively search array values down the chain
  if(Array.isArray(ast_node)){
    for(var i = 0; i<ast_node.length; i++) {
      this.recursive_ast_check([...path, p_key], i, ast_node[i]); 
    }
    return;
  }

  // Recursively search object keys down the chain
  if(typeof ast_node === 'object'){
    for(let key of Object.keys(ast_node)){
      this.recursive_ast_check([...path, p_key], key, ast_node[key]);
    }
  }

}

这只是根据我们的实施改编的模型,但希望它能提供一些指导。还应注意,最好也实施上面讨论的所有策略。多项保障措施胜过一项保障措施。