介绍
目前的Log4j2
检测都需要借助dnslog
平台,是否存在不借助dnslog
的检测方式呢
也许在甲方内网自查等情景下有很好的效果
笔者实习期间参与过xray
的一些开发,对其中的反连平台有一些了解。正好天下大木头师傅找到我,提出了它同样的思路,于是我们交流后编写了一款工具,目前功能简单,后续可能会加强
主要原理是参考LDAP
和RMI
协议文档,编写解析协议的代码,获取我们需要的数据,保存即可
所以本文主要就是分析该工具的介绍和编写思路,首先来看看效果
运行工具:./Log4j2Scan.exe -p 8000
由于我在本地测试,所以ip地址为127.0.0.1
使用RMI
触发漏洞(RMI
方式的Payload
必须有Path
否则不会发请求)
public static void main(String[] args) { logger.error("${jndi:rmi://127.0.0.1:8000/xxx}"); }
使用LDAP
触发漏洞
public static void main(String[] args) { logger.error("${jndi:ldap://127.0.0.1:8000}"); }
可以看到命令行的输出
我另外做了一个动态更新的web
页面,每收到一个请求都会在页面中刷新
这是最初的版本,这两天我加入了一些新功能,可以从路径中带出参数,该功能有利于批量扫描等方式
(例如ldap://127.0.0.1:1389/4ra1n
会收集到4ra1n
)
后来木头师傅又做了Burpsuite
插件的适配(由于一些原因木头师傅删除了这些功能)
LDAP
无论是LDAP
还是RMI
协议情况下的漏洞触发,总是需要发请求的,于是我们将这些请求抓包分析
搭建正常的LDAP Server
并监听lookback
网卡并设置端口为tcp:1389
无需关心前三步,这三步是TCP
的握手,并不包含真正的数据,从PSH+ASK
这一条数据来看
首先是漏洞触发端(客户端)向LDAP
服务端发了300c020101600702010304008000
这样的一串数据
经过多次不同操作系统下的测试,确认这应该是LDAP
协议的指纹,正常情况下客户端都会向服务端首先发送这样一个字符串,为了进一步确认,我尝试到google
和github
进行搜索
在 Github类似代码 中发现该字符串被很多脚本作为LDAP
协议的探测指纹信息,在 官方文档 中确认了为什么是这样的字符串
30 0c -- Begin the LDAPMessage sequence 02 01 01 -- The message ID (integer value 1) 60 07 -- Begin the bind request protocol op 02 01 03 -- The LDAP protocol version (integer value 3) 04 00 -- Empty bind DN (0-byte octet string) 80 00 -- Empty password (0-byte octet string with type context-specific -- primitive zero)
于是我们用Golang
编写了类似的逻辑,构造了一个虚假的LDAP Server
分析来自漏洞触发端的TCP
连接
监听Socket
log.Info("start fake reverse server") listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", config.Port)) ... for { conn, err := listen.Accept() ... // 分析 go acceptProcess(&conn) }
根据上述指纹进行分析
func acceptProcess(conn *net.Conn) { buf := make([]byte, 1024) num, err := (*conn).Read(buf) ... hexStr := fmt.Sprintf("%x", buf[:num]) // LDAP 指纹 if "300c020101600702010304008000" == hexStr { // 如果符合则记录该请求 res := &model.Result{ Host: (*conn).RemoteAddr().String(), Name: "LDAP", Finger: hexStr, } } }
到这一步只能确定是LDAP
协议还拿不到传过来的参数(ldap://127.0.0.1:1389/4ra1n
中的4ra1n
)
于是继续查看官方文档,构造标准的返回包
30 0c -- Begin the LDAPMessage sequence 02 01 01 -- The message ID (integer value 1) 61 07 -- Begin the bind response protocol op 0a 01 00 -- success result code (enumerated value 0) 04 00 -- No matched DN (0-byte octet string) 04 00 -- No diagnostic message (0-byte octet string)
按照标准返回之后,会再次从客户端得到输入
不过这个包并不能匹配到LDAP
官方文档中任意一种协议(也许是我没找到)
通过大量请求做diff
后发现这里新输入的规律
- 输入前7位是固定的
- 输入的第8位代表路径的长度n(例如
4ra1n
长度为05
) - 从第9位到第9+n位是对应的路径参数
按照这个规则编写,即可取到其中的参数
if "300c020101600702010304008000" == hexStr { data := []byte{ 0x30, 0x0c, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0a, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, } _, _ = (*conn).Write(data) _, _ = (*conn).Read(buf) length := buf[8] pathBytes := bytes.Buffer{} for i := 1; i <= int(length); i++ { temp := []byte{buf[8+i]} pathBytes.Write(temp) } // 得到path path := pathBytes.String() ... _ = (*conn).Close() return }
RMI
RMI
的分析过程大致分为5步,我将和大家逐个介绍
(1)Client -> Server
接下来分析RMI
的情况
同样的方式抓包看到4a524d4900024b
的指纹,由漏洞触发端(客户端)发向RMI
服务端
不过RMI
协议的开头并不这么简单,不一定是一个固定的字符串
在Oracle
官网看到了这样的描述:RMI
协议分为请求头Header
和消息Message
部分,上文的字符串是Header
相关的内容,该TCP
连接后续会进行Message
的传输
关于Header
的解释如下:0x4a 0x52 0x4d 0x49
为固定字节(转成字符串是JRMI
)
后面两个字节分别表示Version
和Protocol
信息,按照RMI
协议的规定,这里的Version
应该是0x00 0x01
,实际抓包看到的是0x00 0x02
,或许是文档较老的原因?
末尾的0x4b
表示这是StreamProtocol
协议方式,没有什么问题
Header: 0x4a 0x52 0x4d 0x49 Version Protocol Version: 0x00 0x01 Protocol: StreamProtocol SingleOpProtocol MultiplexProtocol StreamProtocol: 0x4b SingleOpProtocol: 0x4c MultiplexProtocol: 0x4d
其实仔细看Wireshark
的解析,和我做的分析一致
如果只为了确认RMI
协议,那么到这里就可以了
但我们的目的是获取路径参数,在RMI
协议中这一步尤其复杂
(2)Server -> Client
接下来应该是RMI
服务端返回数据给漏洞触发端(客户端)
原始报文为
0000 4e 00 0f 44 45 53 4b 54 4f 50 2d 46 50 30 32 42 N..DESKTOP-FP02B 0010 4b 48 00 00 f8 8e KH....
根据官方文档不难看出0x4e
表示ProtocolAck
且后续内容应该是具体返回的值
In: ProtocolAck Returns ProtocolAck: 0x4e
简单分析了下这里0x00 0x0f
表示长度15
,后15位DESKTOP-FP02BKH
是服务端的主机名
最后的0xf8 0xfe
是RMI
客户端的端口:63630
在Wireshark
中可以看到解析结果和分析一致