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

时间:2019-01-01 03:32:52

标签: java jdbc sql-injection

目标:动态生成不受SQL注入影响的PreparedStatement

    // 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注入的危险?

1 个答案:

答案 0 :(得分:1)

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

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

如何处理动态标识符?就像评论所建议的那样,您最好的办法就是过滤输入,这样它们就不会引入SQL注入。从某种意义上说,部分基于用户输入的动态SQL是 SQL注入。您只需要以受控的方式允许它。

所有SQL实现都允许您在分隔标识符的表名中使用特殊字符。标准SQL使用双引号作为分隔符。 MySQL使用反引号,而Microsoft SQL Server使用方括号。

关键是您可以通过这种方式来创建看起来很奇怪的表名,例如包含空格,标点符号,国际字符或SQL保留字的表名。

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

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

另请参阅我对https://stackoverflow.com/a/214344/20860的回答

但是如果表名本身包含文字双引号字符怎么办?然后,您必须转义该字符。使用双字符或反斜杠:

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

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

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

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

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

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