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 bad, but 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);
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
- Disable the automatic escaping that uses the backslash (
\
) provided by the runtime.
禁用使用运行时提供的反斜杠 (\
) 的自动转义。 - Apply the following steps to each argument:
对每个参数应用以下步骤:- Replace percent sign (
%
) with%%cd:~,%
.
将百分号 (%
) 替换为%%cd:~,%
。 - Replace the backslash (
\
) in front of the double quote ("
) with two backslashes (\\
).
将双引号 ("
) 前面的反斜杠 (\
) 替换为两个反斜杠 (\\
)。 - Replace the double quote (
"
) with two double quotes (""
).
将双引号 ("
) 替换为两个双引号 (""
)。 - Remove newline characters (
\n
).
删除换行符 (\n
)。 - Enclose the argument with double quotes (
"
).
用双引号 ( ) 将参数括起来"
。
- Replace percent sign (
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:用于确定应用程序是否受到影响的流程图
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 补丁可用 |
-
The advisory on CERT/CC will be available soon ↩︎
关于 CERT/CC 的公告即将↩发布 ︎ -
Please refer to the Appendix B for the status of the affected programming languages. ↩︎ ↩︎
请参阅附录 B,了解受影响编程语言的状态。↩︎ ↩︎ -
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,它还会影响其他编程语言。↩︎ -
In fact, many programming languages guarantee that the command arguments are escaped properly, and/or don’t use shell. ↩︎
事实上,许多编程语言都保证命令参数被正确转义,和/或不使用 shell。↩︎ -
When the
shell
option is disabled, thechild_process
module doesn’t spawncmd.exe
and directly spawns the command instead. However, Windows implicitly spawnscmd.exe
when executing batch files, so theshell
option is silently ignored when executing batch files. ↩︎
禁用该shell
选项后,child_process
模块不会生成,cmd.exe
而是直接生成命令。但是,Windowscmd.exe
在执行批处理文件时会隐式生成,因此在执行批处理文件时会以静默方式忽略该shell
选项。↩︎ -
Of course, you need to escape the caret itself. ↩︎
当然,你需要逃避插入符号本身。↩︎ -
When executing
.\\test.bat arg1^ arg2
,arg1
andarg2
will be recognized as separate arguments. ↩︎
执行时.\\test.bat arg1^ arg2
,arg1
和arg2
将被识别为单独的参数。↩︎ -
.\\test.bat "100^%"
will be recognized as100^%
instead of100%
. ↩︎
.\\test.bat "100^%"
将被识别为100^%
而不是100%
.↩︎ -
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. ↩︎
虽然测试表明这可以防止命令注入,但我仍然不确定这种转义是否是防止它的最佳方法,所以如果您知道更好的方法,请告诉我。↩︎