目录导航
有趣的行为 ?我在cmd.exe中确定了一项为数不多的间歇性研究,这些研究是为了追求一些新的命令执行注入攻击媒介而进行的间歇性研究(时不时的)。
所以我主要是想:
- 在某些命令检查/清除代码与程序的其余部分之间找到编码不匹配,从而允许在宽字符的第二个字节中走私现有命令分隔符的ASCII版本(有一段时间我相信我在
StripQuotes
函数中拥有它-我错了( \ (ツ) /¯), - 发现unix shell的反引号运算符的一些隐藏cmd.exe对应项,
- 找到一个命令分隔符替代|,&和\ n -这早就产生了一个有趣的和还活着,但很少出现漏洞的发现- https://vuldb.com/?id.93602。
我最终发现遍历路径时遇到命令/参数混乱…或者这是什么他妈的什么鬼
对于懒惰而又没有耐心阅读整本书的人来说,这是魔术:

在Windows 10 Pro x64(Microsoft Windows [版本10.0.18363.836])上进行测试,cmd.exe版本:10.0.18362.449(SHA256:FF79D3C4A0B7EB191783C323AB8363EBD1FD10BE58D8BCC96B0706774343CA81D5)。
但也应该适用于早期版本…可能适用于所有版本。
一些背景
让我们考虑以下命令行:
cmd.exe /c "ping 127.0.0.1"
而127.0.0.1
参数是由用户在运行外部命令的应用程序中控制的(在此示例中为ping)。
这种精确的语法(命令前面带有/c
开关并用双引号引起来)是外部程序使用cmd.exe执行系统命令(例如PHP shell_exec()
函数及其变体)的默认方式。
现在,用户可以通过提供像这样的参数来欺骗cmd.exe来运行calc.exe而不是ping.exe,
从而127.0.0.1/../../../../../../../../../../windows/system32/calc.exe
遍历他们选择的可执行文件的路径,该cmd.exe将代替ping.exe二进制文件运行。
因此完整的命令行变为:
cmd.exe /c "ping 127.0.0.1/../../../../../../../../../../windows/system32/calc.exe"
这样做的潜在影响包括拒绝服务[ddos],数据泄露,任意代码执行(取决于目标应用程序和系统)。
尽管我非常确定还有其他一些OS命令执行场景,但是命令行的一部分来自与最终命令执行不同的安全上下文(也许有某些服务?我还没有自己搜索过)-无论如何,让我们以网络应用程序为例。
考虑以下示例PHP代码:

由于使用了escapeshellcmd()
,因此不易受到已知命令注入向量的影响(参数注入除外),但是情况略有不同,并且不允许RCE带有ping.exe支持的参数列表-找不到诸如find ‘之类的内置执行参数。 s -exec)。
而且我知道,我知道,有些人会指出,在这种情况下,escapeshellarg()
应该使用它代替-是的,您是对的,尤其是因为将参数放在引号中实际上会阻止这种行为,例如在这种情况下,cmd.exe正确识别要运行的命令(ping.exe)。当参数用单引号/双引号引起来时,此技巧不起作用。
无论如何- 非常常见的是使用escapeshellcmd()而不是escapeshellarg()。请注意,虽然在找到并注册了CVE-2020-12669之后,CVE-2020-12742和CVE-2020-12743却花了一周多的时间针对更多开源项目运行自动源代码分析扫描,并手动跟踪结果-我以前用PHP制作的邪恶SCA工具。同样,这就是让我很快又厌烦PHP的原因,迫使我回到cmd.exe只是让我最终发现了这篇博客文章的主要内容。
我相当确定有一些应用程序容易受到此攻击(执行OS命令注入完整性检查,但未能阻止路径遍历并将参数括在引号中)。尚未搜索,因为我太懒/忙。
同样,其他命令解释器中类似行为的概念也很有趣。
扩展的POC
正常使用:

滥用:

现在,这是Sysmon日志(进程创建事件)中的正常用法:

因此,基本上使用命令行创建子进程(ping.exe),该命令行等于双引号之间的值,并在双引号之前加上/c
从父进程(cmd.exe)命令行进行的切换。
现在,与上述ipconfig.exe劫持相同:

事实证明,我们不仅限于位于中存在的目录中的可执行文件 %PATH%
。我们可以遍历同一磁盘上的任何位置。
另外,我们不仅限于EXE扩展名,也不限于%PATHEXT%
变量中包含的“可执行”扩展名列表(默认情况下.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
– 默认情况下,这些扩展名是cmd.exe将尝试添加至命令名称的扩展名)提供扩展名,例如ping
使用时,而不是显式ping.exe
)。cmd.exe运行的东西与扩展名无关,这是我很久以前注意到的(https://twitter.com/julianpentest/status/1203386223227572224)。
还有一件事-可以在原始命令和走私的可执行路径之间添加更多其他参数。
让我们将所有这些结合起来。
出于演示目的,以下C程序已编译并链接到PE可执行文件中(它只是打印出自己的命令行):

将EXE复制到C:\xampp\tmp\cmd.png(将其作为恶意用户可以写入文件的任何位置的示例)。
行动:

因此,在一个本来就安全的PHP ping脚本中,我们仅有效地实现了等效的实际(执行,而不仅仅是读取)PE本地文件包含。
但是我不认为我们的选择就此结束。
无需链接文件上传/控制即可将其扩展为完整RCE的潜力
我敢肯定,即使没有完全/部分控制目标文件系统中的任何文件并在命令行本身中传递有效载荷的可能性,也可以将其转换为RCE ,从而创建一种多态的恶意命令行有效载荷。
运行目标可执行文件时,cmd.exe会将/c
切换后的命令行的整个部分传递给它。
例如:
cmd.exe /c "ping 127.0.0.1/../../../../../../../windows/system32/calc.exe"
使用命令行equals执行
ping 127.0.0.1/../../../../../../../windows/system32/calc.exe
.
并且,如扩展POC中所述,即使提供多个参数也可能劫持可执行文件,从而导致命令行如下:
ping THE PLACE FOR THE RCE PAYLOAD ARGS 127.0.0.1/../../path/to/lol.bin
这是lol.bin
将与之一起执行的命令行。找到足以容忍无效参数(因为我们作为攻击者无法完全控制它们)的代理执行LOLBin,可能会将其变成完整的RCE。
我们需要的LOLB是一个接受/忽略第一个参数(这是我们无法控制的硬编码命令,在我们的示例中为“ ping”),同时也愿意接受/忽略最后一个参数(即到其自身的遍历路径)。像https://lolbas-project.github.io/lolbas/Binaries/Ieexec/之类的东西,但实际上接受多个参数,而悄悄地忽略了不正确的参数。
另外,我还在考虑powershell。
运行:
cmd.exe /c "ping ;calc.exe; 127.0.0.1/../../../../../../../../../windows/system32/WindowsPowerShell/v1.0/POWERSHELL.EXE"
使Powershell从以下命令行开始
ping ;calc.exe 127.0.0.1/../../../../../../../../../../windows/system32/WindowsPowerShell/v1.0/POWERSHELL.EXE
我希望它会将命令行视为内联命令的字符串,并在运行ping.exe之后运行calc.exe。是的,我知道,这里使用分号将ping与calc分开-但是分号字符不是cmd.exe中的命令分隔符,而在powershell中(反之,几乎所有OS Command Injection过滤器都会阻止它,因为它们是在考虑多个平台的情况下通用编写的-显然导致分号是Unix shell中的命令分隔符)。
一个完美的受支持语法是某种简单的base64编码的代码注入,例如powershell -EncodedCommand
,它找到了一种使它即使在我们无法控制的字符串下也能正常工作的方法。无论如何,这种尝试导致Powershell以交互模式运行,而不是将命令行视为要执行的一系列内联命令。
无论如何,在这一点上,将其转变为RCE可以归结为研究特定LOLbins的行为,着重研究它们处理命令行的方式,而不是研究cmd.exe本身(尽管是的,我也考虑过自链接和滥用cmd.exe作为对此的LOLbin,希望利用它在解析命令行时(从命令行开始解析和不以/c
switch 开头)之间的一些细微差别来利用)。
绊倒和一些分析
我知道这看起来很愚蠢,以至于我建议我使用Burp在HTTP上使用示例代码夯实示例PHP代码时发现它,同时使用适当的过滤器观看Procmon或类似的东西(顺便说这不是一个坏主意)…而不是编写自定义cmd.exe模糊测试(不,您不需要告诉我我的代码离优雅很远,我不在乎),然后在获得相当无聊和令人失望的结果之后,花了数周时间在静态上使用Ghidra进行分析(感谢NSA,我确实爱上了此工具),随后又进行了数周的Ghidra进一步工作,同时使用x64dbg进行了手动调试,同时进一步扩展了Ghidra项目中的注释?
cmd.exe命令行处理始于该CheckSwitches
函数(该函数从中调用Init
,该函数本身从中调用main
)。CheckSwitches
负责确定哪些开关(如/c
,/k
,/v:on
等等)的cmd.exe被调用。选项的完整列表可以在cmd.exe /?
帮助中找到(顺便说一句,令我惊讶的是,它很好地反映了实际功能)。
我花了很多时间仔细分析它,寻找隐藏的开关,逻辑问题允许通过跳出双引号来通过命令行走私多个开关,引号剥离问题以及我挖出的其他任何对我而言很明显的东西在。


在我命名了一些命名版本和注释之后,CheckSwitches函数的开始
如果/c
检测到该开关,则处理将移至用双引号引起来的实际命令行-这是最常用的模式cmd.exe,其余唯一的写法是:

使用/r
开关可以达到相同的模式:

经过一些进一步的逻辑之后,除其他外,解析引用的字符串并进行一些健全的修复(例如,删除从其开头发现的空格,如果有的话),一个具有非常令人鼓舞且不言自明的名称的函数被调用:
拆卸视图:

反编译器视图:

在这一点上,很明显是时候进行调试了。
默认情况下,x64dbg将在入口点-设置断点mainCRTStartup
。
这是设置任意命令行的好机会:


然后再次启动cmd.exe(Debug-> Restart
)。
我们还在SearchForExecutable
函数顶部设置了一个断点,因此我们可以捕获其所有实例。
我们碰到的第一个实例SearchForExecutable
:

我们可以看到,cmd.exe /c
在RBX
和中保留了双引号的正确命令行(在cmd.exe跳过前面的引号之后)及其双引号R15
。另外,堆栈顶部(右下角)的值包含一个指向的地址CheckSwitches
-保存RET
。因此,我们知道此实例是从调用的CheckSwitches
。
如果我们打F9
一次,我们会碰到的第二个实例SearchForExecutable
,但此时在命令行字符串中举行RAX
,RDI
并R11
同时从另一个函数调用起源命名ECWork
:

第二个实例解析并返回ping.exe的完整路径。
在下面,我们可以看到ECWork
函数的主体,并带有SearchForExecutable
(标记为黑色)的调用。这是RIP
截图时的位置-在第二次调用之前SearchForExecutable
:

现在,在下面的屏幕快照中,SearchForExecutable
调用已经返回(请注意ping.exe的完整路径,地址指向R14
)。十五条指令之后ExecPgm
,使用新解析的可执行路径来创建新进程,从而调用该函数:

所以-看到SearchForExecutable
针对整个ping 127.0.0.1
字符串被调用(是的,那些邪恶的空格)表明完整的命令行和实际的文件名之间可能存在混淆…因此,这给了我最初的想法来检查可执行文件是否可能被字面劫持在等于命令行的名称下创建一个,使其运行:

真的吗 有趣。我决定与Procmon一起看一下,看一下cmd.exe试图用什么文件名打开CreateFile
:

因此,是的,结果确认打开了从实际ping .PNG
在当前工作目录中命名的文件的calc.exe副本:

现在,有趣的是,如果我没有先创建文件,那么使用该Procmon过滤器(Operation = CreateFile)将看不到任何结果…
人们可能会希望看到cmd.exe随意地CreateFile
对不存在的文件进行调用,而这些文件的名称是命令行的各种变体,并带有NAME NOT FOUND
结果-一种通常的方式来搜索潜在的DLL侧加载问题…但是在这种情况下不是-cmd.exe实际上是CreateFile
通过调用QueryDirectory
来检查该文件是否存在,然后调用:

为此,在Procmon中,更准确地基于在Path属性中PNG
发生的有效负载的唯一魔术字符串(例如,在这种情况下,因为这是我们作为攻击者可能控制的字符串)来指定过滤器,而不是基于在操作上。
“所以,无论如何,这不是很有用” -我以为回到了x64dbg。
“如果我们可以将一个非常狡猾的名字直接写入目标应用程序的当前目录中,我们就只能劫持该命令。” -我一直在想-“ …当前目录…您肯定只有当前目录吗?” -此时,我的遍历路径反射变亮了,这似乎是疯狂而绝望的想法,试图对解析的命令行部分进行遍历有效载荷SearchForExecutable
。
这使我手动将命令行更改为ping 127.0.0.1/../calc.exe
并重新启动调试…,同时已经在考虑修改cmd.exe模糊器,以便针对cmd.exe 使用psychoPATH抛出为此目的而生成的一组有效负载…但这从未发生,因为我又打F9
了一次之后看到的东西
在下面,我们可以看到带有cmd.exe的x64dbg是通过cmd.exe /c "ping 127.0.0.1/../calc.exe"
命令行运行的(请参阅参考资料RDI
)。我们在第二个SearchForExecutable
调用之后挂起,第二个调用是从ECWork
函数底部发出的。在调用前只需执行少量指令ExecPgm
,它将执行由指向的PE R14
。C:\Windows\System32\calc.exe
呈现的完整路径R14
是SearchForExecutable("ping 127.0.0.1/../calc.exe")
在当前值之前刚刚返回的调用的结果 RIP
:

遍历似乎是相对于当前工作目录的子目录(calc.exe在c:\windows\system32\calc.exe
):

“或者这可能只是路径遍历完整性检查失败的结果,只是删除了第一次出现的../
?” -我一直在想。
因此,我进一步研究了该SearchForExecutable
函数,还试图找到答案,为什么要考虑通过将参数按空格分割而创建的参数的变体,以及为什么在找到时首先选择最右边的参数。
我缩小的罪魁祸首码到内的指示SearchForExecutable
功能,通话之间mystrcspn
在14000ff64,然后号召FullPath
在功能14001005b和exists_ex
在140010414:

同时,我收到了Microsoft的以下反馈:
我们确实有一篇博客文章,可以帮助描述您记录的行为:https : //docs.microsoft.com/zh-cn/dotnet/standard/io/file-path-formats。
Cmd.exe首先尝试将整个字符串解释为路径:“ ping 127.0.0.1/../../../../../../../../../../ Windows / system32 / calc.exe”字符串被视为相对路径,因此“ ping 127.0.0.1”被解释为该路径中的一段,并且由于前面的“ ../”而被删除,这应该有助于解释为什么您不应使用用户控制的输入字符串将参数传递给可执行文件。
在很多情况下,都需要该行为,例如cmd.exe / c“ …. \ Program Files(x86)\ Internet Explorer \ iexplore.exe”,我们不想让它尝试运行某些程序“ …. \ Program”,参数为“ Files(x86)\ Internet Explorer \ iexplore.exe”。
只有在无法将完整字符串解析为有效路径的情况下,它才会在空格处进行分割,并将第一个空格之前的所有内容用作预期的可执行文件名称(因此,为什么“ ping 127.0.0.1”起作用)。
所以是的…那些邪恶的空间和引述。
从这一点出发,我仅通过确认遍历到任意目录的可能性以及强制执行具有任意扩展名的PE文件的能力来将问题升级。
有趣的是,这有点像常见的未引用服务路径问题,只是在这种情况下,优先选择最右边的版本。
披露
发现后,我记录了此特性并将其报告给了MSRC。在不到六天的时间里,该报告被提取并审查。大约一周后,Microsoft完成了评估,认为该评估不符合安全服务的要求.
雨苁:[把发现漏洞的人解决了就没有漏洞了?笑话 !]

一方面,我对Microsoft不会解决它感到失望,并且我有一段时间没有在cmd.exe中获得CVE了。
另一方面,至少没有什么阻止我共享它,并希望它会存在一段时间,以便我们可以使用它?这不是漏洞,这是一种技术?
我要感谢Microsoft使所有这些成为可能,并且非常友好,甚至可以为我提供这篇文章的评论!这是完全出乎意料的,但显然受到高度赞赏。
一些思考
研究资料有时似乎是一个孤独而无助的旅程,尤其是经过数天和数周看似徒劳的摸索和雕刻之后-但我意识到这只是一个短视的看法,而成功仅取决于发现的漏洞/特征的数量/有趣的行为(在这里?无须争论术语)。在进攻安全性中,即使那些失败的尝试同样重要,我们也很少关注尝试过和失败的事物-好像我们没有尝试一样,我们将永远不知道所发生的事情(并冒着误报的风险)。好奇心和需要知道的。软件充满惊喜。
另外,只需处理特定主题(例如分析给定的程序/协议/格式)并逐渐变得越来越熟悉,就会为我们带来新的思维模式,这使我们能够自动提出越来越多的潜在错误提示,场景和奇怪的行为,因为我们一直在黑客活动中。通过代码进行的旅程,伴随着新的灵感,被赋予新的知识,并通过回答问题而获得内心的平静……有时对一个独特的发现感到非常满意。