SQL Server的PDO :: quote()错误引用包含ASCII NUL的字符串

时间:2017-09-07 15:47:06

标签: php sql-server pdo sqlsrv

我正在尝试使用pdo_sqlsrv将ASCII NUL字符(\0 aka U+0000)插入到PHP的SQL Server数据库中。这是处理PHP序列化字符串的要求,它包含NUL字符来表示私有/受保护变量。

但是,有一些关于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\0XX";

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

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

?>

预期输出:

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

实际输出:

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

任何人都可以解释这种奇怪的行为 - 更重要的是 - 解释如何解决它?

更新

似乎最终字符是随机的,因为在后续运行中它是e。这意味着某种形式的存储器访问错误,例如,读过字符串的结尾。也许是pdo_sqlsrv实现中的一个错误?

3 个答案:

答案 0 :(得分:2)

  

任何人都可以解释这种奇怪的行为......

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

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

  

... - 更重要的是 - 解释如何解决它?

我们实现的解决方案是检测这种情况并重写字符串以删除任何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\0XX";

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

print("Original: " . str_replace("\0", "{NUL}", $str) . "\n");
$str = safeQuote($str, $connection);
print("Quoted:   " . str_replace("\0", "{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 \0.
    if (is_string($str) && strpos($str, "\0") !== false) {
        $arrParts = explode("\0", $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 Server中生成正确的插入内容:

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


更新:2017年3月

该错误已在SQLSRV/5.2.0修复。

答案 1 :(得分:0)

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

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

  3.   

    如果您使用此函数构建SQL语句,强烈建议您使用PDO :: prepare()来准备带有绑定参数的SQL语句,而不是使用PDO :: quote()将用户输入插入到SQL语句中。带有绑定参数的预处理语句不仅更易于移植,更方便,不受SQL注入的影响,但执行速度通常比内插查询快得多,因为服务器端和客户端都可以缓存查询的编译形式。

    直播示例

    Sandbox

    <强>输出

    <?php
    $pdo = new \PDO('sqlite::memory:', null, null);
    
    $str = "XX\0XX";
    
    print($pdo->quote(addSlashes($str))); <----> 'XX\0XX'
    
    ?>
    

答案 2 :(得分:0)

在PHP / 5.6.21下的32位SQLSRV / 3.2设置中运行稍微修改过的测试代码版本,表现出一种奇怪的行为:

$str = "XX\0XX";
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存储库中有关于模拟准备的几个错误修正

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

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

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