代码之家  ›  专栏  ›  技术社区  ›  ChrisR

在pdo语句中转义列名

  •  26
  • ChrisR  · 技术社区  · 15 年前

    我目前正在构建一个查询,其中字段/列和值部分都可能包含用户输入的数据。

    问题是正在转义字段名。 我使用准备好的语句来正确地转义和引用这些值,但是在转义字段名时,我遇到了麻烦。

    • mysql_real_escape_string需要一个mysql连接资源,这样就排除了
    • pdo::quote在字段名周围添加引号,这也使它们在查询中无效。
    • 加斜杠有效,但不太安全

    任何人都知道在将字段名传递给pdo::prepare之前,将字段名正确插入查询的最佳方法是什么?

    5 回复  |  直到 9 年前
        1
  •  28
  •   bobince    15 年前

    执行分隔标识符的ANSI标准方法是:

    SELECT "field1" ...
    

    如果有一个“名字,加倍它:

    SELECT "some""thing" ...
    

    不幸的是,对于默认设置,这在MySQL中不起作用,因为MySQL倾向于认为双引号是字符串文本的单引号的替代方法。在这种情况下,必须使用反斜杠转义(如bj_¶rn所述)。

    要正确执行反斜杠转义,请 需要mysql-real-escape-string,因为它依赖于字符集。但这一点是没有意义的,因为 mysql_real_escape_string和addslashes都不能转义后引号字符 . 如果您确定列名中永远不会有非ASCII字符,您只需手动反斜杠转义`和\字符就可以了。

    不管怎样,这都与其他数据库不兼容。通过设置配置选项ansi_quotes,可以告诉mysql允许使用ansi语法。同样,默认情况下,SQL Server也会使用双引号;它还使用另一种语法,即方括号。同样,您可以通过__Quoted_标识符_选项将其配置为支持ANSI语法。

    总结:如果您只需要MySQL兼容性:

    a.使用反引号并禁止在名称中使用反引号、反斜杠和nul字符,因为转义它们是不可靠的。

    如果需要跨DBMS兼容性,请执行以下任一操作:

    b.使用双引号,要求MySQL/SQL Server用户适当更改配置。不允许在名称中使用双引号字符(因为Oracle甚至无法处理它们,甚至无法转义)。或者,

    c.为MySQL与SQL Server与其他服务器进行设置,并根据设置生成后引号、方括号或双引号语法。不允许双引号和反斜杠/反引号/nul。

    这是您希望数据访问层具有的功能,但PDO没有。

    摘要:任意列名称是一个问题,如果可以帮助的话最好避免。

    总结总结:gnnnnnnnnnh。

        2
  •  13
  •   Maryam Jeddian    14 年前

    正确答案是

    str_replace("`", "``", $fieldname)

    错误:

    mysql> SELECT `col\"umn` FROM user;
    ERROR 1054 (42S22): Unknown column 'col\"umn' in 'field list'
    

    正确的:

    mysql> SELECT `kid``s` FROM user;
    ERROR 1054 (42S22): Unknown column 'kid`s' in 'field list'
    mysql> SELECT ```column``name``` FROM user;
    ERROR 1054 (42S22): Unknown column '`column`name`' in 'field list'
    

    (请注意,在上一个示例中,列名中有3(三)个额外的反勾号,只是为了显示极端情况)

        3
  •  2
  •   Chris    14 年前

    这可能会影响性能,但应该是安全的。

    首先运行描述表查询以获取允许的字段名列表,然后再将这些字段名与用户提交的数据相匹配。

    如果有匹配,那么您可以使用用户提交的数据,而不需要任何转义。

    如果没有匹配,那么这是一个打字错误或黑客-不管怎样,这是一个'错误'在输入的数据和查询不应该运行。

    “动态”表名也可以通过运行“显示表”查询并从该结果集进行匹配来实现。

    在我的一个应用程序中,我有一个“安装”脚本;其中的一部分查询数据库和表字段名,然后写一个总是被引用回的PHP文件,这样我就不会经常在数据库中运行“描述”查询,例如

    $db_allowed_names['tableName1']['id'] = 1;
    $db_allowed_names['tableName1']['field1'] = 1;
    $db_allowed_names['tableName1']['field2'] = 1;
    $db_allowed_names['tableName2']['id'] = 1;
    $db_allowed_names['tableName2']['field1'] = 1;
    $db_allowed_names['tableName2']['field2'] = 1;
    $db_allowed_names['tableName2']['field3'] = 1;
    
    if($db_allowed_names['tableName1'][$_POST['field']]) {
         //ok
    }
    

    我使用这样的数组键作为if语句比in-array查找快一点。

        4
  •  1
  •   Jaded    9 年前

    像这样的怎么样?

    function filter_identifier($str, $extra='') {
        return preg_replace('/[^a-zA-Z0-9_'.$extra.']/', '', $str);
    }
    
    try {
        $res = $db->query('SELECT '.filter_identifier($_GET['column'], '\*').' FROM '.filter_identifier($_GET['table']).' WHERE id = ?', $id);
    } catch (PDOException $e) {
        die('error querying database');
    }
    

    这是一个简单的基于白名单的字符列表。不在列表中的任何字符都将被删除。幸运的是,我能够创建数据库和表,所以我知道在“A-Za-Z0-9”之外永远不会有任何字符(注:没有空格)。您可以通过$extra arg向列表中添加额外的字符。如果有人试图把 “(从用户中选择*)” 在“列”中,它将过滤到 “从用户中选择*” ,这将引发异常:)

    如果可能的话,我尽量避免做额外的查询(我对性能非常敏感)。因此,我宁愿不做一些事情,比如预先进行描述或者硬编码一组表/列以进行检查。

        5
  •  0
  •   Björn    15 年前

    wierd设计一个项目,但对于您的问题:用''包围您的字段名,并使用addslashes作为名称。

    select `field1`, `field2` from table where `field3`=:value