@levinzhang
2016-05-15T14:11:36.000000Z
字数 3674
阅读 505
by Abraham Marín Pérez on Apr 28, 2016
在2013年7月,Security Explorations发现了Java中的一个漏洞,通过这个漏洞攻击者可以提升他们的访问权限。Oracle曾经发布过一个补丁,但是只需简单的修改就可以让攻击继续有效。Oracle在8u77中包含了一个补丁对其进行了修复。在本文中,我们会深入介绍比较鲜为人知的类加载过程,它是这个问题的核心所在。
2013年7月,安全组织Security Explorations发现了Java 7u25中的一个安全漏洞,通过这个漏洞攻击者可以完全摆脱Java沙箱。Oracle在更新的7u40中包含了一个补丁,但是据Security Explorations 在今年早些时候声称,这个补丁仅仅在理念上对其进行了修正,对代码稍加修改之后,依然可以利用这个漏洞。另外,随后的研究表明这个漏洞甚至比最初报道的更加严重。在这个问题公开之后,Oracle发布了一个补丁,作为8u77的一部分。
这个漏洞可以在新的反射库中找到,该库从Java 7以后均可以使用,更具体来讲,是在使用新的MethodHandle类动态访问和调用方法的时候。它与不同ClassLoader加载类的方式有关。要理解这个问题,需要一些基本的知识,这些知识涉及到Java ClassLoader的工作方式, 因为类加载是在Java中大家了解最少的领域之一,所以在阐述这个问题本身之前,我们会首先概述一下这个理念。
Java能够在运行时从各种来源动态加载代码。这种功能是通过一系列名为ClassLoader的特殊类来实现的。标准的Java实现会提供多个ClassLoader来加载类,它们能够从文件系统、URL或压缩文件等位置加载类,不过Java也为开发人员提供了创建自定义ClassLoader的能力,以应对个性化的需求。与ClassLoader交互的常见方式是调用其loadClass(String)方法,这个方法会接受类的名称,如果能够找到的话,就会返回相关的Class对象,如果找不到的话,就会抛出ClassNotFoundException异常。在Java应用中的每个类都是通过某个ClassLoader按照这种方式加载的。
通过设置父ClassLoader,这些不同的ClassLoader能够互相连接起来,形成一个层级的结构。如果没有设置父ClassLoader的话,那么父ClassLoader默认将会设置为加载该ClassLoader的那个类加载器(ClassLoader本身也是类,因此也需要通过某个ClassLoader来进行加载)。如果提供了父ClassLoader的话,那么ClassLoader的默认行为就是将加载所请求类的任务委托给它的父加载器,只有父加载器(或祖父加载器)无法加载这个类的时候,这个ClassLoader本身才会试图加载所请求的类。但是,自定义加载器的创建者并非强制性要求遵循这种默认行为,他们完全可以选择实现不同的行为。
当Java应用启动的时候,如下的ClassLoader将会按照顺序发挥作用:
在运行时,使用自定义的ClassLoader动态加载类为很多的应用创造了可能性,否则的话,有些功能可能是无法实现的,不过,遗憾的是,它也造成了很多的安全问题,尤其是在类仿造(class impersonation)方面。理论上,开发人员可以创建一个自定义的ClassLoader,让它来加载原始类java.lang.Object的一个模拟实现,并在应用程序中使用这个自定义的对象。这可能会在两个方面引发安全问题:这个自定义的对象能够访问java.lang包下所有包范围内可见的类内容;其次,这个自定义的Object会被JVM作为标准的Object对象,因此会将其作为由Java实现的可信任的类。
为了保护Java以应对这些安全问题,Java类要通过三个属性来进行识别:类名、包以及ClassLoader的引用。如果两个不同的类具有相同的类名和包名,但是由不同的ClassLoader所加载的话,Java会认为它们是不相等的,在它们两者之间进行赋值的话,将会导致ClassCastException异常。这样的话,就能保护环境免受类仿造的攻击。
Security Explorations最早报告了这个漏洞,并将其归类为CVE-2013-5838,这个漏洞可以描述为,当通过Method Handle调用方法时,对于被调用方法的那个类,它的ClassLoader并没有进行检查,这意味着攻击者可以按照上文所述的方法进行类的仿造。
展现原始漏洞的代码样例,目标类的ClassLoader并没有进行检查;来源:Security Explorations。
Oracle在2013年9月提供了一个修正,作为Java 7u40的一部分,包含了类可见性的检查,它会对比预期类型和传入类型的ClassLoader,对比方式如下:
在第二项检查中,Security Explorations发现exploit稍加修改就可能继续有效。首先,用于仿造类的自定义ClassLoader将目标ClassLoader设置为它的父类加载器,这可以通过API以参数的方式进行设置:
URLClassLoader lookup_CL = URLClassLoader.newInstance(urlArray, member_CL);
通过该机制将自定义的ClassLoader作为等级结构的一部分,来源:Security Explorations。
然后,鉴于ClassLoader的默认行为是将加载类的任务委托给它的父加载器,攻击者就需要确保父ClassLoader无法加载到这个类,这样他们自定义的ClassLoader就能发挥作用了。借助Java以网络方法加载类的过程,这种攻击模式得到了印证:如果这个类通过URL位置的方式来进行定义的话,父ClassLoader将会试图连接相关的服务器并获取这个类的代码,此时,预先构建好的HTTP服务器可以返回404 NOT FOUND错误,让父ClassLoader加载这个类出现失败,因此就会将控制权转移给自定义的ClassLoader。
通过自定义的HTTP服务器,强制父ClassLoader加载类失败之后的代码流,来源:Security Explorations。
当这个缺陷2016年3月重新爆出时,当时最新的可用版本是8u74,这个版本被证明是有漏洞的,Oracle在8u77中为其提供了修正。但是,在8u77的发布说明中,这个漏洞依然描述为“会影响桌面设备上,Web浏览器中所运行的JavaSE[并且]不会影响到Java部署环境,比如典型的服务器或独立部署的桌面应用”,但是事实证明,它还是会影响服务器配置以及Google App Engine的Java环境。
本文错误地认为这个漏洞在8u77、8u91和8u92版本中依然存在,实际上,在8u77中它已经得到了修正。在8u77的发布说明中将其描述为对CVE-2016-0636的修正,具体描述是这样的“未指明的漏洞[...]借助Hotspot子组件中未知的感染内容”并且没有包含对本文中所提及的CVE-2013-5838的明确引用。但是,Security Explorations指出 CVE-2016-0636是针对Issue 69的一个CVE编号,而Issue 69是他们自己代指最初CVE-2013-5838的一种方式。(在本文英文原文的评论区,讨论了该问题解决的相关过程,感兴趣的读者可以访问原文查看。——译者注)
查看英文原文:Vulnerability in Java Reflection Library Fixed after 30 Months