【问题标题】:Preventing SQL Injection from node.js using mysql driver with multipleStatements: true使用带有多个语句的 mysql 驱动程序防止 node.js 的 SQL 注入:true
【发布时间】:2021-05-01 23:45:02
【问题描述】:

我一直在使用带有 express 和 ejs 模板的 node.js 开发一个“员工休假管理”网络应用程序项目供我们内部使用。现在,我的雇主想让我通过互联网访问该应用程序,我担心 SQL 注入。

假设我在 html 中有一个这样的按钮:

<a href="/edit/<%= ReqID %>">Edit</a>

这将来自index.js 文件的GET

const { edit } = require("./request");
app.get("/edit/:ReqID", edit);

然后这将转到request.js 文件中的模块edit

module.exports = {
        edit: (req, res) => {
        let ReqID= req.params.ReqID;

        let squery = `SELECT * FROM table1 WHERE ReqID="${ReqID}";

                      SELECT * FROM table2 WHERE ReqID="${ReqID}";`;

        db.query(squery, function (err, result) {
            if (err) {
                return res.status(500).send(err);
            }
            res.render("edit.ejs", {
                srecords1: result[0],
                srecords2: result[1]
            })
        })
    }
}

那里可能有两个或更多查询,我正在使用带有 multipleStatements: true 的 node.js 驱动程序 mysql 并且我知道警告 "Support for multiple statements is disabled for security reasons (it allows for SQL injection attacks if values are not properly escaped)." 这将在浏览器地址框中返回类似http://localhost:port/edit/reqid 的内容。我在 youtube 上看到了一段视频,上面说 SQL 注入可以通过浏览器的地址框(如 http://localhost:port/edit/reqid;";SELECT * FROM users;)来完成,所以我这样做了,并且可以肯定我可以看到该语法正在发送到服务器。所以我按照视频中的建议做一个这样的占位符:

module.exports = {
        edit: (req, res) => {
        let ReqID= req.params.ReqID;

        let squery = `SELECT * FROM table1 WHERE ReqID= ?;

                      SELECT * FROM table2 WHERE ReqID= ?;`;

        db.query(squery, [ReqID, ReqID], function (err, result) {
            if (err) {
                return res.status(500).send(err);
            }
            res.render("edit.ejs", {
                srecords1: result[0],
                srecords2: result[1]
            })
        })
    }
}

然后我分别尝试极端http://localhost:port/edit/reqid;";DELETE FROM users;http://localhost:port/edit/reqid;";DROP TABLE users; 并且它有效!首先它从users tble 中删除数据,并且确保第二个drop table 命令也有效。第一次尝试后,我使用相同的 sql 注入语法刷新浏览器,并收到以下消息:

{"code":"ER_BAD_TABLE_ERROR","errno":1051,"sqlMessage":"未知表 'users'","sqlState":"42S02","index":1,"sql":"SELECT * FROM table1 WHERE ReqID="ReqID;";drop table users;";SELECT * FROM table1 WHERE ReqID="ReqID;";drop table users;";"}

所以,users 表显然已从数据库中删除。

更新:

我根据from this answer获得的信息做了进一步的测试,我做了这样的事情:

module.exports = {
        edit: (req, res) => {
        let ReqID= req.params.ReqID;

        db.query(`SELECT * FROM table1 WHERE ReqID= ?; SELECT * FROM table2 WHERE ReqID= ?;` , [ReqID, ReqID], function (err, result) {
            if (err) {
                return res.status(500).send(err);
            }
            res.render("edit.ejs", {
                srecords1: result[0],
                srecords2: result[1]
            })
        })
    }
}

然后我用http://localhost:port/edit/reqid;";DROP TABLE users; 的多个变体重新测试(中间有双引号) http://localhost:port/edit/reqid;';DROP TABLE users;(中间单引号)等,它似乎不再放弃表格了。但是,我仍然看到该语句被发送到服务器,所以我仍然对 DROP 语法在某种程度上有效。

更新 2:

注意:幸运的是,部署已经延迟,我有更多时间来解决问题。

研究了一段时间,考虑到cmets并测试了多种方法,我想出了这个结构:

function(req, res) { 
        let dcode = [req.body.dcode];
        let query1 =`SELECT col1, col2 FROM table1 WHERE DCode=?`;
     
        db.query(query1, dcode, function(err, result_1) {
        if (err) {
            return res.status(500).send(err);
        }

        let query2 =`SELECT col1, col2 FROM table2 WHERE DCode=?`;

        db.query(query2, dcode, function(err, result_2) {
          if (err) {
            return res.status(500).send(err);
        }
          res.render("login.ejs", {
            result1: result_1,
            result2: result_2
          });
        });
      });
    }

这很简单,对我当前的代码没有重大改变。这是否足以防止 node.js 中的 SQL 注入?

【问题讨论】:

  • 您可以将ReqID 解析为数值(假设它是数字)。我也不明白你为什么不提出 2 个请求,或者使用外部联接来获取数据?不知道您请求什么数据真的很难说
  • 我看不出你的第二个例子和第三个例子有什么区别。他们不一样吗?
  • 至于多个语句,我真的不明白什么时候会有用。为什么不单独执行每个语句?启用多个语句有什么好处?
  • 我还建议使用github.com/sidorares/node-mysql2,因为它支持原生准备好的语句。
  • 如果您想使用multipleStatements: true,请确保清理您的输入。使用joi 模块清理您的输入

标签: mysql node.js sql-injection


【解决方案1】:

允许多语句字符串本身会导致 SQL 注入。所以,避免它。

A 计划:

考虑在服务器端结束一个数组(可能是 JSON);然后让它执行每条语句,并返回一个结果集数组。

但是一次只发出一个语句会更简单。

(如果客户端和服务器相距很远,一次一个语句的方法可能会导致明显的延迟。)

B计划:

为任何多语句需求构建合适的存储过程。这在可行的情况下避免了多语句调用。并避免延迟问题(通常)。

【讨论】:

  • 我很难理解“一次发出一份声明”的方法。我的最后一个代码(在“更新 2”中)示例仍然没有执行吗?
  • @FaNo_FN - 更新 2 似乎是安全的。
  • 知道了瑞克!...谢谢。我现在正在更改我的所有代码以遵循该结构。
【解决方案2】:

以下是一些可能有帮助的建议:

  1. 永远不要使用这样的模板字符串:Select * from table where id = ${value}。 SQL 注入将发生 - 100%!。相反,您应该使用内置驱动程序防御机制。像这样:query('Select * from table where id = ?', [value])。这应该可以防止 SQL 注入。
  2. 每个查询使用单个语句。如果您需要在一个对数据库的请求中执行多项操作 - 请考虑创建 stored procedure。存储过程也有内置的安全机制。
  3. 考虑使用query builderORM。除了内置驱动程序之外,它们还具有额外的安全层。
  4. 您还可以借助 3rd party library 显式转义 SQL 字符串。

【讨论】:

  • 是的,我已经用 (1) 测试了自己。现在我正在做 (2),就像我上次的 Update 2 一样。我认为该应用程序对于我来说太大了,此时无法使用查询构建器或 ORM;这就像从头开始一样。但我会记住未来的项目(希望如此)。感谢您的建议!
  • 如果它是一个存在许多潜在问题的大型、实时、现有项目,您可以考虑在服务器、代理或防火墙级别添加请求过滤。请注意,这不是“正确”的解决方案,而是在您手动修复代码中的问题时修补问题的临时解决方法(如其他答案中所述)。一些想法:serverfault.com/questions/989076/…
  • 这值得考虑。如果我在它上线之前计算我的剩余时间,我想我还有足够的时间来整理代码,但如果我无法跟上截止日期@Aron,我会保留你的建议作为我的第一选择>
猜你喜欢
  • 2020-11-19
  • 2020-05-15
  • 2011-04-13
  • 1970-01-01
  • 2013-03-24
  • 2018-08-16
  • 1970-01-01
  • 2014-07-17
相关资源
最近更新 更多