原创 | 浅谈Log4j2在JFinal的检测
2023-7-9 00:1:41 Author: 白帽子(查看原文) 阅读量:31 收藏

点击上方蓝字 关注我吧

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

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

默认使用的日志框架

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

JdkLog,Log4jLog,Slf4jLog:

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

并且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依赖的话,自然会受到影响,同样的如果想找到一个稳定触发验证的point。思路之一是可以寻找打印日志的地方。

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

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

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漏洞的触发点:

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

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

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

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

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

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

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

首先是log4j2的logIfEnabled方法:

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

跟进isEnabled方法:

继续跟进,在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,所以不存在类似的问题):

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

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

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

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

相关推荐

原创 | 数据库-MongoDB漏洞利用姿势

原创 | 浅谈Log4j2在Springboot的检测

原创 | 永恒之蓝漏洞分析

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

文章来源: http://mp.weixin.qq.com/s?__biz=MzAwMDQwNTE5MA==&mid=2650246821&idx=1&sn=8bad182364b60d7a6767eb23415908c2&chksm=82ea550cb59ddc1a8c44f2b5832a82b1641986d9c91e1da8b8d2c1a09bdb88eb3d24cae41fda#rd
如有侵权请联系:admin#unsafe.sh