因此,您发现了一个易受 Log4Shell 攻击的应用程序,但绕过小工具不起作用,并且您没有设法使用 Ysoserial 的小工具?如果您阅读了我们上一篇关于查找 Java 小工具的文章,您可能会发现一篇带有小工具检查器的新文章。但是如果 gadget 检查器没有找到有效的链怎么办?您可能会停下来并感到绝望,因为正如我们所见,手动研究小工具并不是一件容易的事!
在本文中,我们将介绍一种新方法和多个 CodeQL 查询,以在 Java 应用程序中查找小工具链。我们将解释如何利用 CodeQL 的强大功能来提供小工具检查器的替代方案,为了说明这一点,我们将展示一个新的 Java 小工具。
什么是 CodeQL?从他们的文档中:
使用我们行业领先的语义代码分析引擎 CodeQL 发现代码库中的漏洞。CodeQL 让您可以像查询数据一样查询代码。编写查询以查找漏洞的所有变体,并永远根除它。
换句话说,CodeQL 是一个非常强大的静态代码分析器,它提供了一种通过查询来分析代码的方法。当您在白盒中搜索漏洞时,它非常有用。您可以通过创建新查询或使用现有查询来搜索代码中易受攻击的模式。它还提供数据流功能来跟踪跨函数调用的数据,例如,这对于查找注入漏洞很有用。
首先,我们需要将查找 Java 小工具的问题转化为 CodeQL。小工具链通常是chain
从source
方法调用readObject
到sink
将执行危险操作(例如调用exec
Java 运行时的方法)的方法的函数调用。
在这里,我们定义了三个主要组件:
source
sink
chain
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
,我们需要链接并找到它们之间的路径。
Security
Java 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 库,并运行我们的查询以验证我们的技术确实适用于实际项目。
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
然后我们可以开始寻找来源:
from Callable c0
where c0 instanceof RecursiveCallToDangerousMethod and
c0 instanceof Source
select c0
请注意,源也必须是 a RecursiveCallToDangerousMethod
,因为我们对不会导致危险方法的源不感兴趣。
我们有一个结果,但这意味着什么?它只是意味着在对compare
方法的调用和我们之前找到的至少一个接收器之间存在一条链。现在我们需要用中间调用填充链。
不幸的是,这是手动的,不能自动化,因为我们事先不知道会有多少中间调用,所以我们从 0 个中间调用开始:
from Callable c0, MethodAccess ma
where c0 instanceof RecursiveCallToDangerousMethod andma.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 mawhere 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
该链与 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
方法,我们可以加载任意类并执行任意命令。可以在此处找到有关此技巧的更多详细信息。
请注意,当您找到通向一个接收器的有效链时,您可以继续添加中间调用以查找通向所有接收器的所有可能链。
正如我们在Click1
示例中看到的,该过程有点手动,我们需要完成以下每个步骤:
我们在其他已知的易受攻击的 Java 库上重复了这个过程,以重新发现我们之前文章中的已知链,如ROME、Hibernate1和Mojarra链。
对于 Hibernate1 链,我们还发现了原始小工具的多个变体,例如红色的那个:
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 上发表后,我们发现了另一个链,它也可以像许多其他链一样找到:
请注意,小工具检查器无法在各自编译的 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 存储库进行了拉取请求。
该类WildFlyDataSource
是org.jboss.as.connector
包的一部分,捆绑在 WildFly GitHub 存储库中。
利用 CodeQL 的强大功能非常有助于查找 Java 小工具链。但是,有一些限制。首先,您需要拥有要分析的项目的源代码,并且您需要能够编译它,否则它将无法与 CodeQL 一起使用。另一个限制是 CodeQL 一次只能分析一个项目。您不能混合使用多个库来查找链,这与能够做到这一点的小工具检查器不同,只要您用一个胖罐子喂它。
尽管如此,找到 Java 库的源代码通常是可能的,并且使用 maven、Gradle 或 ant 编译它非常容易,这使得这种方法仍然很强大。
我们希望我们在 CodeQL 的帮助下尽可能详细地介绍了寻找新的 Java 小工具的过程。现在是时候寻找新的了,请不要忘记分享你的发现!