如何修复由于在单引号之前删除斜杠而损坏的序列化字符串


How to repair a serialized string that has been corrupted due to a removed slash before a single quote?

我在尝试反序列化数据时遇到错误。发生以下错误:

unserialize(): Error at offset 46 of 151 bytes

以下是序列化数据:

s:151:"a:1:{i:0;a:4:{s:4:"name";s:15:"Chloe O'Gorman";s:6:"gender";s:6:"female";s:3:"age";s:3:"3_6";s:7:"present";s:34:"Something from Frozen or a jigsaw ";}}";

该错误是由数据中的单个引号引起的。当我正在使用的网站和数据库已经上线时,如何缓解此问题?

不幸的是,我无法重写负责序列化数据并将其插入数据库的代码。很可能在整个数据库中多次出现此问题。

是否有可用于转义数据的函数?

在进行了进一步的研究之后,我找到了解决方法。根据这篇博文:

"事实证明,如果有一个"、'、:或 ;在任何数组中 值序列化已损坏。

如果我在一个尚未上线的网站上工作,一种预防方法是在序列化数据存储到数据库中之前base64_encode我的序列化数据,如下所示:

base64_encode( serialize( $my_data ) );

然后:

unserialize( base64_decode( $encoded_serialized_string ) );

检索数据时。

但是,由于我无法更改数据库中已经存储的内容,因此这篇非常有用的帖子(原始帖子不再可用,但看起来像这样)提供了一种解决此问题的解决方案:

$fixed_serialized_data = preg_replace_callback ( '!s:('d+):"(.*?)";!', function($match) {
    return ($match[1] == strlen($match[2])) ? $match[0] : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
}, $my_data );
$result = unserialize( $fixed_serialized_data );

据我所知,您有一个有效的序列化字符串嵌套在有效的序列化字符串中 - 这意味着serialize()在形成您发布的字符串时被调用了两次。

看看你是如何s:151:,然后是:

"a:1:{i:0;a:4:{s:4:"name";s:15:"Chloe O'Gorman";s:6:"gender";s:6:"female";s:3:"age";s:3:"3_6";s:7:"present";s:34:"Something from Frozen or a jigsaw ";}}";

⮤ 是包含预序列化数据的有效单个字符串。

反序列化 THAT 后,您将获得:

a:1:{i:0;a:4:{s:4:"name";s:15:"Chloe O'Gorman";s:6:"gender";s:6:"female";s:3:"age";s:3:"3_6";s:7:"present";s:34:"Something from Frozen or a jigsaw ";}}
//                         ^^--^^^^^^^^^^^^^^-- uh oh, that string value has 14 bytes/characters not 15

它看起来像在字符串处理中的某个地方,并且删除了转义斜杠并损坏了字符串。

序列化数据中的单引号没有任何污点。

您可以选择:

  1. 执行转义调用以盲目地将斜杠应用于字符串中的所有单引号(这可能会导致其他地方的损坏)——假设您想转义项目的后续进程的单引号或
  2. 执行我的以下代码片段,它不会转义单引号,而是调整字节/字符计数以形成有效的

代码:(演示)

$corrupted_byte_counts = <<<STRING
s:151:"a:1:{i:0;a:4:{s:4:"name";s:15:"Chloe O'Gorman";s:6:"gender";s:6:"female";s:3:"age";s:3:"3_6";s:7:"present";s:34:"Something from Frozen or a jigsaw ";}}";
STRING;
$repaired = preg_replace_callback(
        '/s:'d+:"(.*?)";/s',
        function ($m) {
            return 's:' . strlen($m[1]) . ":'"{$m[1]}'";";
        },
        unserialize($corrupted_byte_counts)  // first unserialize string before repairing
    );
echo "corrupted serialized array:'n$corrupted_byte_counts";
echo "'n---'n";
echo "repaired serialized array:'n$repaired";
echo "'n---'n";
print_r(unserialize($repaired));  // unserialize repaired string
echo "'n---'n";
echo serialize($repaired);

输出:

corrupted serialized array:
s:151:"a:1:{i:0;a:4:{s:4:"name";s:15:"Chloe O'Gorman";s:6:"gender";s:6:"female";s:3:"age";s:3:"3_6";s:7:"present";s:34:"Something from Frozen or a jigsaw ";}}";
---
repaired serialized array:
a:1:{i:0;a:4:{s:4:"name";s:14:"Chloe O'Gorman";s:6:"gender";s:6:"female";s:3:"age";s:3:"3_6";s:7:"present";s:34:"Something from Frozen or a jigsaw ";}}
---
Array
(
    [0] => Array
        (
            [name] => Chloe O'Gorman
            [gender] => female
            [age] => 3_6
            [present] => Something from Frozen or a jigsaw 
        )
)
---
s:151:"a:1:{i:0;a:4:{s:4:"name";s:14:"Chloe O'Gorman";s:6:"gender";s:6:"female";s:3:"age";s:3:"3_6";s:7:"present";s:34:"Something from Frozen or a jigsaw ";}}";

*请记住,如果要将数据返回到其原始的 Matryoshka 序列化形式,则需要在 $repaired 上再次调用 serialize()

**如果您有包含";的子字符串,您可以尝试我的代码段的扩展版本。

发布的序列化文本没有错。里面的引号不需要转义,因为 PHP 使用类型长度指示符来找出事情开始/停止的位置。例如

php > $foo = "This string contains a '" quote and a ' quote";
php > $bar = serialize($foo);
php > $baz = unserialize($bar);
php > echo "$foo'n$bar'n$baz'n";
This string contains a " quote and a ' quote
s:44:"This string contains a " quote and a ' quote";
This string contains a " quote and a ' quote

请注意,序列化字符串中缺少任何类型的转义 - 字符串中的引号按原样存在,没有引号,没有转义,没有编码。

发布后,序列化数据将正确反序列化为纯 JSON 字符串,而不会出现问题。

php nowdoc

unserialize(<<<'DDDD'
[SERIALIZE_STR]
DDDD
);