[备份]浅谈Log4j2不借助dnslog的检测

0x00 介绍

目前的Log4j2检测都需要借助dnslog平台,是否存在不借助dnslog的检测方式呢

也许在甲方内网自查等情景下有很好的效果

笔者实习期间参与过xray的一些开发,对其中的反连平台有一些了解。正好天下大木头师傅找到我,提出了它同样的思路,于是我们交流后编写了一款工具,目前功能简单,后续可能会加强

主要原理是参考LDAPRMI协议文档,编写解析协议的代码,获取我们需要的数据,保存即可

所以本文主要就是分析该工具的介绍和编写思路,首先来看看效果

运行工具:./Log4j2Scan.exe -p 8000

img

由于我在本地测试,所以ip地址为127.0.0.1

使用RMI触发漏洞(RMI方式的Payload必须有Path否则不会发请求)

使用LDAP触发漏洞

可以看到命令行的输出

img

我另外做了一个动态更新的web页面,每收到一个请求都会在页面中刷新

img

这是最初的版本,这两天我加入了一些新功能,可以从路径中带出参数,该功能有利于批量扫描等方式

(例如ldap://127.0.0.1:1389/4ra1n会收集到4ra1n

img

后来木头师傅又做了Burpsuite插件的适配(由于一些原因木头师傅删除了这些功能)

img

0x01 LDAP

无论是LDAP还是RMI协议情况下的漏洞触发,总是需要发请求的,于是我们将这些请求抓包分析

搭建正常的LDAP Server并监听lookback网卡并设置端口为tcp:1389

img

无需关心前三步,这三步是TCP的握手,并不包含真正的数据,从PSH+ASK这一条数据来看

首先是漏洞触发端(客户端)向LDAP服务端发了300c020101600702010304008000这样的一串数据

img

经过多次不同操作系统下的测试,确认这应该是LDAP协议的指纹,正常情况下客户端都会向服务端首先发送这样一个字符串,为了进一步确认,我尝试到googlegithub进行搜索

Github类似代码 中发现该字符串被很多脚本作为LDAP协议的探测指纹信息,在 官方文档 中确认了为什么是这样的字符串

于是我们用Golang编写了类似的逻辑,构造了一个虚假的LDAP Server分析来自漏洞触发端的TCP连接

监听Socket

根据上述指纹进行分析

到这一步只能确定是LDAP协议还拿不到传过来的参数(ldap://127.0.0.1:1389/4ra1n中的4ra1n

于是继续查看官方文档,构造标准的返回包

按照标准返回之后,会再次从客户端得到输入

不过这个包并不能匹配到LDAP官方文档中任意一种协议(也许是我没找到)

通过大量请求做diff后发现这里新输入的规律

按照这个规则编写,即可取到其中的参数

 

0x02 RMI

RMI的分析过程大致分为5步,我将和大家逐个介绍

(1)Client -> Server

接下来分析RMI的情况

同样的方式抓包看到4a524d4900024b的指纹,由漏洞触发端(客户端)发向RMI服务端

不过RMI协议的开头并不这么简单,不一定是一个固定的字符串

Oracle官网看到了这样的描述:RMI协议分为请求头Header和消息Message部分,上文的字符串是Header相关的内容,该TCP连接后续会进行Message的传输

关于Header的解释如下:0x4a 0x52 0x4d 0x49为固定字节(转成字符串是JRMI

后面两个字节分别表示VersionProtocol信息,按照RMI协议的规定,这里的Version应该是0x00 0x01,实际抓包看到的是0x00 0x02,或许是文档较老的原因?

末尾的0x4b表示这是StreamProtocol协议方式,没有什么问题

其实仔细看Wireshark的解析,和我做的分析一致

img

如果只为了确认RMI协议,那么到这里就可以了

但我们的目的是获取路径参数,在RMI协议中这一步尤其复杂

(2)Server -> Client

接下来应该是RMI服务端返回数据给漏洞触发端(客户端)

原始报文为

根据官方文档不难看出0x4e表示ProtocolAck且后续内容应该是具体返回的值

简单分析了下这里0x00 0x0f表示长度15,后15位DESKTOP-FP02BKH是服务端的主机名

最后的0xf8 0xfeRMI客户端的端口:63630

Wireshark中可以看到解析结果和分析一致

img

(3)Client -> Server

接下来客户端会向服务端发送如下的数据,报文如下

其中0b表示一个内网地址长度,正好是192.168.1.4,其余部分用00填充

于是想到这里的地址是否可以伪造

(4)Server -> Client

接下来服务端需要向客户端传一个空(至关重要)

(5)Client -> Server

下一步是客户端继续向服务端发送,报文以0x50开头,表示call操作

报文如下,开头的aced0005是经典序列化数据头,结尾的jlmz6v是我们需要的路径参数

现在问题来了,这是什么类的序列化数据

想办法对这个数据进行反序列化,发现报错

在尝试研究后,发现这个序列化数据类似String

发现字符串数据位于末尾,且之前有一个表示长度的字节,如这里06 6a 6c 6d 7a06表示jlmz6v长度为6

因此能否从后往前读,如果已读到的长度等于当前读到的字节代表的数字,那么认为已读到的字符串翻转后是路径参数

(这种手段也许会有误报,但由于字母的ASCII码数值很大,所以大概率不会出问题)

(6)实现

首先根据第一步判断是否为RMI协议

进一步获取路径参数比较麻烦

0x03 其他

最后分享一些简单的安全开发技术,对于想自己写安全工具师傅可能会有帮助

监听Socket收到的结果如何传递记录

构造一个非阻塞channel用于传输(给出默认长度就不阻塞了)

收到LDAPRMI请求后将数据输入channel

这时候其他的goroutine就可以取到channel中的结果

如何将结果传递给web页面

上面这个问题最后将结果放入了一个新的channel

在开启web服务的时候,建一个goroutine用于接收这个数据

如何做到web页面实时显示

上一个问题涉及到了互斥锁,正是为了解决这个问题

接收到请求会在HandlerServeHTTP中处理,上文中维护的全局列表在实时地添加最新扫描结果,如果这里直接取全局列表会出现并发问题,所以选用了互斥锁(也有其他的解决方案这种最简单)

如何让前端实时刷新:首先想到的是Ajax定时请求插入新的数据,实现起来麻烦

于是想到暴力办法,定时刷新页面

0x04 总结

最后我将项目名称从Log4j2Scan改为JNDIScan并加入了一些小功能

img

最后,该项目不仅可用于Log4j2的扫描,也可用于Fastjson等可能存在JDNI注入漏洞组件的扫描