如何避免记录来自 Java 异常的敏感信息?

Posted

技术标签:

【中文标题】如何避免记录来自 Java 异常的敏感信息?【英文标题】:How to avoid logging sensitive information from Java exceptions? 【发布时间】:2022-01-02 20:44:58 【问题描述】:

如果完整的堆栈跟踪由日志框架(此处为 Java Util Logging)记录,Java 堆栈跟踪可能包含敏感信息(在我的情况下是密码)。

如何避免记录此类敏感信息,而不会丢失日志文件中堆栈跟踪中的其他重要信息?

例子:

package javalogspassword;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JavaLogsPassword 

    private static final Logger LOG = Logger.getLogger(JavaLogsPassword.class.getName());

    public static void main(String[] args) 

        Connection conn;
        try 
            conn = DriverManager.getConnection("jdbc:mysql://localhost/test?"
                    + "user=root&password=");
            LOG.log(Level.FINER, "Connected");
            try (Statement statement = conn.createStatement()) 
                // this statement contains a syntax error
                statement.execute("ALTER USER test IDENT BY '*Choose-a-good-password*'");
            
            conn.close();

         catch (SQLException ex) 
            // password '*Choose-a-good-password*' will be logged
            LOG.throwing(JavaLogsPassword.class.getName(), "main", ex);
        
    

记录器配置:logging.properties:

handlers= java.util.logging.FileHandler
.level= INFO
java.util.logging.FileHandler.pattern = java%u.log
javalogspassword.level = FINER

运行jar的命令:

java -Djava.util.logging.config.file=logging.properties  -jar dist/JavaLogsPassword.jar 

生成的文件java0.log 包含密码。

【问题讨论】:

@UrvashiSoni 这是非常糟糕的建议。任何敏感信息都在消息中(显然),并且不可能在堆栈跟踪或异常类型中。您的建议正好相反:泄露敏感信息并删除有用的非敏感信息。 @rzwitserloot 点已被占用。评论已删除。谢谢。 【参考方案1】:

没有办法解决这个问题。数据不是黑白的;任何数据的安全敏感性质都是灰色的。即使是看似完全无害的东西,比如堆栈跟踪中的行号或方法名称,理论上也是“敏感的”。例如,它可能会让人推测您必须使用库 A.B 的 X.Y 版本,它存在已知的安全漏洞,我现在可以利用它。

这就是现实生活中安全性的工作原理:很少有单点故障可以最终指出为:这,这是系统受到威胁的唯一原因。相反,它几乎总是有多个因素,每个因素都有一个问题,它本身似乎并不那么令人担忧,但将它们放在一起,瞧。

因此:没有解决方案 - 日志信息敏感。应该这样对待。

当然,这听起来不错,但实际的明文密码就像黑夜一样黑,虽然所有日志信息都必须是敏感的,但“仔细管理日志”和“只在其中打印密码”是有区别的。

不幸的是,那里也没有一个好的解决方案:有文化因素在起作用(要非常小心你在异常消息和日志语句中的内容。无论本能如何让你去:无论如何,我会放整个 SQL 语句直接放在日志文件中 - 解决这个问题),但文化本能很难灌输,几乎不可能测试(你如何单元测试“整个代码库中没有一行有记录密码的风险”?那是...绝对不平凡)。

因此,相反,它更像是“看到火就扑灭”:对于这种特定情况,您现在已经知道了,所以要解决问题。根据我的经验,根据经验,SQL 查询包含整个语句,但如果包含,请使用PreparedStatement 并通过.setString(1, ...) 设置密码,它们不会。这又涉及到文化:在我的开发团队中,将字符串文字放在 SQL 语句中是不可接受的,通常会进行 lint 检查,并通过代码审查积极监管。即使已知字符串文字可以接受并且完全不变 - 无论如何都要使用准备好的语句。如果 SQLException 包括整个语句,包括 ? 的值,即使使用preparedstatements,oof - 如果可以更正,请在 JDBC 驱动程序的设置中寻找。如果你真的做不到,请编写将 SQL 语句从异常中剔除的代码(也许是包装 .query)——这可能但很复杂,不过我会先寻找替代方案。

这里的其他文化方面涉及不希望任何以任何形式的直接敏感信息,这些信息并非设计得非常短暂。换句话说,在数据库中使用纯文本密码是错误的 - 然后该错误以“哦,糟糕,通过日志泄露密码真的很容易”的形式出现。修复不是“防止它出现在日志文件中”,修复是“不要将未散列的密码存储在数据库中”。像加密这样简单的事情,即使解密密码就在代码库中,因此不是一个特别保密的秘密,已经意味着审查日志的开发人员将需要积极承诺破坏他们的合同(即变成恶意的,并且如果你的程序员是恶意的,这是你的问题中最少的,安全方面!)为了真正知道那是什么。

您的开发团队成员可能会被贿赂,有些人可能无法控制他们的好奇心,但安全性不是黑白的,它是灰色的,这已经有所帮助。

更好的是找到任何需要您使用未散列密码的过程并摆脱它们,因为没有理由拥有这些。如果它是您系统中用户的密码,您在做什么?请改用 bcrypt、scrypt 或其他密码哈希设置。如果这是您连接到外部 API 所需的密码,请与外部 API 提供商交谈并告诉他们他们的设置不可接受 - 这应该是使用 JWT 或其他一些 PKI 授权/身份验证方案而不是发送密码约。以此类推。

换句话说,有答案。但没有一劳永逸的灵丹妙药。

【讨论】:

很遗憾,PreparedStatement 解决方案在此示例中没有改变任何内容。也许,因为ALTER USER 命令不是准备好的语句的典型应用,至少最新的 MySQL Connector/J (8.0.27) 将 ALTER USER 作为准备好的语句处理,与上面的明文密码命令相同。也许我为 MySQL 写了一个错误报告。将语句代码与准备好的语句变体交换。日志仍然包含明文密码。【参考方案2】:

这样做的方法是

研究

查看日志并查看哪些错误类型会产生敏感信息。

修改

实现一个执行安全日志记录的类,并在捕获可能包含敏感信息的错误时将错误日志记录委托给该类

抓住一切

您需要确保捕获到任何异常,并以最适合您需要的方式处理异常。

重构

查看项目中的每个捕获,并查看是否存在可能包含敏感信息的捕获异常。

拭目以待

完成该过程后,请稍等片刻,然后再次检查日志,看看是否有您最初遗漏的案例。如果是这样,对以前没有处理过的案件应用相同的流程。

【讨论】:

以上是关于如何避免记录来自 Java 异常的敏感信息?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免 ResultSet 是 Java 中的关闭异常?

企业即时通讯如何避免敏感信息的安全风险

如何避免informix中的锁异常

如何避免异常阴影?

Django模型:如何避免在使用来自同一个表的2个外键时引用相同的记录

在JAVA连接的数据库中插入数据时如何避免重复信息~