0x00 背景
log4j2 和 spring cloud function 爆出严重的安全漏洞,作为静态代码扫描工具,Xcheck 经常会被问到——是否能够发现此类漏洞。
从设计初衷出发,Xcheck 不会对框架代码和工具类代码(第三方 Jar 包)进行有效扫描。
主要有以下原因:
-
此类代码与上层应用代码相比,通常来说漏洞相对少,且使用到的 Java 语言特性比较复杂,Xcheck 使用的污点传播模型来进行分析,既不够高效,效果也不够理想 -
工具类代码的入口缺乏统一的特征,如果要通过自定义规则来逐一标注,不利于实现自动化批量分析
尝试自己使用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 框架和工具类代码,发现很多之前没有关注到的有意思代码,以下是一些归纳总结。
-
很多软件都会对配置文件中的内容调用表达式语言进行解析,例如:
Mybatis3 在处理某些标签时,会调用 OGNL 对标签属性进行解析
spring-cloud-stream-binder-rabbit 的部分 bindings 配置信息会被 SPEL 解析
如果你发现有写配置文件或者XML文件的漏洞,可以考虑上述特性的利用
-
反序列化还是广泛存在于各种软件中,如果有新的反序列化链条被发现,会有很大影响,例如:
spring-cloud-integration的session数据的持久化逻辑中
-
代码中的各种注解,很多都用到表达式语言模块进行解析,例如:
jeecg-boot、resilience4j、spring-batch
0x04 总结
-
目前主流 SAST 工具对于框架类代码和工具类代码,在不做专用规则的前提下,只能输出近似调用链条的结果
-
对于调用关系复杂场景(如 log4j2),输出的调用链条更多的意义在于辅助安全研究人员进行调试分析
-
对于调用关系简单的场景(如 spring cloud function),输出的调用链条比较接近于准确结果
原文始发于微信公众号(腾讯代码安全检查Xcheck):Xcheck 对 Java 工具类代码的分析尝试