PHP反序列化因非编码字符而失败?

Posted

技术标签:

【中文标题】PHP反序列化因非编码字符而失败?【英文标题】:PHP unserialize fails with non-encoded characters? 【发布时间】:2011-02-20 15:50:23 【问题描述】:
$ser = 'a:2:i:0;s:5:"héllö";i:1;s:5:"wörld";'; // fails
$ser2 = 'a:2:i:0;s:5:"hello";i:1;s:5:"world";'; // works
$out = unserialize($ser);
$out2 = unserialize($ser2);
print_r($out);
print_r($out2);
echo "<hr>";

但是为什么呢? 我应该在序列化之前编码吗?怎么样?

我使用 javascript 将序列化字符串写入隐藏字段,而不是 php 的 $_POST 在 JS 我有类似的东西:

function writeImgData() 
    var caption_arr = new Array();
    $('.album img').each(function(index) 
         caption_arr.push($(this).attr('alt'));
    );
    $("#hidden-field").attr("value", serializeArray(caption_arr));
;

【问题讨论】:

【参考方案1】:

unserialize() 失败的原因是:

$ser = 'a:2:i:0;s:5:"héllö";i:1;s:5:"wörld";';

是因为héllöwörld 的长度不对,因为PHP 本身不能正确处理多字节字符串:

echo strlen('héllö'); // 7
echo strlen('wörld'); // 6

但是,如果您尝试 unserialize() 以下正确字符串:

$ser = 'a:2:i:0;s:7:"héllö";i:1;s:6:"wörld";';

echo '<pre>';
print_r(unserialize($ser));
echo '</pre>';

有效:

Array
(
    [0] => héllö
    [1] => wörld
)

如果您使用 PHP serialize(),它应该可以正确计算多字节字符串索引的长度。

另一方面,如果您想以多种(编程)语言处理序列化数据,您应该忘记它并转向 JSON 之类的东西,它更加标准化。

【讨论】:

json_encode: "此函数仅适用于 UTF-8 编码数据..." php.net/manual/en/function.json-encode.php 如果您正在使用 serialize( ) 并且 unserialize( ) 仍然失败,请检查您的存储介质。即你应该存储为二进制或blob的mysql。如果您在 mysql 中存储为文本,它将无法处理您的多字节字符。 在php环境之间切换时也要小心。在保存到数据库然后尝试在实时服务器上反序列化之前,我在本地机器上遇到了编码问题。调整字符的字符数解决了这个问题。 这可能也是我两年前遇到的一个问题的答案,但从未找到答案。 ***.com/questions/30289218/…【参考方案2】:

我知道这是在一年前发布的,但我只是遇到了这个问题并遇到了这个问题,实际上我找到了解决方案。这段代码就像魅力一样!

背后的想法很简单。它只是通过重新计算上面@Alix 发布的多字节字符串的长度来帮助您。

一些修改应该适合您的代码:

/**
 * Mulit-byte Unserialize
 *
 * UTF-8 will screw up a serialized string
 *
 * @access private
 * @param string
 * @return string
 */
function mb_unserialize($string) 
    $string = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $string);
    return unserialize($string);

来源:http://snippets.dzone.com/posts/show/6592

在我的机器上测试过,效果很好!!

【讨论】:

在我的情况下,问题出在数据库编码中,所以我在??? 中丢失了部分数据,但是这个功能可以帮助我使代码工作,谢谢 刚刚让我头疼不已!谢谢。 +1 为这项非常有用的工作。我也对其进行了测试,它适用于带有法语口音的 UTF-8 数据(我的服务器上的 PHP 5.3)。 我在下面发布了您的函数已更改为使用 PHP 5.5。感谢您的有用贡献。 其实正则表达式是错误的,因为字符串本身可能包含与序列化模式无关的模式。例如。函数之后的序列化部分...s:28:"some "quotes"; in the middle";... 将返回...s:13:"some \"quotes"; in the middle";...。这就是首先创建序列化的原因之一。【参考方案3】:

Lionel Chan 答案已修改为使用 PHP >= 5.5:

function mb_unserialize($string) 
    $string2 = preg_replace_callback(
        '!s:(\d+):"(.*?)";!s',
        function($m)
            $len = strlen($m[2]);
            $result = "s:$len:\"$m[2]\";";
            return $result;

        ,
        $string);
    return unserialize($string2);
    

此代码从 PHP 5.5 开始使用 preg_replace_callback 作为 preg_replace with the /e modifier is obsolete。

【讨论】:

我不得不使用这个版本来防止编码数组中的 html 字符串在未序列化的字符串中得到错误转义的双引号。 百万感谢@David。我一直在努力转换这个功能很多天了!【参考方案4】:

问题是 - Alix 指出 - 与编码有关。

在 PHP 5.4 之前,PHP 的内部编码是 ISO-8859-1,这种编码对 unicode 中为多字节的某些字符使用单字节。结果是在 UTF-8 系统上序列化的多字节值在 ISO-8859-1 系统上将不可读。

避免此类问题确保所有系统使用相同的编码:

mb_internal_encoding('utf-8');
$arr = array('foo' => 'bár');
$buf = serialize($arr);

可以使用utf8_(encode|decode)进行清理:

// Set system encoding to iso-8859-1
mb_internal_encoding('iso-8859-1');
$arr = unserialize(utf8_encode($serialized));
print_r($arr);

【讨论】:

【参考方案5】:

回复上面的@Lionel,实际上如果序列化字符串本身包含字符序列";(引号后跟分号),则您建议的函数mb_unserialize()将不起作用。 谨慎使用。例如:

$test = 'test";string'; 
// $test is now 's:12:"test";string";'
$string = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $test);
print $string; 
// output: s:4:"test";string";  (Wrong!!)

JSON 是要走的路,正如其他人所提到的,恕我直言

注意:我将此作为新答案发布,因为我不知道如何直接回复(此处为新答案)。

【讨论】:

您很快就能回复 cmets。继续贡献!干杯~ 很高兴知道。有解决办法吗?【参考方案6】:

当另一端不是 PHP 时,不要使用 PHP 序列化/反序列化。它并不意味着是一种可移植的格式——例如,它甚至包括 ascii-1 字符作为受保护的密钥,这在 javascript 中是你不想处理的(即使它工作得很好,但它非常丑陋)。

改为使用 JSON 等可移植格式。 XML 也可以完成这项工作,但 JSON 的开销更少,并且对程序员更友好,因为您可以轻松地将其解析为简单的数据结构,而不必处理 XPath、DOM 树等。

【讨论】:

更不用说从不受信任的来源反序列化会导致任意代码执行。 不幸的是,选择是由其他人的工作强加给我们的。这在从旧项目/系统中导入数据时尤其常见,其中序列化已经在其数据中建立良好。【参考方案7】:

这里还有一个细微的变化,希望对某人有所帮助……我正在序列化一个数组,然后将其写入数据库。在检索数据时,反序列化操作失败。

原来我写入的数据库长文本字段使用的是 latin1 而不是 UTF8。当我切换它时,一切都按计划进行。

感谢以上所有提到字符编码并让我走上正轨的人!

【讨论】:

【参考方案8】:

这个解决方案对我有用:

$unserialized = unserialize(utf8_encode($st));

【讨论】:

【参考方案9】:

我建议你使用 javascript 编码为 json,然后使用json_decode 进行反序列化。

【讨论】:

也就是说,$ser = 'a:2:i:0;s:5:"héllö";i:1;s:5:"wörld";'; var_dump(反序列化($ser));对我很好。你说的失败是什么意思?调用 unserialize() 失败?【参考方案10】:
/**
 * MULIT-BYTE UNSERIALIZE
 *
 * UTF-8 will screw up a serialized string
 *
 * @param string
 * @return string
 */
function mb_unserialize($string) 
    $string = preg_replace_callback('/!s:(\d+):"(.*?)";!se/', function($matches)  return 's:'.strlen($matches[1]).':"'.$matches[1].'";'; , $string);
    return unserialize($string);

【讨论】:

【参考方案11】:

我们可以将字符串分解为一个数组:

$finalArray = array();
$nodeArr = explode('&', $_POST['formData']);

foreach($nodeArr as $value)
    $childArr = explode('=', $value);
    $finalArray[$childArr[0]] = $childArr[1];

【讨论】:

【参考方案12】:

序列化:

foreach ($income_data as $key => &$value)

    $value = urlencode($value);

$data_str = serialize($income_data);

反序列化:

$data = unserialize($data_str);
foreach ($data as $key => &$value)

    $value = urldecode($value);

【讨论】:

【参考方案13】:

这个对我有用。

function mb_unserialize($string) 
    $string = mb_convert_encoding($string, "UTF-8", mb_detect_encoding($string, "UTF-8, ISO-8859-1, ISO-8859-15", true));
    $string = preg_replace_callback(
        '/s:([0-9]+):"(.*?)";/',
        function ($match) 
            return "s:".strlen($match[2]).":\"".$match[2]."\";"; 
        ,
        $string
    );
    return unserialize($string);

【讨论】:

【参考方案14】:

在我的情况下,问题在于 行尾(可能某些编辑器已将我的文件从 DOS 更改为 Unix)。

我将这些自适应包装器放在一起:

function unserialize_fetchError($original, &$unserialized, &$errorMsg) 
    $unserialized = @unserialize($original);
    $errorMsg = error_get_last()['message'];
    return ( $unserialized !== false || $original == 'b:0;' );  // "$original == serialize(false)" is a good serialization even if deserialization actually returns false


function unserialize_checkAllLineEndings($original, &$unserialized, &$errorMsg, &$lineEndings) 
    if ( unserialize_fetchError($original, $unserialized, $errorMsg) ) 
        $lineEndings = 'unchanged';
        return true;
     elseif ( unserialize_fetchError(str_replace("\n", "\n\r", $original), $unserialized, $errorMsg) ) 
        $lineEndings = '\n to \n\r';
        return true;
     elseif ( unserialize_fetchError(str_replace("\n\r", "\n", $original), $unserialized, $errorMsg) ) 
        $lineEndings = '\n\r to \n';
        return true;
     elseif ( unserialize_fetchError(str_replace("\r\n", "\n", $original), $unserialized, $errorMsg) ) 
        $lineEndings = '\r\n to \n';
        return true;
     //else
    return false;

【讨论】:

以上是关于PHP反序列化因非编码字符而失败?的主要内容,如果未能解决你的问题,请参考以下文章

php数组反序列化失败,求解!!!

php中序列化与反序列化

php序列化与反序列化

PHP 序列化与反序列化函数

PHP反序列化漏洞

技术分享php反序列化漏洞初探