如何修复Veracode CWE 117(日志输出中和不当)

有一个Spring全局@ExceptionHandler(Exception.class)方法,它记录exception,如下所示:

 @ExceptionHandler(Exception.class) void handleException(Exception ex) { logger.error("Simple error message", ex); ... 

Veracode扫描表明此日志记录具有Improper Output Neutralization for Logs并建议使用ESAPI记录器。 有没有办法在不将记录器更改为ESAPI的情况下修复此漏洞? 这是我遇到此问题的唯一代码,我试图找出如何以最小的更改来修复它。 也许ESAPI有一些我没有注意到的方法?

PS当前记录器是slf4j上的Log4j

UPD:最后我使用了ESAPI记录器。 我以为它不会使用我的默认日志记录服务,但我错了,它只是使用我的slf4j logger接口和适当的配置。

 private static final Logger logger = ESAPI.getLogger(MyClass.class); ... logger.error(null, "Simple error message", ex); 

ESAPI具有log4j记录器和记录器工厂的扩展。 可以配置在ESAPI.properties中使用的内容。 例如:

 ESAPI.Logger=org.owasp.esapi.reference.Log4JLogFactory 

有没有办法在不将记录器更改为ESAPI的情况下修复此漏洞?

简而言之,是的。

TLDR:

首先要了解错误的严重性。 主要关注的是伪造日志声明。 假设你有这样的代码:

 log.error( transactionId + " for user " + username + " was unsuccessful." 

如果任一变量在用户控制下,他们可以通过使用\r\n for user foobar was successful\rn类的输入来注入错误的日志记录语句, \r\n for user foobar was successful\rn因此允许他们伪造日志并覆盖他们的轨道。 (好吧,在这个人为的情况下,只是让它变得更难以看到发生了什么。)

第二种攻击方法更像是国际象棋移动。 许多日志都是HTML格式的,可​​以在另一个程序中查看,对于这个例子,我们假装日志是要在浏览器中查看的HTML文件。 现在我们注入 ,您将使用最有可能作为服务器执行的利用框架来浏览浏览器管理员……因为它怀疑首席执行官是否会阅读日志。 现在真正的黑客可以开始了。

防御:

一个简单的防御是确保所有带有userinput的日志语句都使用明显的字符’\ n’和’\ r’来转义字符,例如’֎’,或者你可以做ESAPI所做的事情并使用下划线进行转义。 只要它一致,它就没关系,只要记住不要使用会让你在日志中迷惑的字符集。 像userInput.replaceAll("\r", "֎").replaceAll("\n", "֎");这样的东西userInput.replaceAll("\r", "֎").replaceAll("\n", "֎");

我还发现确保精确指定日志格式很有用……这意味着您确保对日志语句需要具有严格的标准并构建格式,以便更容易捕获恶意用户。 所有程序员必须提交给派对并遵循格式!

为了防范HTML场景,我会使用[OWASP编码器项目] [1]

至于为什么建议使用ESAPI,它是一个经过实战考验的库,但简而言之,这基本上就是我们所做的。 看代码:

 /** * Log the message after optionally encoding any special characters that might be dangerous when viewed * by an HTML based log viewer. Also encode any carriage returns and line feeds to prevent log * injection attacks. This logs all the supplied parameters plus the user ID, user's source IP, a logging * specific session ID, and the current date/time. * * It will only log the message if the current logging level is enabled, otherwise it will * discard the message. * * @param level defines the set of recognized logging levels (TRACE, INFO, DEBUG, WARNING, ERROR, FATAL) * @param type the type of the event (SECURITY SUCCESS, SECURITY FAILURE, EVENT SUCCESS, EVENT FAILURE) * @param message the message to be logged * @param throwable the {@code Throwable} from which to generate an exception stack trace. */ private void log(Level level, EventType type, String message, Throwable throwable) { // Check to see if we need to log. if (!isEnabledFor(level)) { return; } // ensure there's something to log if (message == null) { message = ""; } // ensure no CRLF injection into logs for forging records String clean = message.replace('\n', '_').replace('\r', '_'); if (ESAPI.securityConfiguration().getLogEncodingRequired()) { clean = ESAPI.encoder().encodeForHTML(message); if (!message.equals(clean)) { clean += " (Encoded)"; } } // log server, port, app name, module name -- server:80/app/module StringBuilder appInfo = new StringBuilder(); if (ESAPI.currentRequest() != null && logServerIP) { appInfo.append(ESAPI.currentRequest().getLocalAddr()).append(":").append(ESAPI.currentRequest().getLocalPort()); } if (logAppName) { appInfo.append("/").append(applicationName); } appInfo.append("/").append(getName()); //get the type text if it exists String typeInfo = ""; if (type != null) { typeInfo += type + " "; } // log the message // Fix for https://code.google.com/p/owasp-esapi-java/issues/detail?id=268 // need to pass callerFQCN so the log is not generated as if it were always generated from this wrapper class log(Log4JLogger.class.getName(), level, "[" + typeInfo + getUserInfo() + " -> " + appInfo + "] " + clean, throwable); } 

见第398-453行。 这就是ESAPI提供的所有逃避。 我建议也要复制unit testing。

[免责声明]:我是ESAPI的项目联合负责人。

[1]: https : //www.owasp.org/index.php/OWASP_Java_Encoder_Project并确保您在进入日志记录语句时对输入进行了正确编码 – 每次都与您向用户发送输入时一样多。