动态生成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 语句或异常。
目标:动态生成 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 语句或异常。