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({
    user: userdata.user,
    database: userdata.db,
    password: userdata.password,

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 相关的标准攻击媒介,因为连接属于我们。因此,我们应该考虑与影响其他用户数据或服务器性能相关的问题。


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}` +
  for (let i = 0; i < fields.length; ++i) {
    const field = fields[i];
    res += `/${}:${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:

    '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
    `SELECT TABLE_NAME, TABLE_ROWS, DATA_LENGTH FROM information_schema.tables LIMIT 1;`,
    function(err, results, fields) {

You will see output:


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

> { 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导航