【问题标题】:How to avoid "Security - A prepared statement is generated from a nonconstant String" FindBugs Warning如何避免“安全性 - 从非常量字符串生成准备好的语句” FindBugs 警告
【发布时间】:2012-05-08 14:10:31
【问题描述】:

我正在开发一个项目,其中包含如下代码:

String sql = "SELECT MAX(" + columnName + ") FROM " + tableName;                
PreparedStatement ps = connection.prepareStatement(sql);

有什么方法可以更改此代码,以便 FindBugs 停止给我 “安全性 - 从非常量字符串生成准备好的语句”警告?

请假设这段代码对于 SQL INJECTION 是安全的,因为我可以在代码中的其他地方控制可能 “tableName”和“columnName”的值(它们不直接来自用户输入)。

【问题讨论】:

    标签: java jdbc findbugs


    【解决方案1】:

    不要通过+ 连接sql 字符串。你可以使用

    String sql = String.format("SELECT MAX(%s) FROM %s ", columnName, tableName);
    

    这比连接一个字符串要慢,所以你应该初始化这个static然后这不是问题。

    我认为使用StringBuilder 也可以解决此警告。

    避免此警告的另一种方法是在该字符串(或方法/或类)上方添加@SuppressWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")

    您还可以使用Filter File 来定义应排除的规则。

    【讨论】:

    • 您好 user714965,感谢您的回复。我在使用 StringBuilder 时还出现了其他此警告,因此这不起作用。注释有效(最好向其他读者澄清它的完全限定名称是@edu.umd.cs.findbugs.annotations.SuppressWarnings)。现在我必须决定是否要在我的项目中使用 FindBugs 库。再次感谢。
    • @ederribeiro:你的项目中对 FindBugs 的依赖可能不是你说的最好的。在我的原始答案中,我遗漏了一点,请参阅最后一段。
    • 这解决了这个特殊问题,因为 OP 确信他的代码不会受到 SQL INJECTION 的影响。但它很容易出错,因此通常是一种不好的做法。带参数的准备好的语句是要走的路。不会投反对票,因为它实际上解决了问题,但我建议不要那样做。
    【解决方案2】:
    private static final String SQL = "SELECT MAX(?) FROM ?";
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.preparedStatement.setInt(1,columnName);
    ps.preparedStatement.setString(2,tableName);
    

    如果你使用准备好的语句,那么 in 参数应该是一个最终的字符串,参数应该稍后使用 setInt、setString 方法添加。

    这将解决 findbug 警告。

    【讨论】:

    • 您的声明执行了吗?你得到的绑定表名ORA-00903: invalid table name。传递列名有效,但您会得到列名(而不是列值)。这是NOGO!
    【解决方案3】:

    尝试使用以下...

    private static final String SQL = "SELECT MAX(%s) FROM %s";
    

    然后在使用时使用 String.format() 调用...

    PreparedStatement ps = connection.prepareStatement(String.format(sql,columnName,tableName));
    

    如果这不能解决问题,您可以随时忽略该检查;在您的 FindBugs 配置中将其关闭。

    如果这不起作用(或不是一个选项),某些 IDE(如 IntelliJ)也会让您使用特殊格式的 cmets 或注释来抑制警告。

    【讨论】:

    • 嗨,杰西,感谢您的回复。我试着按照你说的去做,但不幸的是它没有用。您的最后一个建议,与@user714965 提到的相同,可以解决问题。现在我必须决定是否要为在我的项目中安装 FindBugs 库付出代价,只是为了摆脱警告。
    【解决方案4】:

    String.format 和 StringBuilder(或 StringBuffer)都没有帮助我。

    解决方案是“prepareStatement”隔离:

    private PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }
    

    【讨论】:

    • 这对我也没有帮助。
    【解决方案5】:

    可以使用连接来创建您的字符串。这样做不会导致安全警告。在处理较长的 SQL 语句时,为了清楚起见,最好将其拆分为多行

    使用变量构造字符串是导致安全警告的原因。

    这将导致警告:

    String columnName = getName();
    String tableName  = getTableName();
    final String sql = "SELECT MAX(" + columnName + ") FROM " + tableName;
    PreparedStatement ps = connection.prepareStatement(sql);
    

    这行不通:

    String columnName = getName();
    String tableName  = getTableName();
    final String sql = "SELECT MAX(" + "?" + ")" +
                       "FROM " + "?";
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setString(1, columnName);
    ps.setString(2, tableName);
    

    它不起作用,因为准备好的语句只允许将参数绑定到 SQL 语句的“值”位。

    这是有效的解决方案:

    private static final boolean USE_TEST_TABLE = true;
    private static final boolean USE_RESTRICTED_COL = true;
    private static final String TEST_TABLE = "CLIENT_TEST";
    private static final String PROD_TABLE = "CLIENT";
    private static final String RESTRICTED_COL ="AGE_COLLATED";
    private static final String UNRESTRICTED_COL ="AGE";
    
    ....................
    
    final String sql = "SELECT MAX(" +
            ( USE_RESTRICTED_COL ? RESTRICTED_COL : UNRESTRICTED_COL ) +  ")" +
            "FROM " +
            ( USE_TEST_TABLE ? TEST_TABLE : PROD_TABLE );
    PreparedStatement ps = connectComun.prepareStatement(sql);
    

    但它仅在您必须在编译时已知名称的两个表之间进行选择时才有效。您可以在超过 2 种情况下使用复合三元运算符,但它变得不可读。

    如果 getName() 或 getTableName() 从不受信任的来源获取名称,第一种情况可能是安全问题。

    如果这些变量之前已经过验证,则很有可能使用变量构造安全的 SQL 语句。这是您的情况,但 FindBugs 无法弄清楚。 Findbugs 无法知道哪些来源是可信的。

    但是,如果您必须使用来自用户或不受信任的输入的列或表名,那么就没有办法绕过它。您必须使用其他答案中提出的任何方法来验证自己的此类字符串并忽略 Findbugs 警告。

    结论:对于这个问题的一般情况,没有完美的解决方案。

    【讨论】:

    • 嗨@jose-antonio-dura-olmos 谢谢你的建议,但是你试过了吗?似乎无法使用准备好的语句设置“元数据”(例如表名或列名)。我试过用 mysql 和 h2 没有成功。此线程中还有一个用户尝试使用 Oracle 并失败了。
    • 我有以这种方式更改表名的生产代码。我没有检查这个特定的代码,但我会这样做。
    • 这会发生在内存不足的情况下。我需要在两张表之间进行选择,一张用于生产,一张用于测试。但它并没有那样工作,因为表名和列名不能是可变的;语句预编译需要知道用于验证正确 sintax 的表和列名。因此它们不能是可变的。我找到了一个解决方案,它仍然允许我在没有收到警告的情况下使用连接。我已经用那个解决方案更新了我的答案。
    【解决方案6】:

    如果您确定不存在 SQL 注入的可能性,请在方法上使用 SuppressFBWarnings 注释:

    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
    

    【讨论】:

      【解决方案7】:
      StringBuilder sql = new StringBuilder();
      sql.append("SELECT MAX(")
         .append(columnName)
         .append(") FROM ")
         .append(tableName);
      
      PreparedStatement ps = connection.prepareStatement(sql);
      ps.execute();
      

      【讨论】:

      • 这甚至无法编译,原因有两个。你为什么还要在这里使用StringBuilder?那是零意义。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-19
      • 1970-01-01
      • 2010-11-21
      • 2012-08-12
      相关资源
      最近更新 更多