日志注入¶
ID: java/log-injection
Kind: path-problem
Security severity: 7.8
Severity: error
Precision: medium
Tags:
- security
- external/cwe/cwe-117
Query suites:
- java-security-extended.qls
- java-security-and-quality.qls
如果将未经清理的用户输入写入日志条目,恶意用户可能能够伪造新的日志条目。
如果用户提供了一些输入,创建多个日志条目的外观,则可能会发生伪造。这可能包括未转义的新行字符或 HTML 或其他标记。
建议¶
在记录用户输入之前,应适当对其进行清理。
如果日志条目是纯文本,则应从用户输入中删除换行符,例如使用 String replace(char oldChar, char newChar)
或类似的方法。还应注意在日志条目中清楚地标记用户输入,并且恶意用户无法以其他方式造成混淆。
对于将在 HTML 中显示的日志条目,应在记录之前对用户输入进行 HTML 编码,以防止伪造和其他形式的 HTML 注入。
示例¶
在第一个示例中,由用户提供的用户名使用 logger.warn
(来自 org.slf4j.Logger
)记录。在第一种情况下(/bad
端点),用户名在未进行任何清理的情况下被记录。如果恶意用户提供 Guest'%0AUser:'Admin
作为用户名参数,则日志条目将被分成两行,其中第一行将是 User:'Guest'
,第二行将是 User:'Admin'
。
package com.example.restservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogInjection {
private final Logger log = LoggerFactory.getLogger(LogInjection.class);
// /bad?username=Guest'%0AUser:'Admin
@GetMapping("/bad")
public String bad(@RequestParam(value = "username", defaultValue = "name") String username) {
log.warn("User:'{}'", username);
// The logging call above would result in multiple log entries as shown below:
// User:'Guest'
// User:'Admin'
return username;
}
}
在第二个示例(/good
端点)中,使用 matches()
来确保用户输入仅包含字母数字字符。如果恶意用户提供 `Guest’%0AUser:’Admin` 作为用户名参数,则根本不会记录日志条目,从而防止注入。
package com.example.restservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogInjection {
private final Logger log = LoggerFactory.getLogger(LogInjection.class);
// /good?username=Guest'%0AUser:'Admin
@GetMapping("/good")
public String good(@RequestParam(value = "username", defaultValue = "name") String username) {
// The regex check here, allows only alphanumeric characters to pass.
// Hence, does not result in log injection
if (username.matches("\\w*")) {
log.warn("User:'{}'", username);
return username;
}
}
}