日志注入问题(log4j2)

问题复现

log4f.xml 配置快照

<Property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] [%thread] [%file:%line] → [%m]%n"/>
<PatternLayout pattern="${LOG_PATTERN}"/>

代码验证

log.info("Injection1\n\n测试日志注入1");
System.out.println("处理逻辑1");
log.info("Injection2\r\r测试日志注入2");
System.out.println("处理逻辑2");
log.info("Injection3\n\r\n\r测试日志注入3");

验证结果:发现日志被注入

[2023-04-05 08:48:19] [INFO ] [main] [Log_Injection.java:8][Injection1

测试日志注入1]
处理逻辑1
测试日志注入2]
处理逻辑2
[2023-04-05 08:48:19] [INFO ] [main] [Log_Injection.java:12][Injection3

测试日志注入3]

方案1:追加净化方法

/**
 * 获取净化后的消息,过滤掉换行,避免日志注入
 */
public static String cleanMsg(String message) {
    if (message == null) {
        return "";
    }
    message = message.replace('\n', '_').replace('\r', '_');
    return message;
}

代码验证

String s = "Injection1\n\n测试日志注入1";
String s1 = "Injection2\r\r测试日志注入2";
String s2 = "Injection3\n\r\n\r测试日志注入3";
log.info(s);
System.out.println("处理逻辑1");
log.info(s1);
System.out.println("处理逻辑2");
log.info(s2);
System.out.println("--------------------------------");
log.info(cleanMsg(s));
System.out.println("处理逻辑3");
log.info(cleanMsg(s1));
System.out.println("处理逻辑4");
log.info(cleanMsg(s2));

验证结果:\n\r被替换为_,日志不会被注入

[2023-04-05 09:12:28] [INFO ] [main] [Log_Injection.java:12][Injection1

测试日志注入1]
处理逻辑1
测试日志注入2]
处理逻辑2
[2023-04-05 09:12:28] [INFO ] [main] [Log_Injection.java:16][Injection3

测试日志注入3]
--------------------------------
[2023-04-05 09:12:28] [INFO ] [main] [Log_Injection.java:20][Injection1__测试日志注入1]
处理逻辑3
[2023-04-05 09:12:28] [INFO ] [main] [Log_Injection.java:22][Injection2__测试日志注入2]
处理逻辑4
[2023-04-05 09:12:28] [INFO ] [main] [Log_Injection.java:24][Injection3____测试日志注入3]

优劣:

优:确实会避免日志注入
劣:代码冗余+代码泛滥


方案2:%m → %enc{%m}{CRLF}

方案原理:

利用Pattern Layout 提供的标签:enc
enc可以处理4中格式的转义:{[HTML|XML|JSON|CRLF]},默认进行HTML转义。
参考 :log4j2中Pattern Layout 对消息体转义

修改log4f.xml 配置

<Property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] [%thread] [%file:%line] → [%enc{%m}{CRLF}]%n"/>
<PatternLayout pattern="${LOG_PATTERN}"/>

代码验证

String s = "Injection1\n\n测试日志注入1";
String s1 = "Injection2\r\r测试日志注入2";
String s2 = "Injection3\n\r\n\r测试日志注入3";
log.info(s);
System.out.println("处理逻辑1");
log.info(s1);
System.out.println("处理逻辑2");
log.info(s2);
System.out.println("--------------------------------");
log.info(cleanMsg(s));
System.out.println("处理逻辑3");
log.info(cleanMsg(s1));
System.out.println("处理逻辑4");
log.info(cleanMsg(s2));

验证结果:

enc 会对 CRLF 进行转义,从而避免日志注入

[2023-04-05 09:26:34] [INFO ] [main] [Log_Injection.java:12][Injection1\n\n测试日志注入1]
处理逻辑1
[2023-04-05 09:26:34] [INFO ] [main] [Log_Injection.java:14][Injection2\r\r测试日志注入2]
处理逻辑2
[2023-04-05 09:26:34] [INFO ] [main] [Log_Injection.java:16][Injection3\n\r\n\r测试日志注入3]
--------------------------------
[2023-04-05 09:26:34] [INFO ] [main] [Log_Injection.java:20][Injection1__测试日志注入1]
处理逻辑3
[2023-04-05 09:26:34] [INFO ] [main] [Log_Injection.java:22][Injection2__测试日志注入2]
处理逻辑4
[2023-04-05 09:26:34] [INFO ] [main] [Log_Injection.java:24][Injection3____测试日志注入3]

优劣:

优:确实会避免日志注入,而且通过修改配置,避免了代码冗余和代码泛滥
劣:日志文件里依旧会有\n\r,如果我们的日志需要被日志可视化服务读取,他们可能会被我们日志注入,这种直观看来感觉就是我们写入日志出问题。

方案3:%m → %replace{%enc{%m}{CRLF}}{\r|\n|%0D|%0A|%0a|%0d}{}

方案原理:

condition1:

在上述%enc{%m}{CRLF}转义的基础上,把\n\r给替换为空

condition2:

replace{pattern}{regex}{substitution}:将 pattern 中匹配 regex 正则的部分替换为 substitution
例如:

  • %replace{%msg}{\s}{} , 删除 msg 中的所有空白
  • %replace{%logger %msg}{.}{/}, 将logger和msg中的所有点都替换为斜杠
  • %replace{%msg}{"}{'}, 将msg中的双引号替换为单引号,
  • %replace{%xEx{separator(|)}}{\t}{}用 | 做换行符,并删除其中的 \t 制表符

参考:Apache-Log4j → %replace 替换

condition3:

Remove CR and LF characters to prevent CRLF injection

  • location.replaceAll(“(\\r|\\n|%0D|%0A|%0a|%0d)”, “”);

参考:org.owasp.csrfguard.http.InterceptRedirectResponse

修改log4f.xml 配置

Property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] [%thread] [%file:%line] → [%replace{%enc{%m}{CRLF}}{\\r|\\n|%0D|%0A|%0a|%0d}{}]%n"/>
<PatternLayout pattern="${LOG_PATTERN}"/>

代码验证

String s = "Injection1\n\n测试日志注入1";
String s1 = "Injection2\r\r测试日志注入2";
String s2 = "Injection3\n\r\n\r测试日志注入3";
log.info(s);
System.out.println("处理逻辑1");
log.info(s1);
System.out.println("处理逻辑2");
log.info(s2);
System.out.println("--------------------------------");
log.info(cleanMsg(s));
System.out.println("处理逻辑3");
log.info(cleanMsg(s1));
System.out.println("处理逻辑4");
log.info(cleanMsg(s2));

验证结果:

[2023-04-05 10:15:11] [INFO ] [main] [Log_Injection.java:12][Injection1测试日志注入1]
处理逻辑1
[2023-04-05 10:15:11] [INFO ] [main] [Log_Injection.java:14][Injection2测试日志注入2]
处理逻辑2
[2023-04-05 10:15:11] [INFO ] [main] [Log_Injection.java:16][Injection3测试日志注入3]
--------------------------------
[2023-04-05 10:15:11] [INFO ] [main] [Log_Injection.java:20][Injection1__测试日志注入1]
处理逻辑3
[2023-04-05 10:15:11] [INFO ] [main] [Log_Injection.java:22][Injection2__测试日志注入2]
处理逻辑4
[2023-04-05 10:15:11] [INFO ] [main] [Log_Injection.java:24][Injection3____测试日志注入3]

优劣:

优:避免了日志注入,且不会对后续服务进行注入
劣:会把\n\r给完全删除了


参考:

Java云服务开发安全问题解析——日志注入,并没那么简单

你可能感兴趣的:(日志,log4j,java,jvm)