cobaltstrike DOS漏洞(CVE-2021-36798)分析
LSA 漏洞分析 4996浏览 · 2021-09-18 08:52

0x00概述

202108,网上曝出cobaltstrike的DOS漏洞CVE-2021-36798,又名hotcobalt。
https://blog.cobaltstrike.com/2021/08/04/cobalt-strike-dos-vulnerability-cve-2021-36798/
原因是对截屏(或keylogger)功能的返回数据解析处理不当,可被攻击者控制截屏大小,使teamserver不断申请内存以致outofmemory,从而被DOS。

0x01 影响范围

cobaltstrike4.2/4.3

0x02 前置知识

先安装一些要用的东西
https://files.pythonhosted.org/packages/2c/52/c35ec79dd97a8ecf6b2bbd651df528abb47705def774a4a15b99977274e8/M2Crypto-0.38.0.tar.gz
sudo
cd M2Crypto-0.38.0
python setup.py install

pip install pycryptodome
pip3 install hexdump
pip3 install pycryptodome
//pip install M2Crypto
pip install typing
pip install --upgrade setuptools
pip install hexdump

sudo apt-get install jq
sudo apt-get install python-dev
//710 sudo apt-get install python-m2crypto
sudo apt-get install libssl-dev swig
sudo apt-get install ghex

stage和stageless/unstage/full staged

Beacon是Cobalt Strike运行在目标主机上的payload,Beacon在隐蔽信道上我们提供服务,用于长期控制受感染主机。它的工作方式与Metasploit Framework Payload类似。在实际渗透过程中,我们可以将其嵌入到可执行文件、添加到Word文档或者通过利用主机漏洞来传递Beacon

很多攻击框架都是使用分段的shellcode,以防止shellcode过长,覆盖到了上一函数栈帧的数据,导致引发异常。要说分段shellcode就不得不提stager,stager是一段很精短的代码,它可以连接下载真正的payload并将其注入内存。我们使用stager就可以解决shellcode过长的问题。

The payload stagers in Cobalt Strike do not authenticate the controller or verify the payload they download

Cobalt Strike 3.5.1后的版本可以通过在Malleable C2中添加host_stage选项,以限制分段payload

在Cobalt Strike 4中应该尽可能多的使用unstage
一方面以保证安全性(因为你无法确保stager下载的stage是否受到中间人攻击,除非像MSF一样使用SSL保证安全性)。另一方面如果我们通过层层的代理,在内网进行漫游,这个时候使用分段的payload如果网络传输出现问题,stage没有加载过去,可能就会错失一个Beacon,unstage的payload会让人放心不少

Stageless Beacon artifacts include: an executable, a service executable, DLLs, PowerShell, and a blob of shellcode that initializes and runs the Beacon payload.

payload staging : The first stage is called a stager. The stager is a very tiny program, often written in hand-optimized assembly, that: connects to Cobalt Strike, downloads the Beacon payload (also called the stage), and executes it.

//上述内容引用自:
https://cloud.tencent.com/developer/article/1595548
https://blog.cobaltstrike.com/2016/06/15/what-is-a-stageless-payload-artifact/
https://blog.cobaltstrike.com/2016/06/22/talk-to-your-children-about-payload-staging/

简单说:
stage就是分段下载payload,类似先小马(stager)后大马(stage/beacon-payload)的操作。
stageless就是包含了所有的payload,类似直接传大马。

beacon交互

.cobaltstrike.beacon_keys这个文件里有teamserver生成的公私钥,每一个beacon都内嵌了公钥。

beacon解析
python3 parse_beacon_config.py http://192.168.57.88/ --json --version 4 | zsh -c jq *

通过这个listener/host_stage就可以获取到beacon的设置信息,就可以伪造beacon/session上线。
在目标受害机器运行cs生成的后门时,会向c2发出一个get的checksum8格式的请求下载剩余的payload(shellcode分段,另一种是stageless)

beacon在目标受害机器运行时会通过http get向cs的c2传回(元数据metadata)目标机器的一些信息如cpu,ip,AES密钥等(用该rsa公钥加密),姑且称为beacon注册。
之后攻击者就可以和beacon交互了,通常是用http get接收指令,用http post返回信息。这些任务用了之前beacon注册请求发送的AES key加密。

先获取公私钥:
https://gist.github.com/olliencc/af056560e943bafa145120103a0947a3#file-dump-java
javac -cp "cobaltstrike.jar" DumpKeys.java
java -cp ".:./cobaltstrike.jar" DumpKeys

抓包:

用rsa私钥解密的数据:

可以看到部分信息,更具体的解析要逆出cs的通信格式。
//beacon会不停发这个get请求获取teamserver的任务信息

解密beacon的metadata:
https://github.com/WBGlIl/CS_Decrypt/blob/main/Beacon_metadata_RSA_Decrypt.py

AES key:1f6a1085fc9544467b78546215e97282
HMAC key:6b7712a4ab24b25e9347ab82fc073438

解密teamserver->beacon的任务:
通过wireshark抓包保存数据包为bin文件,这是个文件浏览的任务

再利用ghex提取body数据,这是一个下载phpinfo.php文件的任务。

解密之:
https://github.com/WBGlIl/CS_Decrypt/blob/main/CS_Task_AES_Decrypt.py

该请求对应的beacon响应解密(beacon->teamserver):
https://github.com/WBGlIl/CS_Decrypt/blob/main/Beacon_Task_return_AES_Decrypt.py

同理解密一个文件浏览请求的beacon响应:

0x03 漏洞重现

https://github.com/Sentinel-One/CobaltStrikeParser/blob/master/extra/communication_poc.py
用这个poc可以注册一个假beacon

exp:
https://github.com/JamVayne/CobaltStrikeDos
python3 CobaltStrikeDos.py http://192.168.57.88/

成功DOS!

0x04 EXP流量分析

先请求checksum8 url

接着发出大量beacon注册请求

再发出大量beacon响应的截屏任务

尝试解密一个metadata
Rhm3p3/UZFiJbU0fOjQKPHcD3AoYy7OyB8p+3Py7l6WDazCa/fl/V4gCsdIhKYAT4XN60kNzUndaDbCflqqmsSjJts0rK70SjRGRdfTkBhNOASLVR34/+Cy3SuiO0CPL30yhHBDBAKRNZVo+inA3Qjvtyvu9emD9hVWwy7gkwlU=

用这个exp要注释掉process变量。
找到有96m截屏的一个流量:

再通过beaconid找到对应的submit.php

0x05 漏洞分析

decompiled-src/beacon/BeaconC2.java

public void process_beacon_callback_decrypted(final String s, final byte[] array) {
        int int1 = -1;
        if (array.length == 0) {
            return;
        }
        if (!AssertUtils.TestIsNumber(s)) {
            return;
        }
        if (!AssertUtils.TestNotNull(this.getCheckinListener().resolve(s + ""), "process output for beacon session")) {
            return;
        }
        try {
            final DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(array));
            int1 = dataInputStream.readInt();
            if (int1 == 0) {
                final String process = this.getCharsets().process(s, CommonUtils.readAll(dataInputStream));
                this.getCheckinListener().output(BeaconOutput.Output(s, "received output:\n" + process));
                this.runParsers(process, s, int1);
            }

            ......

            else if (int1 == 3) {
                final DataParser dataParser2 = new DataParser(CommonUtils.readAll(dataInputStream));
                dataParser2.little();
                final byte[] countedBytes = dataParser2.readCountedBytes();   //取头4个字节整数作为截图大小
                final int int3 = dataParser2.readInt();
                final String process5 = this.getCharsets().process(s, dataParser2.readCountedBytes());
                final String process6 = this.getCharsets().process(s, dataParser2.readCountedBytes());
                if (countedBytes.length == 0) {
                    this.getCheckinListener().output(BeaconOutput.Error(s, "screenshot from desktop " + int3 + " is empty"));
                    return;
                }
                final BeaconEntry resolve2 = this.getCheckinListener().resolve(s + "");
                if (resolve2 == null) {
                    return;
                }
                final Screenshot screenshot = new Screenshot(s, countedBytes, process6, resolve2.getComputer(), int3, process5);
                this.getCheckinListener().screenshot(screenshot);
                if (process5.length() > 0) {
                    this.getCheckinListener().output(BeaconOutput.OutputB(s, "received screenshot of " + process5 + " from " + process6 + " (" + CommonUtils.formatSize(countedBytes.length) + ")"));
                    this.getResources().archive(BeaconOutput.Activity(s, "screenshot of " + process5 + " from " + process6));
                }
                else {
                    this.getCheckinListener().output(BeaconOutput.OutputB(s, "received screenshot from " + process6 + " (" + CommonUtils.formatSize(countedBytes.length) + ")"));
                    this.getResources().archive(BeaconOutput.Activity(s, "screenshot from " + process6));
                }
                this.getResources().process(new ScreenshotEvent(screenshot));
            }

decompiled-src/common/DataParser.java

public byte[] readCountedBytes() throws IOException {
        final int int1 = this.readInt();   //取4个字节整数
        if (int1 > 0) {
            return this.readBytes(int1);   //返回这4个字节整数的大小
        }
        return new byte[0];
}

接着会分配一个足够大的缓冲区来读取截屏数据。

0x06 修复方案

1.更新至4.4版本。
/
2.host_stage=false
防止beacon设置信息被获取从而防御被dos。
/
3.只允许可信的源访问teamserver。
/
4.添加防御代码:(未测试是否可行)
在用
https://github.com/Sentinel-One/CobaltStrikeParser/blob/master/extra/communication_poc.py

https://github.com/M-Kings/CVE-2021-36798/blob/main/CVE-2021-36798.py
这两个poc测试时候dos不成功。
发现teamserver会出现
Dropped responses from session 111 [type: 222] (no interaction with this session yet)

搜索源代码:
decompiled-src/beacon/BeaconC2.java

else if (int1 == 29) {
                this.parts.put(s, CommonUtils.readAll(dataInputStream));
                if (this.parts.isReady(s)) {
                    this.process_beacon_callback_decrypted(s, this.parts.data(s));
                }
            }
            else {
                if (this.data.isNewSession(s)) {
                    this.getCheckinListener().output(BeaconOutput.Error(s, "Dropped responses from session. Didn't expect " + int1 + " prior to first task."));
                    CommonUtils.print_error("Dropped responses from session " + s + " [type: " + int1 + "] (no interaction with this session yet)");
                    return;
                }

这个好像是对cobaltstrike3.5的rce的修复方案之一,判断beacon和c2要有至少一次交互,而cobaltstrikeparser中parse_beacon_config这个库的poc是没有交互直接发payload的。
//from parse_beacon_config import cobaltstrikeConfig
所以这两个poc不成功应该就是利用了parse_beacon_config库从而被这段代码防住了。
所以或许可以用这个代码来防御利用了parse_beacon_config的poc,复制代码到截屏和keylogger的if分支即可
else if (int1 == 3) //截图功能

else if (int1 == 1) { //keylogger

0x07 参考资料

https://www.sentinelone.com/labs/hotcobalt-new-cobalt-strike-dos-vulnerability-that-lets-you-halt-operations/
https://f5.pm/go-81984.html
https://hub.fastgit.org/Sentinel-One/CobaltStrikeParser
https://hub.fastgit.org/JamVayne/CobaltStrikeDos
https://www.cobaltstrike.com/help-malleable-c2

https://blog.cobaltstrike.com/2016/06/15/what-is-a-stageless-payload-artifact/
https://blog.cobaltstrike.com/2013/06/28/staged-payloads-what-pen-testers-should-know/
https://blog.cobaltstrike.com/2016/06/22/talk-to-your-children-about-payload-staging/

https://wbglil.gitbook.io/cobalt-strike/cobalt-strike-yuan-li-jie-shao/cs-mu-biao-shang-xian-guo-cheng
https://github.com/WBGlIl/CS_Decrypt

0 条评论
某人
表情
可输入 255