浅谈Log4j2在JFinal的检测

admin 2022年4月15日10:27:38浅谈Log4j2在JFinal的检测已关闭评论120 views字数 5206阅读17分21秒阅读模式

引言

  在前面的文章里简单的探讨了Spring框架中如何检测Log4j2漏洞。传送门https://sec-in.com/article/1431

  JFinal 是基于Java 语言的极速 web 开发框架,大量的web应用是基于其进行构建的。同样的JFinal也有自己的日志实现机制。这里尝试找到一种稳定的触发方式来方便log4j2漏洞的排查/运营。

浅谈Log4j2在JFinal的检测

JFinal日志相关

默认使用的日志框架

  JFinal使用的是封装了一层的日志框架,可以兼容其余所有日志框架。主要是接口ILogFactory和一个抽象类Log,JFinal源代码中使用的日志工具就是Log的子类,包含:JdkLog,Log4jLog,Slf4jLog:

浅谈Log4j2在JFinal的检测

以log4j为例,在Log4jLog类中对log4j对应的方法进行了封装:

浅谈Log4j2在JFinal的检测

并且JFinal默认使用的是log4j记录日志,如果引入的依赖中没有log4j的话会使用jdk-log:

```java
public abstract class Log {

private static ILogFactory defaultLogFactory = null;

static {
init();
}

static void init() {
if (defaultLogFactory == null) {
try {
Class.forName("org.apache.log4j.Logger");
Class<?> log4jLogFactoryClass = Class.forName("com.jfinal.log.Log4jLogFactory");
defaultLogFactory = (ILogFactory)log4jLogFactoryClass.newInstance(); // return new Log4jLogFactory();
} catch (Exception e) {
defaultLogFactory = new JdkLogFactory();
}
}
}
```

也就是说默认情况下是不受Apache Log4j2 漏洞影响的

在JFinal中使用log4j2

但是log4j 1.x版本已经不再维护了,并且Apache Log4j <= 1.2.17版本受到CVE-2019-17571 影响,所以不排除有使用log4j2的情况。在JFinal中使用log4j2也很简单,只需要模仿Log4jLog类对其进行封装就可以了。

以maven项目为例,首先引入log4j2的相关依赖。然后继承com.jfinal.log.Log类,对Log4j2的相关方法进行封装:

```java
import com.jfinal.log.Log;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j2Log extends Log {
private Logger log;

public Log4j2Log(Class<?> clazz) {
    log = LogManager.getLogger(clazz);
}

public Log4j2Log(String name) {
    log = LogManager.getLogger(name);

}

@Override
public void debug(String message) {
    log.debug(message);
}

@Override
public void debug(String message, Throwable t) {
    log.debug(message, t);
}

@Override
public void info(String message) {
    log.info(message);
}

@Override
public void info(String message, Throwable t) {
    log.info(message, t);
}

@Override
public void warn(String message) {
    log.warn(message);
}

@Override
public void warn(String message, Throwable t) {
    log.warn(message, t);
}

@Override
public void error(String message) {
    log.error(message);
}

@Override
public void error(String message, Throwable t) {
    log.error(message, t);
}

@Override
public void fatal(String message) {
    log.fatal(message);
}

@Override
public void fatal(String message, Throwable t) {
    log.fatal(message, t);
}

@Override
public boolean isDebugEnabled() {
    return false;
}

@Override
public boolean isInfoEnabled() {
    return false;
}

@Override
public boolean isWarnEnabled() {
    return true;
}

@Override
public boolean isErrorEnabled() {
    return true;
}

@Override
public boolean isFatalEnabled() {
    return false;
}

}
```

然后实现com.jfinal.log.ILogFactory接口:

```java
public class Log4j2Factory implements ILogFactory{

@Override
public Log getLog(Class<?> clazz) {
    // TODO Auto-generated method stub
    return new Log4j2Log(clazz);
}

@Override
public Log getLog(String name) {
    // TODO Auto-generated method stub
    return new Log4j2Log(name);
}

}
```

最后在JFinalConfig中添加log4j2的配置就完成了:

java
/**
* 配置常量
*/
@Override
public void configConstant(Constants me) {
me.setLogFactory(new Log4j2Factory());
}

分析验证

引入了漏洞版本的log4j2依赖的话,自然会受到影响,同样的如果想找到一个稳定触发验证的point。思路之一是可以寻找打印日志的地方。

以JFinal4.5为例,因为本质上的实现其实是封装了一个org.apache.logging.log4j.Logger,然后调用log4j2对应的方法。没有spring那么复杂。只需要找到用户可控且调用了com.jfinal.log中对应的日志方法的class就可以了。下面记录具体的过程:

JFinal中定义了一个过滤器JFinalFilter,它是整个框架的入口。在其doFilter方法里可以看到对请求进行了相应的处理:

浅谈Log4j2在JFinal的检测

handler.handle(target, request, response, isHandled); 是整个Filter最核心的方法,通过JFinalFilter的init方法进行获取:

```Java
public void init(FilterConfig filterConfig) throws ServletException {
if (jfinalConfig == null) {
createJFinalConfig(filterConfig.getInitParameter("configClass"));
}

jfinal.init(jfinalConfig, filterConfig.getServletContext());

String contextPath = filterConfig.getServletContext().getContextPath();
contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());

constants = Config.getConstants();
encoding = constants.getEncoding();

jfinalConfig.onStart();
jfinalConfig.afterJFinalStart();

handler = jfinal.getHandler(); // 开始接受请求
}
```

进一步查看其实是通过jfinal类来获取的,返回的是一个actionHandler为首handler chain。

```Java
private void initHandler() {
ActionHandler actionHandler = Config.getHandlers().getActionHandler();
if (actionHandler == null) {
actionHandler = new ActionHandler();
}

actionHandler.init(actionMapping, constants);
handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler);
}
```

也就是说handler.handle(target, request, response, isHandled); 实际上会调用ActionHandler的handle方法,这里找到了其中一个log4j2漏洞的触发点:

浅谈Log4j2在JFinal的检测

ActionHandler实际上主要用于处理路由,这里想要触发对应的逻辑只需要访问不存在的Action 就可以了。验证下实际的猜想。

直接在url path中访问相关的poc,发现并没有触发:

浅谈Log4j2在JFinal的检测

原因主要是handle方法在处理Action的时候,如果请求的path中包含.的话(poc里恶意的jndi地址里的域名/ip会使用.进行分隔),会直接return,并没有处理日志逻辑:

java
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
if (target.indexOf('.') != -1) {
return ;
}
......

这里可以考虑将jndi地址里的ip转换成数字的形式进行访问,就可以避免上述的问题了。例如127.0.0.1->2130706433:

浅谈Log4j2在JFinal的检测

但是验证后并没有调用log.warn方法,控制台里也没有对应的日志输出:

浅谈Log4j2在JFinal的检测

这里主要跟日志级别有关系。根据上面的分析可以知道,调用的是log.warn(),在触发的位置下断点调试:

浅谈Log4j2在JFinal的检测

首先是log4j2的logIfEnabled方法:

浅谈Log4j2在JFinal的检测

熟悉log4j2漏洞调用链的话应该知道logMessage后具体会调用jndilookup的逻辑,那么问题应该是在isEnabled方法这里了:

浅谈Log4j2在JFinal的检测

跟进isEnabled方法:

浅谈Log4j2在JFinal的检测

继续跟进,在return方法可以看到,这里首选判断日志等级是否为空,同时当前等级要大于等级设置的等级:

```java
boolean filter(Level level, Marker marker, String msg, Throwable t) {
Filter filter = this.config.getFilter();
if (filter != null) {
Result r = filter.filter(this.logger, level, marker, msg, t);
if (r != Result.NEUTRAL) {
return r == Result.ACCEPT;
}
}

return level != null && this.intLevel >= level.intLevel();

}
```

从调试信息可以看到,当前的等级是200,但是warn的等级是300,不符合判断逻辑,所以并没有调用对应的逻辑(Spring Boot默认的日志级别为INFO,所以不存在类似的问题):

浅谈Log4j2在JFinal的检测

log4j文档里有对应的说明,200对应的是error,也就是error和fatal方法都是可以触发的,但是在jfinal中没找到error和fatal比较直观的触发点:

浅谈Log4j2在JFinal的检测

再次印证猜想,修改对应的文件配置日志的等级。同样的请求之前的poc:

浅谈Log4j2在JFinal的检测

控制台也输出了对应的日志信息:

浅谈Log4j2在JFinal的检测

这里以本地执行计算器的方式验证效果,同样是成功触发的:

浅谈Log4j2在JFinal的检测

浅谈Log4j2在JFinal的检测

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月15日10:27:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅谈Log4j2在JFinal的检测https://cn-sec.com/archives/913061.html