问题复现
log4j.xml 配置快照
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info" monitorInterval="10">
<properties>
<Property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] [%thread] [%file:%line] → [%m]%n"/>
</properties>
<appenders>
<console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Filters>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</console>
</appenders>
<loggers>
<root level="all">
<AppenderRef ref="CONSOLE"/>
</root>
</loggers>
</configuration>
代码验证:发现日志被注入
String s1 = "Injection1\n\n测试日志注入1";
String s2 = "Injection2\n\r测试日志注入2";
String s3 = "Injection3\n\r\n\r测试日志注入3";
log.info(s1);
System.out.println("---------------上下文分隔符-----------------");
log.info(s2);
System.out.println("---------------上下文分隔符-----------------");
log.info(s3);
//验证结果
[2023-12-05 23:37:41] [INFO ] [main] [Log_Injection.java:12] → [Injection1
测试日志注入1]
---------------上下文分隔符-----------------
[2023-12-05 23:37:41] [INFO ] [main] [Log_Injection.java:14] → [Injection2
测试日志注入2]
---------------上下文分隔符-----------------
[2023-12-05 23:37:41] [INFO ] [main] [Log_Injection.java:16] → [Injection3
测试日志注入3]
规避方案
方案1:追加净化方法
方案原理:通过追加方法把 CRLF替换掉
public class logUtils {
/**
* 获取净化后的消息,过滤掉换行,避免日志注入
*/
public static String cleanMsg(String message) {
if (message == null) {
return "";
}
message = message.replace('\n', '_').replace('\r', '_');
return message;
}
}
代码验证:\n\r被替换为_,日志不会被注入
String s1 = "Injection1\n\n测试日志注入1";
String s2 = "Injection2\n\r测试日志注入2";
String s3 = "Injection3\n\r\n\r测试日志注入3";
log.info(cleanMsg(s1));
System.out.println("---------------上下文分隔符-----------------");
log.info(cleanMsg(s2));
System.out.println("---------------上下文分隔符-----------------");
log.info(cleanMsg(s3));
//验证结果
[2023-12-05 23:42:52] [INFO ] [main] [Log_Injection.java:20] → [Injection1__测试日志注入1]
---------------上下文分隔符-----------------
[2023-12-05 23:42:52] [INFO ] [main] [Log_Injection.java:22] → [Injection2__测试日志注入2]
---------------上下文分隔符-----------------
[2023-12-05 23:42:52] [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"/>
代码验证:enc 会对 CRLF 进行转义,从而避免日志注入
String s1 = "Injection1\n\n测试日志注入1";
String s2 = "Injection2\n\r测试日志注入2";
String s3 = "Injection3\n\r\n\r测试日志注入3";
log.info(s1);
System.out.println("---------------上下文分隔符-----------------");
log.info(s2);
System.out.println("---------------上下文分隔符-----------------");
log.info(s3);
//验证结果
[2023-12-05 23:47:23] [INFO ] [main] [Log_Injection.java:12] → [Injection1\n\n测试日志注入1]
---------------上下文分隔符-----------------
[2023-12-05 23:47:23] [INFO ] [main] [Log_Injection.java:14] → [Injection2\n\r测试日志注入2]
---------------上下文分隔符-----------------
[2023-12-05 23:47:23] [INFO ] [main] [Log_Injection.java:16] → [Injection3\n\r\n\r测试日志注入3]
优劣:
优:确实会避免日志注入,而且通过修改配置,避免了代码冗余和代码泛滥
劣:日志文件里依旧会有\n\r
,如果我们的日志需要被日志可视化服务读取,他们可能会被我们日志注入,这种直观看来感觉就是我们写入日志出问题。
方案3:%m → %replace{%enc{%m}{CRLF}}{\r|\n|%0D|%0A|%0a|%0d}{}
方案原理:
主体思路:
在上述
%enc{%m}{CRLF}
转义的基础上,把\n\r
给替换为空
替换操作语法格式:
replace{pattern}{regex}{substitution}
:将 pattern 中匹配regex
正则的部分替换为substitution
例如:
%replace{%msg}{\s}{}
, 删除 msg 中的所有空白%replace{%msg}{\.}{/}
, 将logger和msg中的所有点都替换为斜杠%replace{%msg}{\"}{'}
, 将msg中的双引号替换为单引号,%replace{%xEx{separator(|)}}{\t}{}
用 | 做换行符,并删除其中的 \t 制表符
CRLF转义后的枚举值列举:
Remove
CR
andLF
characters to preventCRLF
injection
location.replaceAll("(\\r|\\n|%0D|%0A|%0a|%0d)", "");
修改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"/>
代码验证:CRLF 被置换为空
String s1 = "Injection1\n\n测试日志注入1";
String s2 = "Injection2\n\r测试日志注入2";
String s3 = "Injection3\n\r\n\r测试日志注入3";
log.info(s1);
System.out.println("---------------上下文分隔符-----------------");
log.info(s2);
System.out.println("---------------上下文分隔符-----------------");
log.info(s3);
//验证结果
[2023-12-05 23:50:48] [INFO ] [main] [Log_Injection.java:12] → [Injection1测试日志注入1]
---------------上下文分隔符-----------------
[2023-12-05 23:50:48] [INFO ] [main] [Log_Injection.java:14] → [Injection2测试日志注入2]
---------------上下文分隔符-----------------
[2023-12-05 23:50:48] [INFO ] [main] [Log_Injection.java:16] → [Injection3测试日志注入3]
优劣:
优:避免了日志注入,且不会对后续服务进行注入
劣:会把\n\r
给完全删除了