【问题标题】:"set names" vs mysqli_set_charset — besides affecting mysqli_escape_string, are they identical?"set names" vs mysqli_set_charset — 除了影响 mysqli_escape_string,它们是否相同?
【发布时间】:2014-12-23 03:31:47
【问题描述】:

seems to be common knowledge 使用 mysql_set_charset / mysqli::set_charset 而不是直接 MySQL 查询 set names

经常提到的原因是set names 不安全,因为用于mysql_real_escape_string / mysqli::real_escape_string 的编码只能通过调用mysql_set_charset / mysqli::set_charset 来设置。 (引用的另一个原因是 PHP 文档说它“不推荐”§。)

但是,如果我们使用准备好的语句和或other means of escaping 除了mysql_real_escape_string / mysqli::real_escape_string / mysqli_escape_string,使用直接 MySQL 查询 set names 是否安全?

除了影响mysql_real_escape_string/mysqli::real_escape_string/mysqli_escape_string的编码,set namesmysql_set_charset/mysqli::set_charset有什么区别吗?

【问题讨论】:

    标签: php mysql security encoding libmysql


    【解决方案1】:

    在连接上调用SET NAMES 等同于调用set_charset,前提是您既不调用get_charset 也不调用mysql_real_escape_string(和朋友)。


    当您调用set_charset 时,PHP 会做两件事。首先,它在连接上调用SET NAMES。其次,它会记住您设置的字符集。该状态信息稍后仅用于get_charsetmysql_real_escape_string(和朋友)函数。因此,如果你不使用这些函数,那么你可以考虑两个等价的。

    让我们走源:

    1. Userland 函数 mysql_set_charsetmysqli_set_charset 调用...
    2. 引擎函数mysql_set_character_set调用...
    3. 引擎宏mysqlnd_set_character_set,其定义为:

      #define mysqlnd_set_character_set(conn, cs) \ ((conn)->data)->m->set_charset((conn)->data, (cs)))

      并扩展到...

    4. MYSQLND_METHOD(mysqlnd_conn_data, set_charset) 其中contains 如下代码(编号供讨论,这些不是实际的源代码行号):

     1   if (PASS == conn->m->local_tx_start(conn, this_func)) {
     2      char * query;
     3      size_t query_len = mnd_sprintf(&query, 0, "SET NAMES %s", csname);
     4 
     5      if (FAIL == (ret = conn->m->query(conn, query, query_len))) {
     6          php_error_docref(NULL, E_WARNING, "Error executing query");
     7      } else if (conn->error_info->error_no) {
     8          ret = FAIL;
     9      } else {
    10           conn->charset = charset;
    11      }
    12      mnd_sprintf_free(query);
    13 
    14      conn->m->local_tx_end(conn, this_func, ret);
    15   }
    

    如您所见,PHP 在连接本身上调用SET NAMES(第 3 行)。 PHP 还跟踪刚刚设置的字符集(第 10 行)。 cmets 进一步讨论了conn->charset 会发生什么,但我只想说它最终只出现在get_charsetmysql_real_escape_string(和朋友)中。

    因此,如果您不关心此状态,并且您同意既不使用 get_charset 也不使用 mysql_real_escape_string,那么您可以在连接本身上调用 SET NAMES 而不会产生不良影响。

    顺便说一句,我从来没有这样做过,但看起来用-DPHP_DEBUG=1 编译PHP 可以通过各种DBG 宏进行大量调试。这对于查看您的代码如何通过此块可能很有用。

    【讨论】:

    • 好的,既然 PHP 知道 (conn->charset = charset) 连接是在给定的“字符集”中,那么稍后它将如何处理该信息?
    • @RickJames 存储的charset 状态只有两种用途:mysqli_get_charset 使用它来进行明显的奇偶校验,mysqli_real_escape_string 使用它来了解每个字符有多少字节用于转义目的。由于 OP 自己管理安全引用,并且可能知道字符集,因此 PHP 跟踪的状态值是无关紧要的。
    • 谢谢。我认为这证实了我的断言,即有两件 独立 的事情正在发生——转义以构建 SQL 字符串,并将字节从客户端编码转换为列/表的 CHARACTER SET 以进行存储。
    • 但我仍然对为什么mysqli_real_escape_string 会关心“每个字符的字节数”有多少感到困惑。这对于 utf8 等可变长度编码意味着什么?
    • @RickJames 有两个逃生者:one for quotesanother for slashes。代码说明了一切,但简而言之:检查 unicode 允许进行复制优化。
    【解决方案2】:

    必须做两件事(在这个领域):

    • 在将引号(和其他字符)放入引号之前对其进行转义。否则引号会给您带来语法错误。
    • 在客户端中建立字节的编码。这样INSERTs/SELECTs 将知道如何在写入/读取期间更改字节。

    第一个需要转义撇号和双引号,因为这两者都是 MySQL 语法中字符串可接受的引号。然后,转义字符本身需要转义。这 3 个字符对于必须应用程序来说已经足够了。但是,如果您尝试转义 BLOB(例如 .jpg),则各种控制字符可能会造成麻烦。您最好转换为十六进制,然后使用UNHEX(),以避免出现问题。注意:这里没有提到任何关于字符集的内容。如果您不处理BLOBs,则可以使用PHP 的addslashes()

    第二项的目的是说“这个字节流是这样编码的(utf8/latin1/etc)”。它仅用于在要存储/获取的列的CHARACTER SET 与客户端中所需的编码(PHP 等)之间进行转换。它由各种语言以各种方式处理。对于 PHP:

    • mysql_* -- 不要不要使用这个接口;它已被弃用,很快就会被删除。
    • mysqli_* -- mysqli::set_charset(...)
    • PDO -- new PDO('...;charset=UTF8', ...)

    set_charset() 对 real_escape_string 有什么作用吗?我不知道。但这应该没关系。 SET NAMES 显然不能,因为它是一个 MySQL 命令,对 PHP 一无所知。

    htmlentities() 是该领域的另一个 PHP 函数。它将 8 位代码转换为 & 实体。这不应该用于进入 MySQL。它只会掩盖其他问题。仅在涉及 HTML 而非 PHP 或 MySQL 的特定情况下使用它。

    今天使用的唯一合理的CHARACTER SETs是ascii、latin1、utf8和utf8mb4。那些在“控制”区域中没有“字符”。 Sjis 和其他一些字符集可以。这种对控制字符的混淆可能是 real_escape_string 存在的原因。

    结论:

    在我看来,您需要两种机制:一种用于转义,另一种用于在客户端中建立编码。它们是分开的。

    如果将它们捆绑在一起,PHP 手册未能提供任何令人信服的理由来选择一种方法而不是另一种方法。

    【讨论】:

    • 主教的回答可能比我的更完整。他当然更“研究”。
    【解决方案3】:

    mysql:不推荐使用整个接口,所以不要使用任何一个(PHP 7 删除了该接口)。

    mysqli(和 PDO)已经准备好不需要(也不需要)使用 real_escape_string 的语句。 -> 所以如果你只使用 mysqli 和准备好的语句:不用担心你是如何设置字符集的。

    既然您关心安全性:我认为不使用准备好的语句没什么意义。

    一旦你使用了 mysqli 的预处理语句,唯一的方法就是使用$mysqli->set_charset(),因为你不能再简单地将多个 sql 语句连接到一个字符串中。

    因此,了解差异的问题最多只是学术问题,与现实生活无关。

    总结:

    • mysql:根本不用。

    • mysqli:使用准备好的语句,因此使用 set_charset() 方法
      另外:使用准备好的语句后,您将不再需要 real_escape_string。

    • 或者 - 当然 - 使用 PDO 及其方法。

    【讨论】:

    • 它没有解决原来的问题。
    • Prepared statements 不允许您在一个字符串中发出多个 sql 语句...... - 所以$mysqli->set_charset() 无论如何都是要走的路。
    • 问题是“两者之间有什么区别”。不确定您的回答和评论如何相关,抱歉。我不是说你说的是错的(因为不是),我只是说这不是 OP 要求的。
    • 试图在答案中解释为什么差异无关紧要。
    • 好吧,我不同意差异是无关紧要的。不管一个人应该做什么,为什么这个问题仍然有意义。
    【解决方案4】:

    SET NAMES ... 是一个方便的别名:

    SET NAMES 'charset_name' 语句等价于这三个 声明:

    SET character_set_client = charset_name;
    SET character_set_results = charset_name;
    SET character_set_connection = charset_name;
    

    将 character_set_connection 设置为 charset_name 也会隐式设置 collat​​ion_connection 到 charset_name 的默认排序规则。

    ... 为 MySQL 服务器提供当前连接所需的所有 text-encoding information。到目前为止一切顺利。

    但 PHP 也参与其中,它不会从这里学到任何东西,因为它基本上是一个随机的用户查询。出于明显的性能原因,PHP 不会做两件事:

    • 扫描发送到服务器的所有用户查询以检测对 SET NAMES 的调用。
    • 每次需要做某事时,向 MySQL 询问相关指令的当前值。

    简而言之:此方法通知服务器但不通知客户端。但是,专用的 PHP 函数可以同时做这两件事。

    【讨论】:

    • 然后是为什么 PHP 需要知道或关心编码的问题。
    猜你喜欢
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-02
    • 2020-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多