vlambda博客
学习文章列表

原创 | 浅谈Log4j2在JFinal的检测


原创 | 浅谈Log4j2在JFinal的检测

前言

原创 | 浅谈Log4j2在JFinal的检测
前面的文章里简单的探讨了Spring框架中如何检测Log4j2漏洞(https://sec-in.com/article/1431)。


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

原创 | 浅谈Log4j2在JFinal的检测


原创 | 浅谈Log4j2在JFinal的检测

JFinal日志相关

原创 | 浅谈Log4j2在JFinal的检测

默认使用的日志框架

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

JdkLog,Log4jLog,Slf4jLog:

原创 | 浅谈Log4j2在JFinal的检测

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

原创 | 浅谈Log4j2在JFinal的检测

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

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的相关方法进行封装:

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接口:

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的配置就完成了:

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


原创 | 浅谈Log4j2在JFinal的检测

分析验证

原创 | 浅谈Log4j2在JFinal的检测


引入了漏洞版本的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方法进行获取:

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。

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的检测

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

原创 | 浅谈Log4j2在JFinal的检测

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

原创 | 浅谈Log4j2在JFinal的检测

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

原创 | 浅谈Log4j2在JFinal的检测

首先是log4j2的logIfEnabled方法:

原创 | 浅谈Log4j2在JFinal的检测

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

原创 | 浅谈Log4j2在JFinal的检测

跟进isEnabled方法:

原创 | 浅谈Log4j2在JFinal的检测

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

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的检测


相关推荐







你要的分享、在看与点赞都在这儿~