《Java编码指南:编写安全可靠程序的75条建议》—— 指南18:不要将使用降低安全性检查的方法暴露给不可信代码...

    xiaoxiao2024-04-18  10

    本节书摘来异步社区《Java编码指南:编写安全可靠程序的75条建议》一书中的第1章,第1.18节,作者:【美】Fred Long(弗雷德•朗), Dhruv Mohindra(德鲁•莫欣达), Robert C.Seacord(罗伯特 C.西科德), Dean F.Sutherland(迪恩 F.萨瑟兰), David Svoboda(大卫•斯沃博达),更多章节内容可以访问云栖社区“异步社区”公众号查看。

    指南18:不要将使用降低安全性检查的方法暴露给不可信代码

    大多数方法缺乏安全管理器检查,是因为它们不提供对系统敏感部分(如文件系统)的访问。大多数提供安全管理器检查的方法,都是在调用堆栈中的每个类和方法被执行之前进行身份验证。这个安全模型允许Java applet这样的受限制程序对核心Java库具有完全访问权限。它还可以防止敏感方法扮演成藏身于可信的调用堆栈中的恶意方法。

    但是,某些方法使用了降低安全性检查,只检查正在调用的方法是否已授权,而不检查调用堆栈中的每一个方法。任何调用这些方法的代码必须保证它们不能代表不可信的代码。表1-2列出了这些方法。

    因为方法java.lang.reflect.Field.setAccessible()和getAccessible()被用来通知Java虚拟机(JVM)进行覆盖语言访问检查,所以它们执行标准的(甚至更严格的)安全管理器检查,因此不会出现这条指南中描述的漏洞。然而,使用这些方法时也要倍加小心,其余set()和get()字段反射方法只执行语言访问检查,因此易受到攻击。

    类加载器

    类加载器允许Java应用程序通过加载额外的类而在运行时动态扩展。对于每个被加载的类,JVM都会跟踪用于加载该类的类加载器。当已加载的类第一次引用另一个类时,虚拟机请求使用该类的加载器来加载被引用的类。Java的类加载器架构通过使用不同的类加载器,来控制跟加载自不同来源的代码之间的交互。这种类加载器的分离是代码分离的基础:它可以防止恶意代码获取访问并破坏可信代码。

    其中几个负责加载类的方法,将它们的工作委派给了被调用方法的类加载器。类加载器会执行与类的加载有关的安全检查。因此,任何调用其中的一个类加载方法的代码,必须保证这些方法不能代表不可信的代码。这些方法如表1-3所示。

    除了loadLibrary()和load()方法,列表中其他方法不执行任何安全管理器检查;它们将安全检查委托给了适当的类加载器。

    在实践中,可信代码的类加载器经常允许调用这些方法,而不可信代码的类加载器可能缺少这样的特权。然而,当不可信代码的类加载器委托给可信代码的类加载器时,可信代码对于不可信代码来说是可见的。在缺乏这样的委托关系时,类加载器会确保命名空间的分离,因此,不可信代码将无法观察属于可信代码的成员,也无法调用属于可信代码的方法。

    类加载器委托模型是许多Java实现及框架的基础。要避免将表1-2和表1-3中列出的方法暴露给不可信的代码。例如,试想不可信代码试图加载一个特权类的攻击场景,如果它的类加载器自身缺少加载所请求的特权类的权限,但是,类加载器可以将类的加载委托给可信类的类加载器,那么就会发生特权升级。此外,如果可信代码接受被污染的输入,那么可信代码的类加载器就会代表不可信代码,加载恶意的特权类。

    具有相同的类加载器定义的类,将会存在于相同的命名空间里,但根据安全策略的不同,它们可能具有不同的特权。当特权代码与同一个类加载器加载的无特权代码(或者更少特权的代码)共存时,就会出现安全漏洞。在这种情况下,更少特权的代码可以根据特权代码声明的可访问性,自由地访问特权代码的成员。使用上述表格中API的特权代码,能绕过安全管理器检查(loadLibrary()和load()方法除外)。

    该指南类似于《The CERT® Oracle® Secure Coding Standard for Java™》[Long 2012]的“SEC03-J. Do not load trusted classes after allowing untrusted code to load arbitrary classes”。许多例子也违反“SEC00-J. Do not allow privileged blocks to leak sensitive information across a trust boundary”。

    违规代码示例下面的违规代码示例将System.loadLibrary()方法的调用嵌到了doPrivileged()语句块中。

    public void load(String libName) {  AccessController.doPrivileged(new PrivilegedAction() {   public Object run() {    System.loadLibrary(libName);    return null;   }  }); }``` 这段代码是不安全的,因为它可以代表不可信代码来加载一个库。在本质上,不可信代码的类加载器可以使用这段代码来加载一个库,即使它缺乏足够的权限直接去加载。加载一个库后,不可信代码可以从该库中调用可供访问的本地方法,因为doPrivileged()语句块会妨碍安全管理器检查被应用到调用者进而执行堆栈。 非本地库的代码也容易受相关安全漏洞的影响。假设存在一个库,该库包含一个没有直接暴露的漏洞,也许就藏在一个未被使用的方法中。加载这个库可能也不会直接暴露该漏洞。然而,攻击者可以加载一个额外的库,攻破第一个库的漏洞。此外,非本地库经常使用doPrivileged语句块,这让它们成了有吸引力的攻击目标。 合规解决方案 下面的合规解决方案对代码库的名称进行了硬编码,防止了输入值被污染的可能性。它同时也减少了load()方法的可访问性,从public(公共)变为private(私有)。因此,不可信的调用者被禁止加载awt库。

    private void load() { AccessController.doPrivileged(new PrivilegedAction() {  public Object run() {   System.loadLibrary("awt");   return null;  } });}`违规代码示例下面的违规代码示例将一个java.sql.Connection的实例从可信代码返回到不可信代码。

    public Connection getConnection(String url, String username,   String password) {  // ...  return DriverManager.getConnection(url, username, password); }``` 缺少创建SQL连接所需权限的不可信代码,可以通过使用直接获取的实例,绕过这些限制。getConnection()方法是不安全的,因为它使用url参数来指示要加载的类,这个类就是数据库驱动程序。 ####合规解决方案 下面的合规解决方案可以防止恶意用户提供他们自己的数据库连接URL,从而限制了它们加载不可信的驱动程序。

    private String url = // Hardwired value

    public Connection getConnection(String username,  String password) { // ... return DriverManager.getConnection(this.url,  username, password);}`违规代码示例(CERT Vulnerability 636312)CERT漏洞注解VU#636312描述了一个Java 1.7.0版本第6次更新中的漏洞,该漏洞在2012年8月被广泛利用。攻击程序实际上利用了两个漏洞,另一个的描述在《The CERT® Oracle® Secure Coding Standard for Java™》[Long 2012]的“SEC05-J. Do not use reflection to increase accessibility of classes, methods, or fields”里。

    该攻击程序作为一个Java applet运行。applet的类加载器确保applet不能直接调用com.sun.*包中类的方法。一个正常的安全管理器检查,可以根据调用堆栈中所有调用者方法的特权(这些特权是和类的代码源相关联的),确定是允许还是拒绝特定动作。

    攻击程序的第一个目标是访问私有的sun.awt.SunToolkit类。不过,用该类的名称直接调用class.forName()方法,将会导致抛出SecurityException异常。因此,攻击程序利用了下面的代码来访问任意类,绕过了安全管理器:

    private Class GetClass(String paramString)   throws Throwable {  Object arrayOfObject[] = new Object[1];  arrayOfObject[0] = paramString;  Expression localExpression =   new Expression(Class.class, "forName", arrayOfObject);  localExpression.execute();  return (Class)localExpression.getValue(); }``` java.beans.Expression.execute()方法将它的工作委托给了下面的方法:

    private Object invokeInternal() throws Exception { Object target = getTarget(); String methodName = getMethodName(); if (target == null || methodName == null) {  throw new NullPointerException(   (target == null ? "target" : "methodName") +    " should not be null"); } Object[] arguments = getArguments(); if (arguments == null) {  arguments = emptyArray; } // Class.forName() won't load classes outside // of core from a class inside core, so it // is handled as a special case. if (target == Class.class && methodName.equals("forName")) {  return ClassFinder.resolveClass((String)arguments[0],               this.loader); }// ...`com.sun.beans.finder.ClassFinder.resolveClass()方法将它工作委托给了它的findClass()方法:

    public static Class findClass(String name)   throws ClassNotFoundException {  try {   ClassLoader loader =    Thread.currentThread().getContextClassLoader();   if (loader == null) {    loader = ClassLoader.getSystemClassLoader();   }   if (loader != null) {    return Class.forName(name, false, loader);   }  } catch (ClassNotFoundException exception) {   // Use current class loader instead  } catch (SecurityException exception) {   // Use current class loader instead  }  return Class.forName(name); }``` 虽然这个方法是在applet的上下文中调用的,但它还是使用了class.forName()来获取所请求的类。Class.forName()将该搜索委托给调用方法的类加载器。在这种情况下,调用类(com.sun.beans.finder.ClassFinder)是Java核心的一部分,因此,可信的类加载器代替了受更多限制的applet类加载器,同时可信的类加载器加载了所请求的类,它并不知道自己正在为恶意代码服务。 ####合规解决方案(CVE-2012-4681) Oracle公司通过对com.sun.beans.finder.ClassFinder.findClass()方法打补丁,在Java 1.7.0版本的第7次更新中缓解了这个漏洞。在下面这个实例中,checkPackageAccess()方法检查整个调用堆栈,确保class.forName()只为可信代码获取类。

    public static Class<?> findClass(String name)  throws ClassNotFoundException { checkPackageAccess(name); try {  ClassLoader loader =   Thread.currentThread().getContextClassLoader();  if (loader == null) {   // Can be null in IE (see 6204697)   loader = ClassLoader.getSystemClassLoader();  }  if (loader != null) {   return Class.forName(name, false, loader);  }

     } catch (ClassNotFoundException exception) {  // Use current class loader instead } catch (SecurityException exception) {  // Use current class loader instead } return Class.forName(name);}`

    违规代码示例(CVE-2013-0422)

    Java 1.7.0版本的第10次更新在2013年1月因为几个漏洞被广泛攻击。其中有一个这样的漏洞:com.sun.jmx.mbeanserver.MBeanInstantiator类给无特权代码授予了访问任何类的权限,不受当前安全策略或可访问性规则的限制。可以通过任意一个字符串来调用MBeanInstantiator.findClass()方法,并尝试返回以该字符串命名的Class对象。这个方法将它的工作委派给了MBeanInstantiator.loadClass()方法,其源代码如下所示:

    /** * Load a class with the specified loader, or with this object * class loader if the specified loader is null. **/ static Class<?> loadClass(String className, ClassLoader loader)   throws ReflectionException {  Class<?> theClass;  if (className == null) {   throw new RuntimeOperationsException(    new IllegalArgumentException(     "The class name cannot be null"),      "Exception occurred during object instantiation");  } try {   if (loader == null) {    loader = MBeanInstantiator.class.getClassLoader();   }   if (loader != null) {    theClass = Class.forName(className, false, loader);   } else {    theClass = Class.forName(className);   }  } catch (ClassNotFoundException e) {   throw new ReflectionException(    e, "The MBean class could not be loaded");  }  return theClass; }``` 这个方法将动态加载指定类的任务委托给了Class.forName()方法,Class.forName()又将其工作委托给了它调用的方法的类加载器。因为调用的方法是MBeanInstantiator.loadClass(),而它使用的是核心类加载器,因此没有提供安全检查。 ####合规解决方案(CVE-2013-0422) Oracle公司在Java 1.7.0版本的第11次更新中,添加了对MBeanInstantiator.loadClass()方法的访问检查,缓解了这个漏洞。这个访问检查确保了调用者可以访问所寻求的类。

    // ... if (className == null) {  throw new RuntimeOperationsException(   new IllegalArgumentException(    "The class name cannot be null"),    "Exception occurred during object instantiation"); } ReflectUtil.checkPackageAccess(className); try {  if (loader == null)// ...`

    适用性

    允许不可信代码调用降低安全性检查的方法,将会导致特权升级。同样地,允许不可信代码使用直接调用者的类加载器来执行操作,可能会允许不可信代码以与直接调用者相同的权限执行。

    避免使用直接调用者的类加载器实例的方法,超出了本指南的讨论范围。例如,三参数的java.lang.Class.forName()方法需要一个显式的参数,用以指定要使用的类加载器实例。

    public static Class forName(String name, boolean initialize,     ClassLoader loader) throws ClassNotFoundException``` 相关资源:七夕情人节表白HTML源码(两款)
    最新回复(0)