BatBadBut: You can’t securely execute commands on Windows

Introduction 介绍

Hello, I’m RyotaK ( @ryotkak ), a security engineer at Flatt Security Inc.
大家好,我是 RyotaK ( @ryotkak ),Flatt Security Inc. 的安全工程师。

Recently, I reported multiple vulnerabilities to several programming languages that allowed an attacker to perform command injection on Windows when the specific conditions were satisfied.
最近,我报告了几种编程语言的多个漏洞,这些漏洞允许攻击者在满足特定条件时在 Windows 上执行命令注入。

Today, affected vendors published advisories of these vulnerabilities 1, so I’m documenting the details here to provide more information about the vulnerabilities and minimize the confusion regarding the high CVSS score.
今天,受影响的供应商发布了这些漏洞 1 的公告,因此我在这里记录了详细信息,以提供有关漏洞的更多信息,并最大限度地减少对高 CVSS 分数的混淆。

TL;DR TL的;博士

The BatBadBut is a vulnerability that allows an attacker to perform command injection on Windows applications that indirectly depend on the CreateProcess function when the specific conditions are satisfied.
BatBadBut 是一个漏洞,允许攻击者在满足特定条件时对间接依赖该 CreateProcess 函数的 Windows 应用程序执行命令注入。

CreateProcess() implicitly spawns cmd.exe when executing batch files (.bat.cmd, etc.), even if the application didn’t specify them in the command line.
CreateProcess() cmd.exe 在执行批处理文件( .bat.cmd 等)时隐式生成,即使应用程序未在命令行中指定它们也是如此。

The problem is that the cmd.exe has complicated parsing rules for the command arguments, and programming language runtimes fail to escape the command arguments properly.
问题在于 cmd.exe 命令参数的解析规则很复杂,并且编程语言运行时无法正确转义命令参数。

Because of this, it’s possible to inject commands if someone can control the part of command arguments of the batch file.
因此,如果有人可以控制批处理文件的命令参数部分,则可以注入命令。

The following simple Node.js code snippet, for example, may pop calc.exe on the server machine:
例如,以下简单的Node.js代码片段可能会在服务器计算机上弹出calc.exe:

const { spawn } = require('child_process');
const child = spawn('./test.bat', ['<your-input-here>']);

This happens only if a batch file is explicitly specified in the command line passed to CreateProcess(), and it doesn’t happen when a .exe file is specified.
仅当在传递给 CreateProcess() 的命令行中显式指定批处理文件时,才会发生这种情况,而在指定 .exe 文件时不会发生这种情况。

However, since Windows includes .bat and .cmd files in the PATHEXT environment variable by default, some runtimes execute batch files against the developers’ intention if there is a batch file with the same name as the command that the developer intended to execute. So, even the following snippet may lead to arbitrary command executions whereas it doesn’t include .bat or .cmd explicitly:
但是,由于 Windows 默认情况下在 PATHEXT 环境变量中包含 .bat.cmd 文件,因此如果存在与开发人员打算执行的命令同名的批处理文件,则某些运行时会违背开发人员的意图执行批处理文件。因此,即使是以下代码片段也可能导致任意命令执行,而它不包括.bat或显式.cmd:

cmd := exec.Command("test", "<your-input-here>")
cmd.Run()

Exploitation of these behaviors is possible when the following conditions are satisfied:
当满足以下条件时,可以利用这些行为:

  • The application executes a command on Windows
    应用程序在 Windows 上执行命令
  • The application doesn’t specify the file extension of the command, or the file extension is .bat or .cmd
    应用程序未指定命令的文件扩展名,或者文件扩展名为 .bat.cmd
  • The command being executed contains user-controlled input as part of the command arguments
    正在执行的命令包含用户控制的输入作为命令参数的一部分
  • The runtime of the programming language fails to escape the command arguments for cmd.exe properly2
    编程语言的运行时无法 cmd.exe 正确 2 转义命令参数

By exploiting these behaviors, arbitrary command execution might be possible.
通过利用这些行为,可以执行任意命令。

I created a flowchart to determine if your applications are affected by this vulnerability, so please refer to Appendix A if you are unsure whether you are affected or not, and refer to Appendix B for the status of the affected programming languages.
我创建了一个流程图来确定您的应用程序是否受到此漏洞的影响,因此,如果您不确定自己是否受到影响,请参阅附录 A,并参考附录 B 了解受影响的编程语言的状态。

CVSS Score CVSS 分数

First of all, I have to mention that you shouldn’t apply the CVSS score of library vulnerabilities to your application directly.
首先,我不得不提一下,你不应该将库漏洞的CVSS分数直接应用于你的应用程序。

The user guide of CVSS v3.1 states that the CVSS score of a library should be calculated based on the worst-case scenario, and this is why the recent vulnerabilities for programming languages got high scores despite the requirement of specific conditions.
CVSS v3.1 的用户指南指出,库的 CVSS 分数应该根据最坏的情况来计算,这就是为什么尽管需要特定条件,但最近的编程语言漏洞还是获得了高分。

Instead of applying the CVSS score directly, you should recalculate the score based on the specific implementation:
您应该根据具体实现重新计算分数,而不是直接应用 CVSS 分数:

https://www.first.org/cvss/v3.1/user-guide#3-7-Scoring-Vulnerabilities-in-Software-Libraries-and-Similar

Technical Details 技术细节

While I’m not a fan of naming vulnerabilities that are not internet-breaking, I’m a fan of puns, so I decided to call this vulnerability BatBadBut because it’s about batch files and badbut not the worst.
虽然我不喜欢命名不会破坏互联网的漏洞,但我是双关语的粉丝,所以我决定称这个漏洞为它, BatBadBut 因为它是关于批处理文件和坏的,但不是最糟糕的。

From this section, I’ll explain the technical side of BatBadBut and why the command injection is possible.
在本节中,我将解释技术 BatBadBut 方面以及为什么命令注入是可能的。

Please note that some of the code snippets don’t work on the latest version of runtimes, as some affected vendors already patched the issue.
请注意,某些代码片段不适用于最新版本的运行时,因为一些受影响的供应商已经修补了该问题。

Root Cause 根源

The root cause of BatBadBut is the overlooked behavior of the CreateProcess function on Windows.
BatBadBut 根本原因是该 CreateProcess 函数在 Windows 上的行为被忽视。

When executing batch files with the CreateProcess function, Windows implicitly spawns cmd.exe because Windows can’t execute batch files without it.
使用该函数执行批处理文件时,Windows 会隐式生成, cmd.exe 因为没有它 CreateProcess Windows 无法执行批处理文件。

For example, the following code snippet spawns C:\Windows\System32\cmd.exe /c .\test.bat to execute the batch file test.bat:
例如,生成以下代码片段 C:\Windows\System32\cmd.exe /c .\test.bat 以执行批处理文件 test.bat

wchar_t arguments[] = L".\\test.bat";
STARTUPINFO si{};
PROCESS_INFORMATION pi{};
CreateProcessW(nullptr, arguments, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi);

BatBadBut: You can't securely execute commands on Windows

While this isn’t a problem itself, the issue arises when the programming language wraps the CreateProcess function and adds the escaping mechanism for the command arguments.
虽然这本身不是问题,但当编程语言包装 CreateProcess 函数并为命令参数添加转义机制时,问题就会出现。

Wrapping CreateProcess 包皮 CreateProcess

Most programming languages provide a function to execute a command, and they wrap the CreateProcess function to provide a more user-friendly interface.
大多数编程语言都提供执行命令的函数,并且它们包装该 CreateProcess 函数以提供更用户友好的界面。

For example, the child_process module in Node.js3 wraps the CreateProcess function and provides a way to execute a command with arguments like the following:
例如,Node.js child_process 中的模块 3 包装了 CreateProcess 该函数,并提供了一种执行具有如下参数的命令的方法:

const { spawn } = require('child_process');
const child = spawn('echo', ['hello', 'world']);

As you can see in the above code snippet, the spawn function takes the command and arguments as separate arguments.
如上所述,该 spawn 函数将命令和参数作为单独的参数。

It then internally escapes arguments to pass them to the CreateProcess function.
然后,它在内部转义参数以将它们传递给 CreateProcess 函数。

src/win/process.c line 444-518
src/win/process.c 第 444-518 行

/*
 * Quotes command line arguments
 * Returns a pointer to the end (next char to be written) of the buffer
 */
WCHAR* quote_cmd_arg(const WCHAR *source, WCHAR *target) {
  [...]

  /*
   * Expected input/output:
   *   input : hello"world
   *   output: "hello\"world"
   *   input : hello""world
   *   output: "hello\"\"world"
   *   input : hello\world
   *   output: hello\world
   *   input : hello\\world
   *   output: hello\\world
   *   input : hello\"world
   *   output: "hello\\\"world"
   *   input : hello\\"world
   *   output: "hello\\\\\"world"
   *   input : hello world\
   *   output: "hello world\\"
   */

  *(target++) = L'"';
  start = target;
  quote_hit = 1;

  for (i = len; i > 0; --i) {
    *(target++) = source[i - 1];

    if (quote_hit && source[i - 1] == L'\\') {
      *(target++) = L'\\';
    } else if(source[i - 1] == L'"') {
      quote_hit = 1;
      *(target++) = L'\\';
    } else {
      quote_hit = 0;
    }
  }
  target[0] = L'\0';
  _wcsrev(start);
  *(target++) = L'"';
  return target;
}

Most developers expect that the spawn function properly escapes the command arguments, and it’s true in most cases.4
大多数开发人员都希望该 spawn 函数能够正确地转义命令参数,并且在大多数情况下都是如此。 4

However, as I mentioned earlier, the CreateProcess function implicitly spawns cmd.exe when executing batch files.
但是,正如我之前提到的,该 CreateProcess 函数 cmd.exe 在执行批处理文件时隐式生成。

And unfortunately, the cmd.exe has different escaping rules compared to the usual escaping mechanism.
不幸的是,与通常的逃逸机制相比,它 cmd.exe 有不同的逃逸规则。

Parsing rule of cmd.exe
解析规则 cmd.exe

Most shells for Unix-like systems have similar (or the same) escaping rules; backslashes (\) are used as an escape character.
大多数类 Unix 系统的 shell 都有相似(或相同)的转义规则;反斜杠 ( \ ) 用作转义字符。

So, if you want to escape a double quote (") inside of a double-quoted string, you can use the backslash like the following:
因此,如果要在双引号字符串中转义双引号 ( " ),可以使用反斜杠,如下所示:

echo "Hello \"World\""

Using backslash as the escape character seems to be a de facto standard, and other things like JSON or YAML also use it.
使用反斜杠作为转义字符似乎是一个事实上的标准,JSON 或 YAML 等其他东西也使用它。

However, when you execute the following command on the command prompt, calc.exe will be executed:
但是,当您在命令提示符下执行以下命令时, calc.exe 将执行:

echo "\"&calc.exe"

This is because the command prompt doesn’t use the backslash as an escape character, and uses the caret (^) instead.
这是因为命令提示符不使用反斜杠作为转义字符,而是使用插入符号 ( ^ )。

Back to the child_process example, it escapes the double quotes (") in command arguments using a backslash (\).
回到示例, child_process 它使用反斜杠 ( \ ) 转义命令参数中的双引号 ( " )。

Due to the escaping rules of cmd.exe mentioned above, this escaping is not sufficient when executing the batch file, so the following snippet spawns calc.exe even though the argument is separated properly, and the shell option5 is not enabled:
由于上述转义规则 cmd.exe ,在执行批处理文件时,这种转义是不够的,因此 calc.exe 即使参数被正确分隔,也会生成以下代码片段,并且未启用该 shell 选项 5

const { spawn } = require('child_process');
const child = spawn('./test.bat', ['"&calc.exe']);

Because of this behavior, a malicious command line argument might be able to perform command injection, and this is the main problem of BatBadBut.
由于这种行为,恶意命令行参数可能能够执行命令注入,这是 的主要 BatBadBut 问题。

Mitigation 缓解

Escaping double quotes? 转义双引号?

The problem here is that the double-quoted string is broken by the double quote inside of the string.
这里的问题是双引号字符串被字符串内部的双引号破坏。

So, it seems that escaping double quotes (") with a caret (^) is sufficient to prevent the command injection.6
因此,似乎用插入符号 ( ^ ) 转义双引号 ( " ) 就足以防止命令注入。 6

But in fact, that is not enough to prevent the command injection.
但实际上,这还不足以阻止命令注入。

Surprisingly, the command prompt parses and expands variables (e.g., %PATH%) before any other parsing.
令人惊讶的是,命令提示符在任何其他解析之前解析和扩展变量(例如, %PATH% )。

This means that the following command will execute calc.exe although the &calc.exe is inside of the double-quoted string:
这意味着,尽管 &calc.exe is 位于双引号字符串中,但将执行 calc.exe 以下命令:

SET VAR=^"
echo "%VAR%&calc.exe"

While the default environment variables of Windows don’t contain the double quote (") in their value, there is a special variable called CMDCMDLINE, that contains the command line used to start the current command prompt session.
虽然 Windows 的默认环境变量的值中不包含双引号 ( " ),但有一个名为 CMDCMDLINE 的特殊变量,其中包含用于启动当前命令提示符会话的命令行。

Assuming that the following command is executed on the PowerShell, "C:\WINDOWS\system32\cmd.exe" /c "echo %CMDCMDLINE%" will be printed:
假设在 PowerShell 上执行以下命令, "C:\WINDOWS\system32\cmd.exe" /c "echo %CMDCMDLINE%" 将打印:

cmd.exe /c "echo %CMDCMDLINE%"

And, by using the variable substring extraction in the command prompt, it’s possible to extract the double quote (") from this variable.
而且,通过在命令提示符下使用变量子字符串提取,可以从此变量中提取双引号 ( " )。

So, the following command spawns calc.exe when executed on PowerShell:
因此,在 PowerShell 上执行 calc.exe 时会生成以下命令:

cmd.exe /c 'echo "%CMDCMDLINE:~-1%&calc.exe"'

Due to this behavior, escaping double quotes with a caret is insufficient to prevent the command injection when executing the batch file, and requires further escaping. I’ll explain about it in the next section.
由于此行为,使用插入符号转义双引号不足以在执行批处理文件时阻止命令注入,并且需要进一步转义。我将在下一节中对此进行解释。

As a Developer 作为开发人员

Since not all programming languages patched the issue2, you should be careful when executing commands on Windows.
由于并非所有编程语言都修补了这个问题 2 ,因此在Windows上执行命令时应小心。

As a developer who executes commands on Windows, but doesn’t want to execute batch files, you should always specify the file extension of the command.
作为在 Windows 上执行命令但不想执行批处理文件的开发人员,您应始终指定命令的文件扩展名。

For example, the following code snippet may execute test.bat instead of test.exe if the user places test.bat in the directory included in the PATH environment variable:
例如, test.exe 如果用户放置 test.bat 在环境变量中包含的 PATH 目录中,则可能会执行 test.bat 以下代码片段:

cmd := exec.Command("test", "arg1", "arg2")

To prevent this, you should always specify the file extension of the command like the following:
若要防止出现这种情况,应始终指定命令的文件扩展名,如下所示:

cmd := exec.Command("test.exe", "arg1", "arg2")

If you want to execute batch files, and your runtime doesn’t escape the command arguments properly for the batch file, you must escape user-controlled input before using it as command arguments.
如果要执行批处理文件,并且运行时未正确转义批处理文件的命令参数,则必须先转义用户控制的输入,然后才能将其用作命令参数。

Since spaces can’t be escaped properly outside of the double-quoted string7, you have to use double quotes to wrap the command arguments.
由于空格无法在双引号字符串 7 之外正确转义,因此必须使用双引号来包装命令参数。

However, inside the double-quoted string, % can’t be escaped properly8.
但是,在双引号字符串中, % 无法正确 8 转义。

To solve this situation, the following tricky escaping is required:9
要解决这种情况,需要以下棘手的转义: 9

  1. Disable the automatic escaping that uses the backslash (\) provided by the runtime.
    禁用使用运行时提供的反斜杠 ( \ ) 的自动转义。
  2. Apply the following steps to each argument:
    对每个参数应用以下步骤:

    1. Replace percent sign (%) with %%cd:~,%.
      将百分号 ( % ) 替换为 %%cd:~,%
    2. Replace the backslash (\) in front of the double quote (") with two backslashes (\\).
      将双引号 ( " ) 前面的反斜杠 ( \ ) 替换为两个反斜杠 ( \\ )。
    3. Replace the double quote (") with two double quotes ("").
      将双引号 ( " ) 替换为两个双引号 ( "" )。
    4. Remove newline characters (\n).
      删除换行符 ( \n )。
    5. Enclose the argument with double quotes (").
      用双引号 ( ) 将参数括起来 "

By replacing % with %%cd:~,%%cd:~,% will be expanded to an empty string, and the command prompt fails to expand the actual variable, so the % will be treated as a normal character.
通过 % 替换为 %%cd:~,%%cd:~,% 将扩展为空字符串,并且命令提示符无法扩展实际变量,因此将 % 被视为普通字符。

Please note that if delayed expansion is enabled via the registry value DelayedExpansion, it must be disabled by explicitly calling cmd.exe with the /V:OFF option.
请注意,如果通过注册表值 DelayedExpansion 启用延迟扩展,则必须通过使用 /V:OFF 该选项显式调用 cmd.exe 来禁用它。

Also, note that the escaping for % requires the command extension to be enabled. If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option.
另请注意,转义需要 % 启用命令扩展。如果通过注册表值 EnableExtensions 禁用它,则必须使用该 /E:ON 选项启用它。

As a User 作为用户

To prevent the unexpected execution of batch files, you should consider moving the batch files to a directory that is not included in the PATH environment variable.
为了防止批处理文件意外执行,应考虑将批处理文件移动到 PATH 环境变量中未包含的目录。

In this case, the batch files won’t be executed unless the full path is specified, so the unexpected execution of batch files can be prevented.
在这种情况下,除非指定了完整路径,否则不会执行批处理文件,因此可以防止批处理文件的意外执行。

As a Maintainer of the runtime
作为运行时的维护者

If you maintain a runtime of a programming language, I’d recommend you implement an additional escaping mechanism for batch files.
如果你维护一种编程语言的运行时,我建议你为批处理文件实现一个额外的转义机制。

Even if you don’t want to fix it on the runtime layer, you should at least document the issue and provide a proper warning to the users, as this problem is not well-known.
即使你不想在运行时层修复它,你也应该至少记录这个问题,并向用户提供适当的警告,因为这个问题并不为人所知。

Conclusion 结论

In this article, I explained the technical details of BatBadBut, a vulnerability that allows an attacker to perform command injection on Windows when the specific conditions are met.
在本文中,我解释了 BatBadBut 的技术细节,该漏洞允许攻击者在满足特定条件时在 Windows 上执行命令注入。

As I mentioned several times in this article, this issue doesn’t affect most applications, but in case you are affected, you should properly escape the command arguments manually.
正如我在本文中多次提到的,此问题不会影响大多数应用程序,但如果您受到影响,则应手动正确转义命令参数。

I hope that this article helps you to understand the severity of this issue and mitigate the issue properly.
我希望本文能帮助您了解此问题的严重性并正确缓解该问题。

Appendix 附录

Appendix A: Flowchart to determine if your applications are affected
附录 A:用于确定应用程序是否受到影响的流程图

BatBadBut: You can't securely execute commands on Windows

Appendix B: Status of the affected programming languages
附录 B:受影响的编程语言的状态

Project 项目 Status 地位
Erlang 二郎 Documentation update 文档更新
Go Documentation update 文档更新
Haskell 哈斯克尔 Patch available 补丁可用
Java 爪哇岛 Won’t fix 无法修复
Node.js Patch will be available
补丁将可用
PHP Patch will be available
补丁将可用
Python  Documentation update 文档更新
Ruby 红宝石 Documentation update 文档更新
Rust  Patch available 补丁可用

  1. The advisory on CERT/CC will be available soon ↩︎
    关于 CERT/CC 的公告即将↩发布 ︎

  2. Please refer to the Appendix B for the status of the affected programming languages. ↩︎ ↩︎
    请参阅附录 B,了解受影响编程语言的状态。↩︎ ↩︎

  3. As I use Node.js mostly, I’m using it as an example here. However, the issue is not specific to Node.js, and it affects other programming languages as well. ↩︎
    由于我主要使用Node.js,因此我在这里将其用作示例。但是,该问题并非特定于 Node.js,它还会影响其他编程语言。↩︎

  4. In fact, many programming languages guarantee that the command arguments are escaped properly, and/or don’t use shell. ↩︎
    事实上,许多编程语言都保证命令参数被正确转义,和/或不使用 shell。↩︎

  5. When the shell option is disabled, the child_process module doesn’t spawn cmd.exe and directly spawns the command instead. However, Windows implicitly spawns cmd.exe when executing batch files, so the shell option is silently ignored when executing batch files. ↩︎
    禁用该 shell 选项后, child_process 模块不会生成, cmd.exe 而是直接生成命令。但是,Windows cmd.exe 在执行批处理文件时会隐式生成,因此在执行批处理文件时会以静默方式忽略该 shell 选项。↩︎

  6. Of course, you need to escape the caret itself. ↩︎
    当然,你需要逃避插入符号本身。↩︎

  7. When executing .\\test.bat arg1^ arg2arg1 and arg2 will be recognized as separate arguments. ↩︎
    执行时 .\\test.bat arg1^ arg2arg1arg2 将被识别为单独的参数。↩︎

  8. .\\test.bat "100^%" will be recognized as 100^% instead of 100%↩︎
    .\\test.bat "100^%" 将被识别为 100^% 而不是 100% .↩︎

  9. While the testing shows that this prevents the command injection, I’m still unsure if this escaping is the best way to prevent it, so if you are aware of a better way, please let me know. ↩︎
    虽然测试表明这可以防止命令注入,但我仍然不确定这种转义是否是防止它的最佳方法,所以如果您知道更好的方法,请告诉我。↩︎

原文始发于RyotaK:BatBadBut: You can’t securely execute commands on Windows

版权声明:admin 发表于 2024年4月11日 上午10:03。
转载请注明:BatBadBut: You can’t securely execute commands on Windows | CTF导航

相关文章