12

使用/代替直接 MySQL 查询似乎是常识mysql_set_charsetmysqli::set_charsetset names

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

set names但是,如果我们使用准备好的语句和// 以外的其他转义方式,使用直接 MySQL 查询是否安全?mysql_real_escape_stringmysqli::real_escape_stringmysqli_escape_string

除了影响//的编码, vs /mysql_real_escape_string还有什么区别吗?mysqli::real_escape_stringmysqli_escape_stringset namesmysql_set_charsetmysqli::set_charset

4

4 回答 4

6

在连接上调用SET NAMES相当于 call set_charset,前提是你既不打电话get_charset也不mysql_real_escape_string(和朋友)。


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

让我们走一下源:

  1. 用户态函数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)其中包含以下代码(编号用于讨论,这些不是实际的源代码行号):

 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 行)。评论进一步讨论了 会发生什么conn->charset,但足以说它最终只出现在get_charsetmysql_real_escape_string(和朋友)中。

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

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

于 2016-06-21T14:51:38.417 回答
3

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

  • 转义引号(和其他字符),然后再将它们放在引号内。否则引号会给你语法错误。
  • 在客户端中建立字节的编码。这样INSERTs/SELECTs将知道如何在写入/读取期间更改字节。

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

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

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

对real_escape_stringset_charset()做些什么?我不知道。但这应该没关系。 SET NAMES显然不能,因为它是一个 MySQL 命令,并且对 PHP 一无所知。

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

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

结论:

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

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

于 2016-06-18T17:58:14.580 回答
1

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 服务器提供当前连接所需的所有文本编码信息。到目前为止,一切都很好。

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

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

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

于 2016-06-21T14:52:54.807 回答
1

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

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

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

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

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

总之:

  • mysql:根本不用。

  • mysqli:使用准备好的语句,因此该set_charset()方法
    还有:一旦使用准备好的语句,您将不再需要 real_escape_string 。

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

于 2016-06-20T23:57:08.187 回答