payloads/JRMPListener
JRMPListener的攻击流程:
1.生成payloads/JRMPListener发送给服务器,当服务器反序列化payloads/JRMPListener后,即会开启一个端口进行监听。
2.使用exploit/JRMPClient模块发送payload,服务器就会把payload进行反序列化,从而完成进行攻击。
直接通过ysoserial源码调试JRMPListener类来理解payloads/JRMP模块的利用原理
生成Payload
打开idea,设置启动参数,主类为ysoserial.GeneratePayload,参数为JRMPListener 1199
断点启动,GeneratePayload类中调用了JRMPListener的getObject方法
跟进JRMPListener#getObject
第一行jrmpPort获取了端口号1199
重点讲一下第二部分
46行new了一个UnicastServerRef对象并且把jrmpPort端口号传入了进去
然后调用了一个Reflections工具类构造了UnicastRemoteObject对象
Reflections构造对象
在前面调用了Reflections#createWithConstructor方法,利用ActivationGroupImpl.class、RemoteObject.class、RemoteRef.class、UnicastServerRef构造了一个对象
现在转到工具类Reflections.java
debug来解释下这个工具类的作用
1.获取有参构造方法
在这里获取了RemoteObject类的有参构造,参数为RemoteRef.class
2.暴力反射
setAccessible(objCons);
3.获得Constructor
在这里用了ReflectionFactory.newConstructorForSerialization来创建无参构造方法
下面用一个简单的demo来解释下这段代码的作用
ReflectionFactory.getReflectionFactory().newConstructorForSerialization()
新建一个Person类,创建其有参和无参构造方法
public class Person{
private String name;
public Person() {
System.out.println("Person无参构造方法");
}
public Person(String name) {
this.name = name;
System.out.println("调用有参构造" + name.toString());
}
}
新建一个User类,继承Person
public class User extends Person{
public void eat(){
System.out.println("eat...");
}
}
新建Test类
public class Test1 {
public static void main(String[] args) throws Exception {
//获得Person的有参构造函数
Constructor<Person> personConst = Person.class.getDeclaredConstructor(String.class);
//获得Person的Constructor对象
Constructor constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(
User.class,
personConst
);
//constructor.setAccessible(true);
//实例化对象,给person构造方法传入xxx,并转型成User
User user = (User) constructor.newInstance("xxx");
user.eat();
}
}
运行结果:
调用有参构造xxx
eat...
可以看到,虽然User类没有构造方法,但是依然能通过父类创建出来,并且调用其方法。
demo解读
解释下在这里newConstructorForSerialization()传入的参数
第一个参数是需要创建对象的Class,比如User.class
第二个参数是对象的父类的构造方法,比如Person.class.getDeclaredConstructor()或者Person.class.getDeclaredConstructor(String.class)
因为不能确定一个类中是否有无构造函数,所以在这里yososeroal中利用ReflectionFactory.newConstructorForSerialization创建对象的话,比直接反射创建对象适用性要高,并不会产生报错。
继续回到ysoserial源码中,就看的很明白了
ReflectionFactory.getReflectionFactory()获取了ReflectionFactory对象的实例
之后进行了newConstructorForSerialization()
传入的第一个值是ActivationGroupImpl.class,第二个则是它的父类RemoteObject的构造方法。以下是ActivationGroupImpl的继承关系图
4.创建出ActivationGroupImpl对象
最后一行把获取到的Constructor进行了newInstance,并且传入了consArgs,即UnicastServerRef对象
UnicastServerRef对象通过父类有参构造函数,赋值给了ref
然后对其向下转型成了ActivationGroupImpl类(T即为ActivationGroupImpl)
最后返回值就是一个ActivationGroupImpl对象了
回到JRMPListener.java,此时的uro为ActivationGroupImpl
跟进49行,这里又调用了Reflections类得getField方法,把UnicastRemoteObject中的port值设置成了jrmpPort,也就是1199。
最后返回的payload对象就是ActivationGroupImpl
然后在GeneratePayload中把ActivationGroupImpl进行了序列化操作
gadget链分析
以下是ysoserial给出给出得gadget链
* Gadget chain:
* UnicastRemoteObject.readObject(ObjectInputStream) line: 235
* UnicastRemoteObject.reexport() line: 266
* UnicastRemoteObject.exportObject(Remote, int) line: 320
* UnicastRemoteObject.exportObject(Remote, UnicastServerRef) line: 383
* UnicastServerRef.exportObject(Remote, Object, boolean) line: 208
* LiveRef.exportObject(Target) line: 147
* TCPEndpoint.exportObject(Target) line: 411
* TCPTransport.exportObject(Target) line: 249
* TCPTransport.listen() line: 319
在前面生成payload得时候返回的是ActivationGroupImpl对象,但是其对象本身是没有readObject方法的,所以从父类找readObject最终找到UnicastRemoteObject
攻击流程:
payload发送给服务器,服务器会对其进行反序列化,触发UnicastRemoteObject#readObject
进入reexport
这里执行了exportObject,传入了ActivationGroupImpl对象和port
继续跟进到UnicastServerRef#exportObject,这里后面就是之前分析RMI时候操作了。
在this.ref.exportObject执行后会最终到TCPTransport类的exportObject方法
跟进listen
调用TCPEndpoint#newServerSocket方法,会开启端口监听
如果服务器反序列化了该payload,就会开启一个监听。
这里就做了一个很简单的调试,更具体的可以看我之前写的文章,RMI源码调试
参考链接:
https://www.cnblogs.com/nice0e3/p/14333695.html
https://blog.csdn.net/whatday/article/details/106971531