一、Log Forging是什么?
Log Forging的中文翻译为日志伪造,是指在系统后台输出的日志中含有前端用户输入的内容,用户可以输入特定的符号或者代码,从而篡改日志中原本应该输出的正常内容。
我们先来看下如下的代码:
public void getDemo(HttpServletRequest req, HttpServletResponse resp){
String uri = req.getRequestURI();
log.info("get request uri : {}", uri);
String requestParams = req.getQueryString();
log.info("request query param : {}", requestParams);
...
}
这段代码在进行Fortify扫描的时候,就被告知含有Log Forging漏洞,我们因此可以得出,凡是从ServletRequest中获取的参数值都是有可能被篡改的,如果程序不做任何校验就直接保存到日志中,就有可能引起日志信息的不真实与不准确。
二、 Log Forging漏洞有什么危害?
我们先来看看Fortify在扫描出该漏洞时是怎么描述的:
...将未验证的用户输入写入日志,攻击者可以利用这一行为来伪造日志条目或者将恶意内容注入日志。比如,用户输入了如下的字符串
twenty-one%0aINFO:+User+logged+out%3dbadguy
,那么原先之应该输出为一行的日志将输出为如下的两行信息:INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
因此,日志篡改的危害主要就是影响系统日志记录的真实性和准确性,从而妨碍开发者对日志的解读。
三、 怎么修复Log Forging漏洞?
3.1 遵从Fortify的建议
不要将用户输入的内容记录在日志中,特别实在抛出异常的时候。需要自定义异常提示信息。下面是官方给出的示例:
public static final String NFE = "Failed to parse val. The input is required to be an integer value.";
...
String val = request.getParameter("val");
try{
int value = Integer.parseInt(val);
} catch(NumberFormatException nfe) {
log.info(NFE);
}
可以看到,缺点很明显,查看日志的时候,我们根本无法的hi用户真实输入的内容到底是什么。
3.2 替换特殊字符
恶意用户用来篡改日志的方式目前已知的就是通过输入回车或者换行符,因此我们需要在打印用户输入的内容时,对其进行特殊字符的过滤,如下代码所示:
public static String convertValidLog(String log){
List<String> list = new ArrayList<String>();
list.add("%0d");
list.add("\r");
list.add("%0a");
list.add("\n");
// 将日志内容归一化
String encode = Normalizer.normalize(log, Normalizer.Form.NFKC);
for(String toReplaceStr : list){
encode = encode.replace(toReplaceStr, "");
}
return encode;
}
@Test
public void convertValidLog(){
String dangerousLogStr = "this %0d is \r an %0a apple \n .";
String safeLogStr = "this is an apple .";
assertNotEquals(dangerousLogStr, safeLogStr);
assertEquals(FortifyUtils.convertValidLog(dangerousLogStr), safeLogStr);
}
这种方式就要求开发者在记录用户输入内容的时候都要调用这个方法,比较容易遗漏,而且略显繁琐。
当然,以后可能还会出现新的会篡改日志的字符,那么就可以在如上的list中增加一个需要被替换的即可。
3.3 对日志内容编码保存
既然我们知道用户可能会输入篡改日志的字符,那么在记录这类日志的时候我们就可以将用户输入的内容进行再次编码,如此日志中记录的就是真实的用户输入,而且也不会出现日志被篡改的问题了。如下代码所示:
public static void main(String[] args){
String userInput = "this is an \n apple.";
log.info("user input is : {}", userInput);
String encodeStr = "";
try{
encodeStr = URLEncoder.encode(userInput, "utf-8");
} catch (UnsupportedEncodingException e){
e.printStackTrace();
}
log.info("encoded user input is : {}", encodeStr);
}
如此,在日志中输出的内容将是这样的:
user input is : this is an
apple.
encoded user input is : this+is+an+%0A+apple.
缺点也很明显,你需要将日志信息使用解码工具解码后才能知道用户到底输入的真实内容是什么。
四、 总结
Log Forging漏洞时High级别的,建议大家可以根据以上的建议合理地选择一个进行修复。如果按照上述的建议进行修复后,Fortify扫描仍然报错,那么也可以直接忽略掉。
全文完。