点击蓝字 / 关注我们
关于java代理
代理模式是常用的 java 设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
这里需要关注的重点有如下几点 – 代理类与委托类有同样的接口 – 代理类主要负责为委托类预处理消息、过滤消息等简而言之经过代理的类方法被调用后会先经过代理类的处理。 – 一个代理类的对象与一个委托类的对象关联
从这里你能够发现其实实现代理模式需要三个东西:一个公共接口,一个具体的类,一个代理类,代理类持有具体类的实例,代为执行具体类实例方法。
下面是最常见的两种代理模式
静态代理
这种代理方式需要代理对象和目标对象实现一样的接口。但是当需要代理的对象过多就需要实现大量的代理类,并且一旦接口增加方法,目标对象与代理对象都要进行修改。
举个例子学生交作业,一般都是通过学生先交给课代表然后课代表交给老师这种模式,课代表在这里就相当于是一个学生代理类。
首先你需要一个公共代理接口,这个接口就是学生(被代理类),和课代表(代理类)的公共接口
public interface Event {
void SubmitWork();
}
然后你需要一个学生类,这是被代理类。
public class Student implements Event{
String name;
public Student(String n) {
this.name = n;
}
@Override
public void SubmitWork() {
System.out.println(this.name + "提交作业");
}
}
最后你需要一个代理类
package test;
public class StudentInnovation implements Event{
Student student;
int count = 0; //收到的作业数量
public StudentInnovation(Student stu){
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.student = (Student)stu;
}
}
public void setStudent(Student student) {
this.student = student;
}
@Override
public void SubmitWork() {
this.student.SubmitWork();
this.count += 1;
System.out.println("已收作业数量为" + this.count);
}
}
有了这三样东西你就能实现一个简单的代理。简单测试一下。
package test;
public class main {
public static void main(String[] args) {
//被代理的学生张三,他的作业提交由代理对象monitor(课代表)完成
Student s1 = new Student("张三");
Student s2 = new Student("李四");
Student s3 = new Student("王五");
//生成代理对象,并将张三传给代理对象
StudentInnovation monitor = new StudentInnovation(s1);
//向课代表提交作业
monitor.SubmitWork();
monitor.setStudent(s2);
monitor.SubmitWork();
monitor.setStudent(s3);
monitor.SubmitWork();
}
}
但是你能很明显的感受这样的模式完成代理一个类是很容易的,但如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。并且当接口被改变代理类同样需要改变,这样就产生了更大的局限性和更多麻烦。这就需要动态代理来解决问题了。
动态代理
动态代理和静态代理一样也需要三样东西:公共接口,代理对象,代理类。区别就是动态代理是利用反射机制在运行时创建代理类。
例如这里代理对象类的接口类不变,代理类这样实现。这里使用了jdk原生自带的InvocationHandler
,这个类也是文章后面会重点使用到的利用类。这样写就不再局限于一种接口了。
package test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler {
private Object object;
int count = 0; //收到的作业数量
public void setStudent(Student student) {
this.object = student;
}
public ProxyHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(object, args);
this.count += 1;
System.out.println("已收作业数量为" + this.count);
return null;
}
}
进行测试
public class main {
public static void main(String[] args) {
//被代理的学生张三,他的作业提交由代理对象monitor(课代表)完成
Student s1 = new Student("张三");
InvocationHandler handler = new ProxyHandler(s1);
Event proxyHello = (Event) Proxy.newProxyInstance(s1.getClass().getClassLoader(), s1.getClass().getInterfaces(), handler);
proxyHello.SubmitWork();
}
}
动态代理简单利用
而有了代理如何进行利用呢,这里简单搭建一个攻击场景。
首先有两个公共接口
package test;
public interface Teacher {
Object getObject();
void attack();
}
有一个实现公共接口的类A和一个后门类
package test;
public class A implements Teacher{
Object object;
@Override
public Object getObject() {
return null;
}
@Override
public void attack() {
System.out.println("attack");
}
}
有一个后门类,例如这样。
package test;
import java.io.IOException;
public class Backdoor implements Teacher{
@Override
public Object getObject() {
return null;
}
@Override
public void attack() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
有一个可利用的代理类myProxy
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class myProxy implements InvocationHandler {
private Object object;
public myProxy(Object o){
this.object = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return this.object;
}
}
有一个代理类
package test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler {
private A object;
public ProxyHandler(Object object){
this.object = (A) object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method is " + method.getName());
method.invoke(this.object.getObject(), args);
return null;
}
}
很明显我们最终的目的是需要调用到后门类里面的attack方法。这里我们能够控制ProxyHandler.invoke()
中的method为attack方法名,因为Teacher接口里面也有attack方法。
所以关键就是让this.object.getObject()
能够返回一个Backdoor后门类实例对象就能完成attack方法调用。所以怎么做呢这里的通常做法就是能够寻找到一个新的代理,我们能够控制这个代理的invoke返回对象,然后用它来代理ProxyHandler中的object,当调用到this.object.getObject()
进入到我们找的可利用的代理对象中控制返回对象为一个Backdoor后门类实例。所以很明显这里的myProxy
就是那个新的代理。
最终的利用代码如下
public class main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
A t = new A();
Backdoor backdoor = new Backdoor();
InvocationHandler backdoorhandler = new myProxy(backdoor);
Teacher proxyInstance = (Teacher) Proxy.newProxyInstance(backdoor.getClass().getClassLoader(), new Class[]{Teacher.class}, backdoorhandler);
InvocationHandler handler = new ProxyHandler(t);
Field field = handler.getClass().getDeclaredField("object");
field.setAccessible(true);
field.set(handler,proxyInstance);
Teacher proxyHello = (Teacher) Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), handler);
proxyHello.attack();
}
}
关于题目
此题最终是4解,最终的做法也是将java动态代理运用的很巧妙,很有趣的一道题。
题目描述和提示
The server resets every 5 minutes hint 1: https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf page 50 hint 2: https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/Spring1.java
题目给了一个rmi服务,但是题目的远程服务端绑定死了,所以即使控制了lookup的参数也无法进行jndi。
但是如果能够在rmi服务端绑定我们的恶意对象,然后恶意对象的地址只想我们的恶意服务(例如jrmp listener),然后在注册端lookup这个对象然后会在我们的恶意服务端返回序列化好的数据让题目客户端反序列化即可进行rce。
题目给的两个hint很明显契合了这个做法,第一个hint用来绕过高版本jdk限制除本地服务外的其它连接来注册对象。第二个hint用来完成进行rce的反序列化链。
注册恶意对象
工具一把梭https://github.com/qtc-de/remote-method-guesser
绑定之后,起一个JRMPListener服务使用URLDNS的链子,然后请求我们注册的恶意对象,可以看到题目客户端成功将URLDNS的链子反序列化,很明显此做法是可行的,所以剩下需要做的就是找出找出一条链子进行rce。
反序列化链的尝试
这个是这个题目的难点,题目给的提示是spring1的链子,所以必然需要先了解这条链子。
关于spring1
这条链子在JDK 8u66之前是可以使用的。
链子调用如下图,最终利用TemplatesImpl.newTransformer()来实例化恶意字节码,链子主要靠的是使用InvocationHandler层层代理。
使用ysoserial项目调试。程序反序列化的入口为org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
此时可以看到总共是有三层代理的。
在this.provider.getType().getClass()
存在第一层代理,在invoke里会返回一个org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler
代理,这个代理的invoke是最终执行命令的方法。
从上图也可以看到最后一层代理是代理objectFactory的getObject方法,然后返回一个templates,此时method又被设置为newTransformer,所以就能成功实例化恶意字节码。
接着往下调试,第一次代理返回就是上面所说的AutowireUtils$ObjectFactoryDelegatingInvocationHandler
然后在ReflectionUtils.findMethod
中会获取到newTransformer方法,因为代理类实例化时传入了接口。
然后还会调用一次getType,调用过程和第一次是一样的返回对象也是一样的,都是返回了一个代理对象。 重要的是在调用ReflectionUtils.invokeMethod
时,可以看到此时的method已经被代理
所以会再次跳转到代理类的invoke,这里就是我们最终能够成功调用templates的newtransformer的地方。可以看到这里调用到了getObject方法,但是objectFactory已经被代理所以这里的getObject方法返回的类也可以被控制,让其返回一个templatesImpl实例对象即可。
进入到getObject代理中,看到从HashMap中取出来templatesImpl实例对象 。
后面就是经典的利用templatesImpl实例化恶意字节码来进行rce的做法了。
回到题目,比赛时错误的做法。
说是错误做法就是没有注意到题目jdk版本导致写出来的链子有个类在jdk202下被更改过了导致无法使用,不过还是值得记录一下。
题目的readObject入口是这样的,和spring1的链子对比一下,可以看到几乎一摸一样。这里的getGirlFriend
就相当于时getType
方法
同时看到题目提供的一些接口,几乎和spring1如出一辙,区别就是这些都是出题者自己实现的。
但是相比于spring1还差一个可以最终rce的反射调用方法。再看代码发现出题者实现了MyInvocationHandler
,所以很明显需要用的都提供了剩下的就是改改实例化类的名字了。
exp代码如下 “`java= package ysoserial.payloads;
import com.ctf.threermi.*; import org.springframework.beans.factory.ObjectFactory; import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.JavaVersion; import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.Reflections;
import javax.xml.transform.Templates; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Type;
import static java.lang.Class.forName;
public class TCTF3rmi extends PayloadRunner implements ObjectPayload
跳跳糖是一个安全社区,旨在为安全人员提供一个能让思维跳跃起来的交流平台。
原文始发于微信公众号(跳跳糖社区):从TCTF的3rm1学习java动态代理