Modify Ysoseriali jar serialVersionUID
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一致進行反序列。
類未定義serialVersionUID屬性
測試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的場景, 大概想了幾種方法。
1 反射
嘗試通過反射新增SUID屬性, 然後再修改屬性值為7387108436934414104。
但是翻了下文件, 沒看到反射能新增屬性這個操作, 就只有放棄了。
2 Hook
Hook computeDefaultSUID方法, 如果傳入的類是com.mchange.v2.c3p0.PoolBackedDataSource, 直接修改返回值為7387108436934414104。
但是找了一下 都沒找到個合適的能hook class的框架,
就直接用idea來”hook”了。
在computeDefaultSUID裡下個斷點,

把hash修改為7387108436934414104

就不會在出現SUID異常了。

3 修改位元組碼
直接使用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屬性
如果序列化的類顯示定義了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(); }