前言
由于最近h.v.v期间爆出了weblogic关于jdk7u21的绕过,因此准备学习一下这个链。
实验环境:jdk7u17、ysoserial源码
代码分析
首先创建一个类,模拟使用jdk7u21进行序列化和反序列化的过程
1 | public class jdk17Gadgat { |
关键的jdk7u21.getObject(final String command)方法如下
1 | public Object getObject(final String command) throws Exception { |
可以看到最后还是调用了TemplatesImpl进行命令执行的,因此我们直接在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl中的newTransformer()方法中下断点
直接debug
顺着堆栈找之前的调用过程,看是如何一步步的到我们打到断点的那个语句的,只要走到我们打断点的那个语句,命令执行就等于成功了。找到Hashset的readObject的那条堆栈,发现是从map.put(方法进入的)。
此时的e,为javax.xml.transform.Templates的动态代理,而这个动态代理类的InvocationHandler的memberValues的值为f5a5a608 -> {TemplatesImpl@761}
接下来回到我们的map.put的下一步继续看
调用了key.equals,key即为上一步的e,调用proxy类型的任何方法,都会先调用其Invocation的invoke方法。
最后调用到euqalsImpl方法,在这个方法中,遍历所有AnnotationInvocationHandler.this.type.getDeclaredMethods()中的方法,然后逐个调用,最后调用到com.sun.org.apache.xalan.internal.xsltc.trax的newTransformer()方法,触发命令执行。
关键点代码解析
1、为什么Jdk7u21代码中map的key值要为f5a5a608?
经过我把f5a5a608这个字符串修改后发现,修改后就触发不了命令执行了。
最关键的原因是如果修改字符串,则压根进入不了下图的for循环
这个问题的答案还与为什么Jdk7u21的代码中的HashSet中需要放入两个对象有关(如下图)
我们把断点下到下图位置,然后开启debug模式
可以看到,第一次反序列化得到的e为TemplatesImpl的类,对应的是Jdk7u21代码中的set.add(templates)。
进入put方法后,算得hash为578003213,其在table数组中得序列为13(每次运行hash和序列数都不同)。然后f9直接运行到下一次得map.put位置,此时得e为Proxy对象
f7跟入put方法中,如果i和上一波得到得i相同,那么table[i]就不为空,否则为空。因此我们大致可以判断,f5a5a608是为了让proxy对象得到得i和TemplatesImpl对象得到得到的i相同。
具体跟入hash(key)的逻辑
继续跟入k.hashCode()
跟入this.hashCodeImpl(),var2为this.memberValues这个hashmap中的迭代器。var3为f5a5a608 -> {TemplatesImpl@761}
最后的返回值为var1,而var1为
1 | var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) |
首先计算(String)var3.getKey()).hashCode()
f7跟入var3.getKey获取到var3.getKey==”f5a5a608”
然后跟入”f5a5a608”.hashCode(),也就是String类的hashCode方法,最后算得其hashCode为0
而var1得值为
1 | 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) |
(String)var3.getKey()).hashCode()为0,0与任何数异或都为任何数其本身
而memberValueHashCode(var3.getValue)值为TemplateImpl.HashCode(),则var1得值为TemplateImpl.HashCode()
我们知道上一波得hash值就是为TemplateImpl.HashCode(),而这一波得hash值同样也是TemplateImpl.HashCode,所以其hash同样为578003213
hash不变,数组长度不变,所以序列也不变
因此顺利进入key.equals(k)和之前得分析接上了。而如果我们把“f5a5a608”这个字符串改了,那两波得hash会不同,就进不来for循环了。
后记
继续搞java,加油