###5.6 日志伪造漏洞

概述

应用程序通常使用日志文件来存储事件或事务的历史,以供以后查看、收集统计信息或调试。根据应用程序的性质,检查日志文件的任务可以根据需要手动执行,也可以使用工具自动筛选重要事件或趋势信息的日志。

当日志条目包含未经过授权的用户输入时,会造成日志伪造。

实例

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中获取的参数值都是有可能被篡改的,如果程序不做任何校验就直接保存到日志中,就有可能引起日志信息的不真实与不准确。

构造条件

满足以下条件,就构成了一个日志伪造的安全漏洞:

1、数据是从不可靠的来源(包括但不局限于不可靠用户的输入信息或是不可靠用户可能更改的文件)进入应用程序;
2、数据写入到应用程序或是系统日志文件。

影响后果

关键词:修改应用程序的数据;隐藏活动;执行未授权的代码或命令

如果攻击者向记录到日志文件的应用程序提供恶意数据,则可能会妨碍或误导日志文件的解读。最理想的情况是,攻击者可能通过向应用程序提供包括适当字符的输入,在日志文件中插入错误的条目。如果日志文件是自动处理的,那么攻击者可以破坏文件格式或注入意外的字符,从而使文件无法使用。通过伪造或其他方式,可能会导致日志文件中的统计信息发生偏差,受到破坏的日志文件可用于掩护攻击者的跟踪轨迹,甚至还可以牵连第三方来执行恶意行为。最糟糕的情况是,攻击者可能向日志文件注入代码或者其他命令,利用日志处理实用程序中的漏洞。

补救方式

遵从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用户真实输入的内容到底是什么。

替换特殊字符

恶意用户用来篡改日志的方式目前已知的就是通过输入回车或者换行符,因此我们需要在打印用户输入的内容时,对其进行特殊字符的过滤,如下代码所示:

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中增加一个需要被替换的即可。

对日志内容编码保存

既然我们知道用户可能会输入篡改日志的字符,那么在记录这类日志的时候我们就可以将用户输入的内容进行再次编码,如此日志中记录的就是真实的用户输入,而且也不会出现日志被篡改的问题了。如下代码所示:

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.

缺点也很明显,你需要将日志信息使用解码工具解码后才能知道用户到底输入的真实内容是什么。

作者:天下兵马大都督  创建时间:2022-05-19 09:36
 更新时间:2023-04-12 16:17