【问题标题】:Variable column names using prepared statements使用准备好的语句的变量列名
【发布时间】:2011-03-09 08:04:30
【问题描述】:

我想知道是否有任何方法可以使用准备好的语句指定返回的列名。

我正在使用 MySQL 和 Java。

当我尝试时:

String columnNames="d,e,f"; //Actually from the user...
String name = "some_table"; //From user...
String query = "SELECT a,b,c,? FROM " + name + " WHERE d=?";//...
stmt = conn.prepareStatement(query);
stmt.setString(1, columnNames);
stmt.setString(2, "x");

我得到了这种类型的语句(在执行前打印)。

SELECT a,b,c,'d,e,f' FROM some_table WHERE d='x'

不过,我希望看到:

SELECT a,b,c,d,e,f FROM some_table WHERE d='x'

我知道我不能对表名执行此操作,如前所述 here,但想知道是否有办法对列名进行处理。

如果没有,那么我只需要尝试确保我对输入进行了清理,以免导致 SQL 注入漏洞。

【问题讨论】:

    标签: java sql jdbc prepared-statement


    【解决方案1】:

    这表明一个糟糕的数据库设计。用户不需要知道列名。创建一个真正的 DB 列,其中包含这些“列名”并将数据存储在其中。

    无论如何,不​​,您不能将列名设置为PreparedStatement 值。您只能将列 values 设置为 PreparedStatement values

    如果您想继续朝这个方向发展,您需要清理列名(以避免SQL Injection)并自己连接/构建 SQL 字符串。引用单独的列名并使用String#replace() 转义列名内的相同引号。

    【讨论】:

    • 嗯,用户实际上并不需要知道列名,但是需要的列名是根据用户提交的表单推断出来的。不过,这是在客户端处理的,所以我想看看是否有某种方法可以确保数据安全。然后我是否应该将整个批次移至服务器端,从而确保列数据不受污染?
    • 改为在服务器端处理。不要在客户端做生意。
    • @BalusC : “你不能将列名设置为 PreparedStatement 值” - 这完全是虚构的。在准备好的语句值列表中使用列名当然是可能的 - 但这并不意味着它应该这样使用,它仍然是糟糕的设计。
    • 我希望看到您使用带有 JSON 的 REST 动态限制给定资源的字段。不是每个人都希望返回整个资源,也不是每个人都希望为所述列的每个排列创建 1,000 个查询。
    • 使用可变列名的查询的动态排序怎么样?这也会被认为是糟糕的设计吗?
    【解决方案2】:

    准备一个允许列名的白名单。使用“查询”在白名单中查找以查看列名是否存在。如果不是,拒绝查询。

    【讨论】:

      【解决方案3】:

      接受的答案实际上并不正确。虽然 OP 方法表明数据库设计不佳,但业务逻辑可能需要它(例如 MySQL IDE)

      无论如何,对于 MySQL 准备好的语句,您需要知道的是 ? 是用于值,但如果您需要转义列名、表名等,请改用 ??

      这样的事情会起作用:

      SELECT ??, ??, ?? FROM ?? WHERE ?? < ? 
      

      将值设置为['id', 'name', 'address', 'user', 'id', 100]

      【讨论】:

      • 这是一个非常有趣的评论。我找不到对该转义参数值语法 (??) 的任何引用。你能不能给我指一个?
      • 我不记得我在哪里看到过这个,绝对不是在官方文档中。但是,我在一个玩具项目中成功地使用了它。
      • 我认为这是 Node.js 打包的指定用法mysqljs/mysql
      • @YamiOdymel 实际上,这可能仅适用于 mysqljs/mysql Node.js 包。从 MySQLWorkbench 运行时,选择似乎无效此语句不起作用,因为 ?? 无法识别 PREPARE stmt1 FROM 'SELECT ??, ?? FROM ?? WHERE ?? = ?'; SET @a = 'foo'; SET @b = 'bar'; SET @c = 'Foos'; SET @d = 'foo'; SET @e = 'fooooo'; EXECUTE stmt1 USING @a, @b, @c, @d, @e;
      【解决方案4】:

      我认为这种情况行不通,因为准备好的语句的全部目的是防止用户输入未转义的查询位 - 所以你总是会引用或转义文本。

      如果您想安全地影响查询结构,则需要在 Java 中清理此输入。

      【讨论】:

      • 你是对的“它不能工作”。然而,PreparedStatement 的第一个原因是因为资源效率,允许保持语句缓存并多次提交它,只需更改值(尤其适用于 OLTP)。它对 SQL 注入尝试的弹性是一个非常理想的副作用。
      【解决方案5】:

      利用语句接口的sql注入劣势作为优势。 例如:

      st=conn.createStatement();
      String columnName="name";
      rs=st.executeQuery("select "+ columnName+" from ad_org ");
      

      【讨论】:

        【解决方案6】:
        public void MethodName(String strFieldName1, String strFieldName2, String strTableName)
        {
        //Code to connect with database
        String strSQLQuery=String.format("select %s, %s from %s", strFieldName, strFieldName2, strTableName);
        st=conn.createStatement();
        rs=st.executeQuery(strSQLQuery);
        //rest code
        }
        

        【讨论】:

        • 问题是关于利用preparedStatement()。您的解决方案不使用它。容易出现sql注入。
        • 哎哟!通过字符串连接生成 SQL 代码,无需转义!请在你的老板认出这是你写的之前删除它。
        【解决方案7】:

        下面是java中的解决方案。

        String strSelectString = String.format("select %s, %s from %s", strFieldName, strFieldName2, strTableName);
        

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-10-11
        • 1970-01-01
        • 1970-01-01
        • 2016-05-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多