Xcheck 对 Java 工具类代码的分析尝试
2022-5-9 16:43:20 Author: mp.weixin.qq.com(查看原文) 阅读量:1 收藏

0x00 背景

log4j2 和 spring cloud function 爆出严重的安全漏洞,作为静态代码扫描工具,Xcheck 经常会被问到——是否能够发现此类漏洞。

从设计初衷出发,Xcheck 不会对框架代码和工具类代码(第三方 Jar 包)进行有效扫描。

主要有以下原因:

  1. 此类代码与上层应用代码相比,通常来说漏洞相对少,且使用到的 Java 语言特性比较复杂,Xcheck 使用的污点传播模型来进行分析,既不够高效,效果也不够理想
  2. 工具类代码的入口缺乏统一的特征,如果要通过自定义规则来逐一标注,不利于实现自动化批量分析

尝试自己使用CodeQL,以及参考网上的文章,在不添加专用规则的前提下并没有实现精准的分析出 log4j2 的漏洞。

那么换一个思路,如果只需要得出近似的结果,Xcheck 是否可以做?

近期,基于 Java 引擎进行扩展,实现了对框架代码和工具类代码的分析,能够得出一些近似结果,可以辅助做安全研究的同学用于分析。

0x01 log4j2

分析后,根据lookup函数得出可能的入口函数有 426 个,其中虽然包含了所需要的函数,但是也存在不少干扰数据:

AbstractLogger.debug(Marker, String, Object, Object, Object, Object, Object, )
AbstractLogger.debug(Marker, String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.debug(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.debug(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.debug(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.debug(String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.debug(String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.debug(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.debug(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.enter(String, MessageSupplier, )
AbstractLogger.error(Marker, String, Object, Object, Object, Object, )
AbstractLogger.error(Marker, String, Object, Object, Object, Object, Object, )
AbstractLogger.error(Marker, String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(Marker, String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(String, Object, Object, Object, Object, Object, )
AbstractLogger.error(String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.error(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(String, Object, Object, Object, Object, )
AbstractLogger.fatal(String, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.fatal(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, String, Object, Object, Object, Object, )
AbstractLogger.log(Level, String, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Level, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.log(Message, )
AbstractLogger.readObject(ObjectInputStream, )
AbstractLogger.throwing(Level, T, )
AbstractLogger.trace(Marker, String, Object, Object, Object, Object, Object, )
AbstractLogger.trace(Marker, String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(Marker, String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.trace(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(Marker, String, Object, Object, Object, Object, Object, )
AbstractLogger.warn(Marker, String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(Marker, String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(Marker, String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(String, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(String, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(String, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
AbstractLogger.warn(String, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, )
...
...

Xcheck 能够找到从风险函数到入口函数的调用链路,但是,在不做专用规则的前提下,无法生成与真实路径一致的结果。

在此类较为复杂的场景下,Xckeck可以做的是,根据Debug过程中的实际数据,实时生成可能的调用链路,从而辅助漏洞挖掘:

0x02 Spring Cloud Function

分析后,根据parseExpression函数得出可能的入口函数有 4 个:

FunctionDeployerConfiguration.functionArchiveUnDeployer(FunctionDeployerProperties, FunctionRegistry, ApplicationArguments, MavenProperties, ApplicationContext, )

HeaderEnricher.apply(Object, )

RoutingFunction.apply(Object, )

SimpleFunctionRegistry.FunctionInvocationWrapper.accept(Object, )

其中调试确认 RoutingFunction.apply(Object)是真实触发路径上的函数:

Xcheck 分析出的调用路径与实际结果吻合:

RoutingFunction.apply(Object)
RoutingFunction.route(Object, String)
RoutingFunction.functionFromExpression(String, Object, String)
org.springframework.expression.spel.standard.SpelExpressionParser.parseExpression()

还缺少 doApply 到 post 的调用链条,是由于工具在处理上存在一点小的瑕疵,后续会补齐。

0x03 其它

尝试分析常见的 Java 框架和工具类代码,发现很多之前没有关注到的有意思代码,以下是一些归纳总结。

  1. 很多软件都会对配置文件中的内容调用表达式语言进行解析,例如:

    Mybatis3 在处理某些标签时,会调用 OGNL 对标签属性进行解析

    spring-cloud-stream-binder-rabbit 的部分 bindings 配置信息会被 SPEL 解析

    如果你发现有写配置文件或者XML文件的漏洞,可以考虑上述特性的利用

  2. 反序列化还是广泛存在于各种软件中,如果有新的反序列化链条被发现,会有很大影响,例如:

    spring-cloud-integration的session数据的持久化逻辑中

  3. 代码中的各种注解,很多都用到表达式语言模块进行解析,例如:

    jeecg-boot、resilience4j、spring-batch

0x04 总结

  1. 目前主流 SAST 工具对于框架类代码和工具类代码,在不做专用规则的前提下,只能输出近似调用链条的结果

  2. 对于调用关系复杂场景(如 log4j2),输出的调用链条更多的意义在于辅助安全研究人员进行调试分析

  3. 对于调用关系简单的场景(如 spring cloud function),输出的调用链条比较接近于准确结果


文章来源: https://mp.weixin.qq.com/s?__biz=Mzg2ODQ3ODE1NA==&mid=2247486008&idx=1&sn=f90b681a5cce77d88f53fe6403fc4683&chksm=ceaaf79df9dd7e8b4485b15eb0f29bc2dd039c86f36b8fc1a07b50a75794cb790cebae078f4c&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh