CodeQL - 2022 年寻找 GADGETS 小技巧
2022-3-15 20:20:0 Author: mp.weixin.qq.com(查看原文) 阅读量:31 收藏

前言

因此,您发现了一个易受 Log4Shell 攻击的应用程序,但绕过小工具不起作用,并且您没有设法使用 Ysoserial 的小工具?如果您阅读了我们上一篇关于查找 Java 小工具的文章,您可能会发现一篇带有小工具检查器的新文章。但是如果 gadget 检查器没有找到有效的链怎么办?您可能会停下来并感到绝望,因为正如我们所见,手动研究小工具并不是一件容易的事!

在本文中,我们将介绍一种新方法和多个 CodeQL 查询,以在 Java 应用程序中查找小工具链。我们将解释如何利用 CodeQL 的强大功能来提供小工具检查器的替代方案,为了说明这一点,我们将展示一个新的 Java 小工具。

CodeQL

什么是 CodeQL?从他们的文档中:

使用我们行业领先的语义代码分析引擎 CodeQL 发现代码库中的漏洞。CodeQL 让您可以像查询数据一样查询代码。编写查询以查找漏洞的所有变体,并永远根除它。

换句话说,CodeQL 是一个非常强大的静态代码分析器,它提供了一种通过查询来分析代码的方法。当您在白盒中搜索漏洞时,它非常有用。您可以通过创建新查询或使用现有查询来搜索代码中易受攻击的模式。它还提供数据流功能来跟踪跨函数调用的数据,例如,这对于查找注入漏洞很有用。

将问题转换为 CODEQL

首先,我们需要将查找 Java 小工具的问题转化为 CodeQL。小工具链通常是chainsource方法调用readObjectsink将执行危险操作(例如调用execJava 运行时的方法)的方法的函数调用。

在这里,我们定义了三个主要组件:

  • source
  • sink
  • chain

寻找新的Sink

Sink 方法是我们想要达到的危险方法。我们可以像这样在 CodeQL 中定义它们:

private class RuntimeExec extends Method {
  RuntimeExec(){
    hasQualifiedName("java.lang""Runtime""exec")
  }
}

在这里,我们定义了一个名为RuntimeExec. 由于该类扩展了Method该类,因此它将匹配在包的类中exec定义的 所有被调用的方法。Runtime``java.lang

定义新的接收器非常容易。我们定义了一个DangerousMethod包含每种危险类型Sink的类。然后可以通过对该类的新检查添加新的:

class DangerousMethod extends Callable {
  DangerousMethod(){
    this instanceof ExpressionEvaluationMethod or
    this instanceof ReflectionInvocationMethod or
    this instanceof RuntimeExec or
    this instanceof URL or
    this instanceof ProcessBuilder or 
    this instanceof Files or
    this instanceof FileInputStream or 
    this instanceof FileOutputStream or
    this instanceof EvalScriptEngine or
    this instanceof ClassLoader or
    this instanceof ContextLookup

    }
}

我们可以使用代表方法调用的类来搜索对那些方法 MethodAccess的调用。例如,以下查询将返回对该 exec方法的所有调用:

from MethodAccess ma
where ma.getMethod() instanceof RuntimeExec
select ma

可能有趣的方法调用示例。

请注意,我们的示例中使用的代码可以在我们的GitHub 上找到。

我们想要所有调用 a 的方法DangerousMethod,因此我们寻找 MethodAccess危险方法,并使用封闭的可调用对象(调用危险方法的方法)作为结果:

private class CallsDangerousMethod extends Callable {

  CallsDangerousMethod(){

    exists(MethodAccess ma | ma.getMethod() instanceof DangerousMethod and ma.getEnclosingCallable() = this)

  }

}

在精确描述了我们正在寻找的对象之后CallsDangerousMethod,可以直接在我们的代码库中请求它们:

from Callable c
where c instanceof CallsDangerousMethod
select c

潜在接收器的示例。

VulnMethod是可调用的CallsDangerousMethod,因为它使用 Java 反射并调用invoke被认为是危险的方法。

Callable, MethodAccess, Method, DangerousMethod如果这是您第一次使用 CodeQL,那么所有这些都可能会让人感到困惑。稍微使用一下这个工具可以帮助理解这些查询。

寻找新资源

源是我们可以调用来启动小工具链的方法,第一个显而易见的是, readObject但还有其他方法,例如:

  • readObjectNoData
  • readResolve
  • readExternal
  • ...

这些是可以在反序列化对象时调用的方法,因此任何具有这些方法之一的 Serializable 类都是有效的起点。

但是,我们可以调用其他已知方法。从我们之前的文章中我们知道我们可以调用任意地图的get方法。我们也可以想到hashCode, equals, compare...

所有这些方法都可以添加到源起点:

class Source extends Callable{
    Source(){
        getDeclaringType().getASupertype*() instanceof TypeSerializable and (
            this instanceof MapSource or 
            this instanceof SerializableMethods or
            this instanceof Equals or
            this instanceof HashCode or
            this instanceof Compare or
            this instanceof ExternalizableMethod or 
            this instanceof ObjectInputValidationMethod or
            this instanceof InvocationHandlerMethod or
            this instanceof MethodHandlerMethod or
            this instanceof GroovyMethod
        )

    }
}

请注意,源方法的声明类型必须是可序列化的,否则无法反序列化。

我们可以搜索所有实例Source

from Callable c
where c instanceof Source
select c

链的潜在起点示例。

如您所见,此 hashCode方法是一个源,但也是一个接收器,它使小工具易于发现。

将源连接到接收器

我们有 Source现在 Sink,我们需要链接并找到它们之间的路径。

SecurityJava CodeQL存储库目录的大多数示例查询都使用数据流或污染分析。这与我们的案例无关,因为我们对跟踪特定数据不感兴趣,因为我们控制了反序列化对象的所有字段。

事实上,一个有效的链将是一个方法,它是一个 Source调用一个或多个方法的实例,最后一个是CallsDangerousMethod. 在 CodeQL 中,这可以通过以下polyCalls方法完成:

from Callable source, Callable sink 
where source instanceof Source and
sink instanceof CallsDangerousMethod and 
source.polyCalls(sink)
select  source, sink

导致接收器的多个方法的调用链。

但这需要递归,因为小工具链不仅仅是调用接收器的源:

private class RecursiveCallToDangerousMethod extends Callable {
  RecursiveCallToDangerousMethod(){

    (
      getDeclaringType().getASupertype*() instanceof TypeSerializable or 
      this.isStatic() 
    ) 
    
    and
    
    (
     this instanceof CallsDangerousMethod or
    exists(RecursiveCallToDangerousMethod unsafe | this.polyCalls(unsafe))
    ) 
  }
}

我们只寻找可序列化或静态类的方法,因为我们可以从反序列化流程中调用静态方法,就像在Click1小工具链中一样。请注意,此陈述部分正确,我们也可以调用不可序列化的类的方法。这是一个小例子:

private void readObject(java.io.ObjectInputStream in) {
    in.defaultReadObject();
    dangerousObject = new DangerousObject();
    dangerousObject.execDangerousMethod()
}

删除可序列化检查是可能的,它会增加误报的数量,但可以根据您的需要产生新的结果。

最后,如果 a是一个 表达式,或者如果它调用另一个是 a ,则将 aCallable视为a 。RecursiveCallToDangerousMethod``CallsDangerousMethod``Callable``RecursiveCallToDangerousMethod

现在我们有一切可以找到新的链条。

重新发现旧链

我们下载了已知包含类似Ysoserial的小工具链的 Java 库,并运行我们的查询以验证我们的技术确实适用于实际项目。

Click1

Click在 2.3.0 版本的库中有一个链, 完整的链是:

java.util.PriorityQueue.readObject()
      java.util.PriorityQueue.heapify()
        java.util.PriorityQueue.siftDown()
          java.util.PriorityQueue.siftDownUsingComparator()
            org.apache.click.control.Column$ColumnComparator.compare()
              org.apache.click.control.Column.getProperty()
                org.apache.click.control.Column.getProperty()
                  org.apache.click.util.PropertyUtils.getValue()
                    org.apache.click.util.PropertyUtils.getObjectPropertyValue()
                      java.lang.reflect.Method.invoke()
                        com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()

首先我们需要找到 sinks,因为如果没有 sinks 我们可以在这里停下来,我们将无法找到 gadget 链并且可以在这里停下来:

from Callable c0,  MethodAccess ma
where c0 instanceof RecursiveCallToDangerousMethod and
ma.getMethod() instanceof DangerousMethod and 
ma.getEnclosingCallable() = c0
select c0, ma
Click2

然后我们可以开始寻找来源:

from Callable c0
where c0 instanceof RecursiveCallToDangerousMethod and
c0 instanceof Source
select c0

请注意,源也必须是 a RecursiveCallToDangerousMethod,因为我们对不会导致危险方法的源不感兴趣。

Click3

我们有一个结果,但这意味着什么?它只是意味着在对compare方法的调用和我们之前找到的至少一个接收器之间存在一条链。现在我们需要用中间调用填充链。

不幸的是,这是手动的,不能自动化,因为我们事先不知道会有多少中间调用,所以我们从 0 个中间调用开始:

from Callable c0, MethodAccess ma
where c0 instanceof RecursiveCallToDangerousMethod and

ma.getMethod() instanceof DangerousMethod and 
ma.getEnclosingCallable() = c0 and

c0 instanceof Source 

select c0, ma

此查询不会有任何结果,因为源和接收器不相同。因此,我们添加中间调用,直到找到一个链:

from Callable c0, Callable c1, Callable c2, Callable c3, Callable c4, 
MethodAccess ma

where c0 instanceof RecursiveCallToDangerousMethod and
ma.getMethod() instanceof DangerousMethod and 
ma.getEnclosingCallable() = c0 and

c1.polyCalls(c0) and
c1 instanceof RecursiveCallToDangerousMethod and

c2.polyCalls(c1) and
c2 instanceof RecursiveCallToDangerousMethod and

c3.polyCalls(c2) and
c3 instanceof RecursiveCallToDangerousMethod and

c4.polyCalls(c3) and
c4 instanceof RecursiveCallToDangerousMethod and

c4 instanceof Source 

select  c4, c3, c2, c1, c0, ma

Click1

该链与 Ysoserial 中的链相同。最后一个方法是getObjectPropertyValue通过反射调用任何 getter 方法。这可以与TemplatesImpl执行任意命令结合使用。

File: PropertyUtils.java
187:     private static Object getObjectPropertyValue(Object source, String name, Map cache) {
188:         PropertyUtils.CacheKey methodNameKey = new PropertyUtils.CacheKey(source, name);
189
190:         Method method = null;
191:         try {
192:             method = (Method) cache.get(methodNameKey);
193
194:             if (method == null) {
195
196:                 method = source.getClass().getMethod(ClickUtils.toGetterName(name));
197:                 cache.put(methodNameKey, method);
198:             }
199
200:             return method.invoke(source);

事实上,这是一个众所周知的技巧。该类 TemplatesImpl是 JDK 的一部分,如果我们设法调用该getOutputProperties方法,我们可以加载任意类并执行任意命令。可以在此处找到有关此技巧的更多详细信息。

请注意,当您找到通向一个接收器的有效链时,您可以继续添加中间调用以查找通向所有接收器的所有可能链。

ROME / Hibernate / Mojarra

正如我们在Click1示例中看到的,该过程有点手动,我们需要完成以下每个步骤:

  • 检查 sink 的存在
  • 检查导致 sink  的源的存在
  • 找到完整的链

我们在其他已知的易受攻击的 Java 库上重复了这个过程,以重新发现我们之前文章中的已知链,如ROME、Hibernate1和Mojarra链。

对于 Hibernate1 链,我们还发现了原始小工具的多个变体,例如红色的那个:

休眠2
org.hibernate.cache.spi.QueryKey.readObject
        org.hibernate.cache.spi.QueryKey.generateHashCode
              org.hibernate.type.ComponentType.getHashCode
                    org.hibernate.type.ComponentType.getPropertyValue
                          org.hibernate.type.ComponentType.getPropertyValue
                                org.hibernate.property.access.spi.GetterMethodImpl.get
                                      java.lang.reflect.Method.invoke

这条链对于 hibernate 的 5.6.5 版本和原来的一样有效。

莫哈拉也是如此。在我们上一篇文章@testanull在 Twitter 上发表后,我们发现了另一个链,它也可以像许多其他链一样找到:

莫哈拉2

请注意,小工具检查器无法在各自编译的 jar 上找到 ROME 和 Hibernate1 链。

新链条

寻找旧的小玩意很好,但现在是时候寻找新的了。

我们在Wildfly项目上运行查询。Wildfly 是一个 Java 应用服务器,拥有超过 10000 个 Java 类。

通过运行源查询,我们可以找到以下readObject方法:

File: WildFlyDataSource.java
113:     private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
114:         in.defaultReadObject();
115:         jndiName = (String) in.readObject();
116
117
118:         try {
119:             InitialContext context = new InitialContext();
120
121:             DataSource originalDs = (DataSource) context.lookup(jndiName);
[...]

这显然容易受到 JNDI 注入的影响。利用代码可以在我们的 GitHub 上找到,并且已经对 Ysoserial 存储库进行了拉取请求。

该类WildFlyDataSourceorg.jboss.as.connector包的一部分,捆绑在 WildFly GitHub 存储库中。

结论

利用 CodeQL 的强大功能非常有助于查找 Java 小工具链。但是,有一些限制。首先,您需要拥有要分析的项目的源代码,并且您需要能够编译它,否则它将无法与 CodeQL 一起使用。另一个限制是 CodeQL 一次只能分析一个项目。您不能混合使用多个库来查找链,这与能够做到这一点的小工具检查器不同,只要您用一个胖罐子喂它。

尽管如此,找到 Java 库的源代码通常是可能的,并且使用 maven、Gradle 或 ant 编译它非常容易,这使得这种方法仍然很强大。

我们希望我们在 CodeQL 的帮助下尽可能详细地介绍了寻找新的 Java 小工具的过程。现在是时候寻找新的了,请不要忘记分享你的发现!


文章来源: http://mp.weixin.qq.com/s?__biz=MzU0MDcyMTMxOQ==&mid=2247486056&idx=1&sn=51927553fce0f48ef77d01c9a4d82007&chksm=fb35a3a0cc422ab6f8d1583dcb85de02e674c5b1224a7c1127eaad1dbaf371f69948b8511f61#rd
如有侵权请联系:admin#unsafe.sh