Java安全之原生readObject方法解读
Java安全之原生readObject方法解读
0x00 前言
在上篇文章分析shiro中,遇到了Shiro重写了ObjectInputStream
的resolveClass
导致的一些基于InvokerTransformer
去实现的利用链没法使用,因为这需要去定义一个InvokerTrans
数组,而该数组传入到Shiro重写后的resolveClass
方法中会报错。但是在此之前,并没有去对readObject
方法去做一个解读和分析。所以也不知道他具体的实现。包括在分析利用链的时候,只知道到调用了ObjectInputStream.readObject
方法后,如果readObject
被重写的话,就会调用重写后的readObject
方法,但是我们也并不知道在内部是怎么样去做一个实现的。那么下面来分析一下readObject
的功能实现。
0x01 readObject方法分析
在前面先贴一张readObject
的执行流程图,这是一张weblogic的反序列化执行流程图。第一个readObject
直接忽略,到下篇文weblogic再做讲解。
这里写一段测试代码去进行反序列化操作,然后进行动态跟踪。
User实体类:
package com.nice0e3;
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
ReadTest类:
package com.nice0e3;
import java.io.*;
public class ReadTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User();
user.setName("nice0e3");
user.setAge(20);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(user);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.txt"));
Object o = ois.readObject();
}
}
然后将断点落在ObjectInputStream.readObject
方法中,进行执行测试类代码动态跟踪。
这里对enableOverride
进行了一个判断,不为flase的话就会去返回readObjectOverride
方法,而在构造方法中就定义该值为flase。
下面就直接执行到了这步
调用了readObject0
方法,选择跟进查看一下内部的实现。
在这里会去获取序列化信息第一个字节,如果为TC_RESET
就会调用bin.readByte()
和handleReset();
方法。
查看TC_RESET
内容。
而该值转换Byte后,为121,我们序列化数据的第一个字节为151,这里就跳过不执行了。
接下来代码中定义了一个switch去做一个判断,TC_OBJECT
的值转换后刚刚好为115。那么就会执行到这一步。
在这里面会调用readOrdinaryObject
方法,进行跟进。
在该方法中还会去调用readClassDesc
方法,继续跟进。
看到这里发现就很有意思了,获取我们序列化数据的第二个字节,然后又进行一次switch,这次走到了readNonProxyDesc
方法中,跟进!
在这又调用了resolveClass
方法然后传入readDesc
参数。还是跟进方法。
这里返回了
Class.forName(name, false, latestUserDefinedLoader());
latestUserDefinedLoader()
方法返回的是sun.misc.VM.latestUserDefinedLoader()
说明指定了该加载器。
返回到readOrdinaryObject
方法中继续做分析。
直接定位到这一步,该方法对反序列化的操作进行实现。
这里的slotDesc.hasReadObjectMethod()
获取的是readObjectMethod
这个属性,如果反序列化的类没有重写readobject()
,那么readObjectMethod
这个属性就是空,如果这个类重写了readobject()
,那么就会进入到if之中的
slotDesc.invokeReadObject(obj, this);
如果readobject()
方法被重写则是走到这一步
0x02 Shiro resolveClass方法分析
在shiro里面resolveClass
方法被进行了重写,导致大部分利用链都使用不了,查看一下该方法实现。
这里去调用了ClassUtils.forName
方法进行跟踪。
这里是调用了THREAD_CL_ACCESSOR.loadClass
,查看一下THREAD_CL_ACCESSOR
是什么。
跟进查看一下该类。
这里调用getClassLoader
方法获取类加载器,而在这里获取到的是ParallelWebappClassLoader
,那么下面调用的肯定也就是ParallelWebappClassLoader.loadClass
上面内容中的一些补充:
TC_NULL描述符表示空对象引用
TC_REFERENCE描述符表示引用已写入流的对象
TC_PROXYCLASSDESC是新的代理类描述符
TC_CLASSDESC是新的类描述符
参考文章
https://blog.csdn.net/niexinming/article/details/106665753
https://www.anquanke.com/post/id/192619#h2-2
0x03 结尾
其实在前面的一些cc链的调试铺垫下,再去调试其他的一些漏洞,都会比较熟练。本文也是为了下文去做了一个较好的铺垫。