PDO::quote() for SQL 服务器错误引用包含 ASCII NUL 的字符串

PDO::quote() for SQL Server mis-quotes strings containing ASCII NUL

我正在尝试使用 pdo_sqlsrv 从 PHP 将 ASCII NUL 字符([=15=] 又名 U+0000)插入到 SQL 服务器数据库中。这是处理 PHP 序列化字符串的要求,其中包含 NUL 字符以表示 private/protected 变量。

但是,关于 PDO::quote() 的一些事情破坏了字符串。

要重现的代码(用适当的值替换 DBNAMEUSERNAMEPASSWORD):

<?php

try {
    $dsn = 'sqlsrv:Server=.\SQLEXPRESS;Database=DBNAME';
    $user = 'USERNAME';
    $pass = 'PASSWORD';

    $connection = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
    die("Connection error: " . $e->getMessage());
}

$str = "XX[=12=]XX";

header("Content-Type: text/plain");

print("Original: " . str_replace("[=12=]", "{NUL}", $str) . "\n");
$str = $connection->quote($str);
print("Quoted:   " . str_replace("[=12=]", "{NUL}", $str) . "\n");

?>

预期输出:

Original: XX{NUL}XX
Quoted:   'XX{NUL}XX'

实际输出:

Original: XX{NUL}XX
Quoted:   'XX'{NUL}{NUL}a

谁能解释这种奇怪的行为,更重要的是,解释如何解决它?

更新

最后一个字符似乎是随机的,因为在随后的 运行 上它是一个 e。这意味着某种形式的内存访问错误,例如阅读超过字符串的末尾。可能是 pdo_sqlsrv 实现中的错误?

  1. 由于 \ 可以用在 LIKE 语句中,因此您需要在引用之前添加斜杠,从而转义原始斜杠。

  2. 而不是使用 PDO::quote,而是使用 PDO::quote 文档中提到的准备好的语句。

If you are using this function to build SQL statements, you are strongly recommended to use PDO::prepare() to prepare SQL statements with bound parameters instead of using PDO::quote() to interpolate user input into an SQL statement. Prepared statements with bound parameters are not only more portable, more convenient, immune to SQL injection, but are often much faster to execute than interpolated queries, as both the server and client side can cache a compiled form of the query.

实例

Sandbox

输出

<?php
$pdo = new \PDO('sqlite::memory:', null, null);

$str = "XX[=10=]XX";

print($pdo->quote(addSlashes($str))); <----> 'XX[=10=]XX'

?>

运行 在我的 32 位 SQLSRV/3.2 下 PHP/5.6.21 下的测试代码稍微修改后的版本显示出奇怪的行为:

$str = "XX[=10=]XX";
for($i=0; $i<5; $i++){
    $connection = new PDO($dsn, $user, $pass);
    printf("0x%s -> 0x%s\n", bin2hex($str), bin2hex($connection->quote($str)));
    printf("0x%s -> 0x%s\n", bin2hex($str), bin2hex($connection->quote($str)));
}
0x5858005858 -> 0x27585827000005
0x5858005858 -> 0x27585827000005
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x27585827000306
0x5858005858 -> 0x27585827000306
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x2758582700b72b
0x5858005858 -> 0x27585827000005
0x5858005858 -> 0x27585827000005

每个 运行 上的实际字节数变化。

我的发现:

  • 结果在会话中是一致的,但在连接之间随机变化
  • CharacterSet 连接选项(此处显示)没有影响
  • github 存储库中有几个关于模拟准备的错误修正

所以这一切都指向库中的错误。我已经尝试了最新的稳定版本(v4.3.0 32 位 PHP/7.1.9),它仍然存在(尽管,至少在我的系统上,结果仍然是任意的,但不是随机的)。

无论是功能错误,我会说 PDO::quote() 在 SQLSRV 下不是二进制安全的。如果底层框架不允许准备好的语句,您可能需要考虑 converting to hex 并作为(未加引号的)十六进制文字发送,例如:

INSERT INTO foo (bar) VALUES (0x5858005858);

Can anyone explain this odd behaviour...

这似乎是 pdo_sqlsrv 扩展中的错误(并且可能是非 PDO sqlsrv 扩展中的等效函数)。

我在扩展的问题跟踪器 (ticket #538) 中记录了该问题,开发人员已确认他们可以重现该问题,这是一个需要修复的错误。

...and - more importantly - explain how to resolve it?

我们实施的解决方案是检测这种情况并重写字符串以删除任何 NUL 字符:

try {
    $dsn = 'sqlsrv:Server=.\SQLEXPRESS;Database=DBNAME';
    $user = 'USERNAME';
    $pass = 'PASSWORD';

    $connection = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
    die("Connection error: " . $e->getMessage());
}

$str = "XX[=10=]XX";

header("Content-Type: text/plain");

print("Original: " . str_replace("[=10=]", "{NUL}", $str) . "\n");
$str = safeQuote($str, $connection);
print("Quoted:   " . str_replace("[=10=]", "{NUL}", $str) . "\n");

function safeQuote($str, $connection) {
// Special handling of ASCII NUL characters, which cause breakages.
// This appears to be due to a bug in the pdo_sqlsrv driver.
// For performance reasons, we only do this for strings that contain [=10=].
    if (is_string($str) && strpos($str, "[=10=]") !== false) {
        $arrParts = explode("[=10=]", $str);
        foreach ($arrParts as $Key => $Part) {
            $arrParts[$Key] = $connection->quote($Part);
        }
        $str = implode(" + CHAR(0) + ", $arrParts);
    }
// Otherwise, prepare the quoted string in the standard way.
    else {
        $str = $connection->quote($str);
    }

    return $str;
}

?>

这会输出以下内容,据我所知,这会导致在 SQL 服务器中正确插入:

Original: XX{NUL}XX
Quoted:   'XX' + CHAR(0) + 'XX'


更新:2017 年 3 月

错误已在 SQLSRV/5.2.0 中修复。