处理动态表/列名时如何防止SQL注入?

3

我正在使用jdbc PreparedStatement 进行数据插入。

Statement stmt = conn.prepareStatement(
"INESRT INTO" + tablename+ "("+columnString+") VALUES (?,?,?)");
tablenamecolumnString 是动态生成的内容。
我尝试将 tablename 和 columnString 参数化,但它们最终会解析为类似于“tablename”的内容,这会违反语法规则。
我在网上找到了一些建议,建议我查找数据库以检查有效的 tablename/columnString,并将其缓存到其他查询的某个地方(例如 Hashset),但我正在寻找更好的性能/快速解决方法,也许是一个字符串验证器/正则表达式可以解决问题。
有没有人遇到过这个问题,你是如何解决的?

你需要引用它们,对于 SQL Server 使用 [],对于 MySQL 使用 或者使用"`,具体取决于数据库。 - Lukasz Szozda
这个“动态生成”的本质是什么?它来自可信的来源吗?你需要处理带有空格的表名等吗? - Jon Skeet
@JonSkeet 这是来自可信来源的数据。这是我之前提取的数据库,可以通过发送事件来触发此插入,或者手动触发插入。在后一种情况下,在插入之前,有人可能会更改提取的数据。对于第二个问题,我希望它尽可能通用。 - Mc Kevin
@lad2025 数据库是任何符合JDBC标准的数据库,你的方法可能有效,如果所有符合JDBC标准的数据库都提供了一些转义机制。但我目前正在使用Postgres进行测试。 - Mc Kevin
您的数据库结构如此动态,以至于您无法创建静态函数吗?有没有其他更深层次的含义需要将语句参数化? - JoSSte
@JoSSte 我将每个数据库行作为POJO,并根据它构建插入语句。这样我就可以加载任何表的任何列的任何行。 - Mc Kevin
5个回答

1

我不是Java专家,只有理论知识。

您可以动态格式化添加的标识符,也可以将它们列入白名单

第二个选项更好。因为:

  • 大多数开发人员对标识符不够熟悉,无法正确格式化它们。例如,引用第一个评论中提供的标识符并不能使其受到保护。
  • 可能存在另一种攻击方式,不完全是注入,但类似:假设您的表中有一列普通用户无权使用,比如称为“admin”。使用来自客户端的数据构建的动态columnString,轻而易举地就可以伪造特权升级。

因此,在代码中预先列出所有可能(和允许)的变量,然后对输入的值进行验证,是最好的选择。

至于columnString——它由单独的列名称组成。因此,要保护它,必须对每个单独的列名称进行白名单验证,然后从中组装出最终的columnString


我可以看到白名单在表名上起作用,但是列字符串呢?; < - Mc Kevin
我的意思是使用类似于以下的语句:Statement stmt = conn.prepareStatement( "INESRT INTO" + my_qoute_function(tablename)+ "("+columnString+") VALUES (?,?,?)"); 这样,即使开发人员不熟悉引用,也会在执行之前对名称进行引用。对于每个列也是如此,但是需要使用columnString。 - Lukasz Szozda

0
创建一个方法为您生成 SQL 字符串:
private static final String template = "insert into %s (%s) values (%s)";

private String buildStmt(String tblName, String ... colNames) {
    StringJoiner colNamesJoiner = new StringJoiner(",");
    StringJoiner paramsJoiner = new StringJoiner(",");
    Arrays.stream(colNames).forEach(colName -> {
        colNamesJoiner.add(colName);
        paramsJoiner.add("?");
    });
    return String.format(template, tblName, colNamesJoiner.toString(), paramsJoiner.toString());
}

然后使用它...

Statement stmt = conn.prepareStatement(buildStmt(tablename, [your column names]));

我已经在做类似的事情了。如果其中一个列名被更改以执行攻击,这种情况会如何处理? - Mc Kevin
我不确定colName可能具有什么价值来执行你所考虑的某种攻击...但是如果您想要,您可以始终进行一些合理性检查...例如验证colName是否为单词。 - anders

0

我们可以应用多种解决方案。

1)白名单输入验证

String tableName;
switch(PARAM):
   case "Value1": tableName = "fooTable";
                  break;
   case "Value2": tableName = "barTable";
              break;
   ...

   default      : throw new InputValidationException("unexpected value provided for table name");
  • 通过对tableName进行输入验证,只允许查询指定的表格,从而防止SQL注入攻击。

2) 将您的动态columnName(s)或tableName(s)与下面显示的特殊字符绑定

例如:

  • 对于Mysql:使用反引号(`)

    Select `columnName ` from `tableName `;

  • 对于MSSQL:使用双引号("或[ ])

    select "columnName" from "tableName";
    或者
    select [columnName] from [tableName];

注意:在执行此操作之前,应使用这些特殊字符(`,",[,])对数据进行清理。


0
作为对@Anders答案的详细说明,不要直接使用输入参数作为名称,而是保留一个属性文件(或数据库表),将一组允许的输入映射到实际的表名。

这样,任何无效的名称都不会导致有效的SQL(并且可以在生成任何SQL之前捕获),实际名称也永远不会在应用程序外部知道,因此更难猜测哪些是有效的SQL语句。

0

我认为,最好的方法是从数据库或其他非用户输入中获取表和列名,并在准备好的语句中使用参数来处理其余部分。


检查表和列名与数据库匹配是我会使用的最后手段,因为数据库操作很费资源,特别是在使用远程数据库时。 - Mc Kevin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接