动态生成DDL时如何防止SQL注入?

How to prevent SQL injection when generating DDL dynamically?

目标:动态生成 PreparedStatement 免疫 SQL 注入。

    // This is a bad method. SQL injection danger . But it works 
    private PreparedStatement generateSQLBad(Connection connection, String tableName, 
        String columnName, String columnType) throws SQLException {
        String sql = "create table " + tableName + " (" + columnName + " " + columnType + ")";
        PreparedStatement create = connection.prepareStatement(sql);
        return create;
    }

    // I tried this. But it didn't work  
    private PreparedStatement generateSQLGood(Connection connection, String tableName, 
        String columnName, String columnType) throws SQLException {
        String sql = "create table ? (? ?)";
        PreparedStatement create = connection.prepareStatement(sql);
        create.setString(1, tableName);
        create.setString(2, columnName);
        create.setString(3, columnType);
        return create;
    } 

如何动态生成 PreparedStatement 用户可以选择表名、列类型等并且没有 SQL 注入的危险?

您不能使用 ? 参数占位符作为标识符(table 名称和列名称)。它们也不能用于 SQL 关键字,例如数据类型。准备查询需要能够验证语法,并验证您的 table 名称等是否合法。这必须在准备时完成,而不是在执行时完成。 SQL 不允许参数包含语法。它们始终被视为标量值。 这就是他们防止 SQL 注入的方式。

因此参数只能用于代替标量文字,例如带引号的字符串或日期,或数值。

动态标识符怎么办?正如评论所建议的那样,您能做的最好的事情就是过滤输入,这样它们就不会引入 SQL 注入。在某种程度上,动态 SQL 部分基于用户输入 SQL 注入。您只需要以可控的方式允许它。

所有 SQL 实现都允许您在 table 名称中使用特殊字符(如果您分隔标识符)。标准 SQL 使用双引号作为分隔符。 MySQL 使用反引号,Microsoft SQL 服务器使用方括号。

重点是您可以用这种方式创建看起来很奇怪的 table 名称,例如 table 包含空格、标点符号、国际字符或 SQL 保留字的名称。

CREATE TABLE "my table" ( col1 VARCHAR(20) );

CREATE TABLE "order" ( col1 VARCHAR(20) );

另请参阅我对

的回答

但是如果 table 名称本身包含文字双引号字符怎么办?那么你必须转义那个角色。使用双字符或反斜杠:

CREATE TABLE "Dwayne ""The Rock"" Johnson" ( col1 VARCHAR(20) );

CREATE TABLE "Dwayne \"The Rock\" Johnson" ( col1 VARCHAR(20) );

您也可以设计函数来检查此类字符的动态 table 名称,然后将它们删除或抛出异常。

但即使您通过仔细过滤输入使语句安全,这也可能无法满足 checkmarx 警告。 SQL 注入测试人员无法分析您的自定义代码以确保它可靠地过滤输入。

您可能只需要尽最大努力使动态 SQL 安全,因为您知道 checkmarx 总是会抱怨它。在您的代码中写下注释,向未来阅读您代码的开发人员解释您的安全措施。

同时编写单元测试以确保危险的输入会导致安全的 DDL 语句或异常。