unserialize() [function.unserialize]: Error at off

2018-12-31 18:26发布

问题:

I am using Hotaru CMS with the Image Upload plugin, I get this error if I try to attach an image to a post, otherwise there is no error.

The offending code (error points to line with **):

/**
     * Retrieve submission step data
     *
     * @param $key - empty when setting
     * @return bool
     */
    public function loadSubmitData($h, $key = \'\')
    {
        // delete everything in this table older than 30 minutes:
        $this->deleteTempData($h->db);

        if (!$key) { return false; }

        $cleanKey = preg_replace(\'/[^a-z0-9]+/\',\'\',$key);
        if (strcmp($key,$cleanKey) != 0) {
            return false;
        } else {
            $sql = \"SELECT tempdata_value FROM \" . TABLE_TEMPDATA . \" WHERE tempdata_key = %s ORDER BY tempdata_updatedts DESC LIMIT 1\";
            $submitted_data = $h->db->get_var($h->db->prepare($sql, $key));
            **if ($submitted_data) { return unserialize($submitted_data); } else { return false; }** 
        }
    }

Data from the table, notice the end bit has the image info, I am not an expert in PHP so I was wondering what you guys/gals might think?

tempdata_value:

a:10:{s:16:\"submit_editorial\";b:0;s:15:\"submit_orig_url\";s:13:\"www.bbc.co.uk\";s:12:\"submit_title\";s:14:\"No title found\";s:14:\"submit_content\";s:12:\"dnfsdkfjdfdf\";s:15:\"submit_category\";i:2;s:11:\"submit_tags\";s:3:\"bbc\";s:9:\"submit_id\";b:0;s:16:\"submit_subscribe\";i:0;s:15:\"submit_comments\";s:4:\"open\";s:5:\"image\";s:19:\"C:fakepath100.jpg\";}

Edit: I think I\'ve found the serialize bit...

/**
     * Save submission step data
     *
     * @return bool
     */
    public function saveSubmitData($h)
    {
        // delete everything in this table older than 30 minutes:
        $this->deleteTempData($h->db);

        $sid = preg_replace(\'/[^a-z0-9]+/i\', \'\', session_id());
        $key = md5(microtime() . $sid . rand());
        $sql = \"INSERT INTO \" . TABLE_TEMPDATA . \" (tempdata_key, tempdata_value, tempdata_updateby) VALUES (%s,%s, %d)\";
        $h->db->query($h->db->prepare($sql, $key, serialize($h->vars[\'submitted_data\']), $h->currentUser->id));
        return $key;
    }

回答1:

unserialize() [function.unserialize]: Error at offset was dues to invalid serialization data due to invalid length

Quick Fix

What you can do is is recalculating the length of the elements in serialized array

You current serialized data

$data = \'a:10:{s:16:\"submit_editorial\";b:0;s:15:\"submit_orig_url\";s:13:\"www.bbc.co.uk\";s:12:\"submit_title\";s:14:\"No title found\";s:14:\"submit_content\";s:12:\"dnfsdkfjdfdf\";s:15:\"submit_category\";i:2;s:11:\"submit_tags\";s:3:\"bbc\";s:9:\"submit_id\";b:0;s:16:\"submit_subscribe\";i:0;s:15:\"submit_comments\";s:4:\"open\";s:5:\"image\";s:19:\"C:fakepath100.jpg\";}\';

Example without recalculation

var_dump(unserialize($data));

Output

Notice: unserialize() [function.unserialize]: Error at offset 337 of 338 bytes

Recalculating

$data = preg_replace(\'!s:(\\d+):\"(.*?)\";!e\', \"\'s:\'.strlen(\'$2\').\':\\\"$2\\\";\'\", $data);
var_dump(unserialize($data));

Output

array
  \'submit_editorial\' => boolean false
  \'submit_orig_url\' => string \'www.bbc.co.uk\' (length=13)
  \'submit_title\' => string \'No title found\' (length=14)
  \'submit_content\' => string \'dnfsdkfjdfdf\' (length=12)
  \'submit_category\' => int 2
  \'submit_tags\' => string \'bbc\' (length=3)
  \'submit_id\' => boolean false
  \'submit_subscribe\' => int 0
  \'submit_comments\' => string \'open\' (length=4)
  \'image\' => string \'C:fakepath100.jpg\' (length=17)

Recommendation .. I

Instead of using this kind of quick fix ... i\"ll advice you update the question with

  • How you are serializing your data

  • How you are Saving it ..

================================ EDIT 1 ===============================

The Error

The Error was generated because of use of double quote \" instead single quote \' that is why C:\\fakepath\\100.png was converted to C:fakepath100.jpg

To fix the error

You need to change $h->vars[\'submitted_data\'] From (Note the singe quite \' )

Replace

 $h->vars[\'submitted_data\'][\'image\'] = \"C:\\fakepath\\100.png\" ;

With

 $h->vars[\'submitted_data\'][\'image\'] = \'C:\\fakepath\\100.png\' ;

Additional Filter

You can also add this simple filter before you call serialize

function satitize(&$value, $key)
{
    $value = addslashes($value);
}

array_walk($h->vars[\'submitted_data\'], \"satitize\");

If you have UTF Characters you can also run

 $h->vars[\'submitted_data\'] = array_map(\"utf8_encode\",$h->vars[\'submitted_data\']);

How to detect the problem in future serialized data

  findSerializeError ( $data1 ) ;

Output

Diffrence 9 != 7
    -> ORD number 57 != 55
    -> Line Number = 315
    -> Section Data1  = pen\";s:5:\"image\";s:19:\"C:fakepath100.jpg
    -> Section Data2  = pen\";s:5:\"image\";s:17:\"C:fakepath100.jpg
                                            ^------- The Error (Element Length)

findSerializeError Function

function findSerializeError($data1) {
    echo \"<pre>\";
    $data2 = preg_replace ( \'!s:(\\d+):\"(.*?)\";!e\', \"\'s:\'.strlen(\'$2\').\':\\\"$2\\\";\'\",$data1 );
    $max = (strlen ( $data1 ) > strlen ( $data2 )) ? strlen ( $data1 ) : strlen ( $data2 );

    echo $data1 . PHP_EOL;
    echo $data2 . PHP_EOL;

    for($i = 0; $i < $max; $i ++) {

        if (@$data1 {$i} !== @$data2 {$i}) {

            echo \"Diffrence \", @$data1 {$i}, \" != \", @$data2 {$i}, PHP_EOL;
            echo \"\\t-> ORD number \", ord ( @$data1 {$i} ), \" != \", ord ( @$data2 {$i} ), PHP_EOL;
            echo \"\\t-> Line Number = $i\" . PHP_EOL;

            $start = ($i - 20);
            $start = ($start < 0) ? 0 : $start;
            $length = 40;

            $point = $max - $i;
            if ($point < 20) {
                $rlength = 1;
                $rpoint = - $point;
            } else {
                $rpoint = $length - 20;
                $rlength = 1;
            }

            echo \"\\t-> Section Data1  = \", substr_replace ( substr ( $data1, $start, $length ), \"<b style=\\\"color:green\\\">{$data1 {$i}}</b>\", $rpoint, $rlength ), PHP_EOL;
            echo \"\\t-> Section Data2  = \", substr_replace ( substr ( $data2, $start, $length ), \"<b style=\\\"color:red\\\">{$data2 {$i}}</b>\", $rpoint, $rlength ), PHP_EOL;
        }

    }

}

A better way to save to Database

$toDatabse = base64_encode(serialize($data));  // Save to database
$fromDatabase = unserialize(base64_decode($data)); //Getting Save Format 


回答2:

I don\'t have enough reputation to comment, so I hope this is seen by people using the above \"correct\" answer:

Since php 5.5 the /e modifier in preg_replace() has been deprecated completely and the preg_match above will error out. The php documentation recommends using preg_match_callback in its place.

Please find the following solution as an alternative to the above proposed preg_match.

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


回答3:

There\'s another reason unserialize() failed because you improperly put serialized data into the database see Official Explanation here. Since serialize() returns binary data and php variables don\'t care encoding methods, so that putting it into TEXT, VARCHAR() will cause this error.

Solution: store serialized data into BLOB in your table.



回答4:

Quick Fix

Recalculating the length of the elements in serialized array - but don\'t use (preg_replace) it\'s deprecated - better use preg_replace_callback:

$data = preg_replace_callback(\'!s:(\\d+):\"(.*?)\";!\', function($m) { return \'s:\'.mb_strlen($m[2]).\':\"\'.$m[2].\'\";\'; }, $data);


回答5:

This error is caused because your charset is wrong.

Set charset after open tag:

header(\'Content-Type: text/html; charset=utf-8\');

And set charset utf8 in your database :

mysql_query(\"SET NAMES \'utf8\'\");


回答6:

public function unserializeKeySkills($string) {

    $output = array();
    $string = trim(preg_replace(\'/\\s\\s+/\', \' \',$string));
    $string = preg_replace_callback(\'!s:(\\d+):\"(.*?)\";!\', function($m) { return \'s:\'.strlen($m[2]).\':\"\'.$m[2].\'\";\'; }, utf8_encode( trim(preg_replace(\'/\\s\\s+/\', \' \',$string)) ));
    try {
        $output =  unserialize($string);
    } catch (\\Exception $e) {
        \\Log::error(\"unserialize Data : \" .print_r($string,true));
    }
    return $output;
}


回答7:

You can fix broken serialize string using following function, with multibyte character handling.

function repairSerializeString($value)
{

    $regex = \'/s:([0-9]+):\"(.*?)\"/\';

    return preg_replace_callback(
        $regex, function($match) {
            return \"s:\".mb_strlen($match[2]).\":\\\"\".$match[2].\"\\\"\"; 
        },
        $value
    );
}


回答8:

$badData = \'a:2:{i:0;s:16:\"as:45:\"d\";
Is \\n\";i:1;s:19:\"as:45:\"d\";
Is \\r\\n\";}\';

You can not fix a broken serialize string using the proposed regexes:

$data = preg_replace(\'!s:(\\d+):\"(.*?)\";!e\', \"\'s:\'.strlen(\'$2\').\':\\\"$2\\\";\'\", $badData);
var_dump(@unserialize($data)); // Output: bool(false)

// or

$data = preg_replace_callback(
    \'/s:(\\d+):\"(.*?)\";/\',
    function($m){
        return \'s:\' . mb_strlen($m[2]) . \':\"\' . $m[2] . \'\";\';
    },
    $badData
);
var_dump(@unserialize($data)); // Output: bool(false)

You can fix broken serialize string using following regex:

$data = preg_replace_callback(
    \'/(?<=^|\\{|;)s:(\\d+):\\\"(.*?)\\\";(?=[asbdiO]\\:\\d|N;|\\}|$)/s\',
    function($m){
        return \'s:\' . mb_strlen($m[2]) . \':\"\' . $m[2] . \'\";\';
    },
    $badData
);

var_dump(@unserialize($data));

Output

array(2) {
  [0] =>
  string(17) \"as:45:\"d\";
Is \\n\"
  [1] =>
  string(19) \"as:45:\"d\";
Is \\r\\n\"
}

or

array(2) {
  [0] =>
  string(16) \"as:45:\"d\";
Is \\n\"
  [1] =>
  string(18) \"as:45:\"d\";
Is \\r\\n\"
}


回答9:

the official docs says it should return false and set E_NOTICE

but since you got error then the error reporting is set to be triggered by E_NOTICE

here is a fix to allow you detect false returned by unserialize

$old_err=error_reporting(); 
error_reporting($old_err & ~E_NOTICE);
$object = unserialize($serialized_data);
error_reporting($old_err);

you might want to consider use base64 encode/decode

$string=base64_encode(serialize($obj));
unserialize(base64_decode($string));


回答10:

In my case I was storing serialized data in BLOB field of MySQL DB which apparently wasn\'t big enough to contain the whole value and truncated it. Such a string obviously could not be unserialized.
Once converted that field to MEDIUMBLOB the problem dissipated. Also it may be needed to switch in table options ROW_FORMAT to DYNAMIC or COMPRESSED.



回答11:

After having tried some things on this page without success I had a look in the page-source and remarked that all quotes in the serialized string have been replaced by html-entities. Decoding these entities helps avoiding much headache:

$myVar = html_entity_decode($myVar);


回答12:

You will have to alter the collation type to utf8_unicode_ci and the problem will be fixed.



回答13:

Another reason of this problem can be column type of \"payload\" sessions table. If you have huge data on session, a text column wouldn\'t be enough. You will need MEDIUMTEXT or even LONGTEXT.



回答14:

I faced same problem while unserializing data. Found out that if there\'s a \", \', :, or ; in any of the array values, the serialization gets corrupted. I had a : in my array, so removed it and it was fixed.

Hope it helps someone.