developer tip

잘못된 바이트 수 길이로 인해 손상된 직렬화 된 문자열을 복구하는 방법은 무엇입니까?

optionbox 2020. 9. 2. 17:37
반응형

잘못된 바이트 수 길이로 인해 손상된 직렬화 된 문자열을 복구하는 방법은 무엇입니까?


이미지 업로드 플러그인과 함께 Hotaru CMS를 사용하고 있습니다. 게시물에 이미지를 첨부하려고하면이 오류가 발생합니다. 그렇지 않으면 오류가 없습니다.

unserialize () [function.unserialize] : 오프셋 오류

문제가되는 코드 (오류가 **와 일치 함) :

/**
     * 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; }** 
        }
    }

테이블의 데이터, 끝 비트에 이미지 정보가 있음을 알 수 있습니다. 저는 PHP 전문가가 아니므로 여러분이 어떻게 생각할지 궁금합니다.

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";}

편집 : 직렬화 비트를 찾은 것 같습니다 ...

/**
     * 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;
    }

unserialize() [function.unserialize]: Error at offsetinvalid serialization data유효하지 않은 길이 인해

빠른 수정

당신이 할 수있는 것은 recalculating the length직렬화 된 배열의 요소입니다.

현재 직렬화 된 데이터

$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";}';

재 계산이없는 예

var_dump(unserialize($data));

산출

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

재 계산

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

산출

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)

추천 .. 나

이런 종류의 빠른 수정을 사용하는 대신 ... 질문을 업데이트 할 때 조언을 드리겠습니다.

  • 데이터 직렬화 방법

  • 저장 방법 ..

=============================== 편집 1 ================ ===============

오류

오류 때문에 큰 따옴표의 사용으로 생성 된 "대신 작은 따옴표 '이유 C:\fakepath\100.png로 전환되었다C:fakepath100.jpg

오류를 수정하려면

당신은 변경해야합니다 $h->vars['submitted_data'](꽤 분량의 주에서 ')

바꾸다

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

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

추가 필터

serialize를 호출하기 전에이 간단한 필터를 추가 할 수도 있습니다.

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

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

UTF 문자가 있으면 실행할 수도 있습니다.

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

향후 직렬화 된 데이터에서 문제를 감지하는 방법

  findSerializeError ( $data1 ) ;

산출

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 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;
        }

    }

}

데이터베이스에 저장하는 더 좋은 방법

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

나는 논평 할만한 평판이 충분하지 않기 때문에 위의 "정확한"답변을 사용하는 사람들이 이것을 보길 바랍니다.

PHP 5.5 이후 preg_replace ()의 / e 수정자는 완전히 사용되지 않으며 위의 preg_match는 오류가 발생합니다. PHP 문서는 대신 preg_match_callback을 사용하도록 권장합니다.

위에서 제안한 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 );

unserialize()직렬화 된 데이터를 데이터베이스에 부적절하게 넣었 기 때문에 실패한 또 다른 이유 있습니다. 여기의 공식 설명을 참조 하십시오. 이후 serialize()반환 이진 데이터와 PHP 변수 방법을 인코딩 상관 없어, TEXT로 퍼팅 그래서, VARCHAR ()이 오류가 발생합니다.

솔루션 : 직렬화 된 데이터를 테이블의 BLOB에 저장하십시오.


빠른 수정

직렬화 된 배열에서 요소의 길이를 재 계산하지만 사용하지 마십시오 (preg_replace). 더 이상 사용되지 않습니다. preg_replace_callback을 사용하는 것이 좋습니다.

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

편집 : 새 버전 지금은 단지 잘못되지 길이지만 또한 수정 행 구분 및 aczent와 올바른 문자를 계산 (덕분에 mickmackusa )

// New Version
$data = preg_replace_callback('!s:\d+:"(.*?)";!s', function($m) { return "s:" . strlen($m[1]) . ':"'.$m[1].'";'; }, $data);

이 오류는 문자 세트가 잘못 되었기 때문에 발생합니다.

열린 태그 뒤에 문자 세트 설정 :

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

그리고 데이터베이스에 charset utf8을 설정하십시오.

mysql_query("SET NAMES 'utf8'");

멀티 바이트 문자 처리 와 함께 다음 함수를 사용하여 끊어진 직렬화 문자열을 수정할 수 있습니다 .

function repairSerializeString($value)
{

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

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

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;
}

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

제안 된 정규식을 사용하여 끊어진 직렬화 문자열을 수정할 수 없습니다.

$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:' . strlen($m[2]) . ':"' . $m[2] . '";';
    },
    $badData
);
var_dump(@unserialize($data)); // Output: bool(false)

다음 정규식을 사용하여 끊어진 직렬화 문자열을 수정할 수 있습니다.

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

var_dump(@unserialize($data));

산출

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

또는

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

공식 문서는 그것이 거짓과 세트 E_NOTICE를 반환해야 말한다

하지만 오류가 발생 했으므로 오류보고는 E_NOTICE에 의해 트리거되도록 설정됩니다.

여기에 의해 반환 된 거짓을 감지 할 수있는 수정 사항이 있습니다. unserialize

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

base64 인코딩 / 디코딩 사용을 고려할 수 있습니다.

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

제 경우 BLOB에는 MySQL DB 필드에 직렬화 된 데이터를 저장하고 있었는데 , 이는 전체 값을 포함 할만큼 크지 않고 잘 렸습니다. 이러한 문자열은 분명히 직렬화 해제 될 수 없습니다.
일단 그 필드를 MEDIUMBLOB문제 로 변환하면 사라졌습니다. 또한 테이블 옵션 ROW_FORMATDYNAMIC또는 로 전환해야 할 수도 있습니다 COMPRESSED.


성공하지 못한 채이 페이지에서 몇 가지를 시도한 후 페이지 소스를 살펴보고 직렬화 된 문자열의 모든 따옴표가 html-entities로 대체되었음을 언급했습니다. 이러한 엔티티를 디코딩하면 많은 골칫거리를 피할 수 있습니다.

$myVar = html_entity_decode($myVar);

이 질문의 손상은 직렬화 된 문자열 끝에있는 단일 하위 문자열로 분리되어 image파일 이름 을 느리게 업데이트하려는 사람이 수동으로 대체했을 수 있습니다 . 이 사실은 OP의 게시 된 데이터를 사용하여 아래에있는 내 데모 링크 명백 할 것이다 - 짧은에, C:fakepath100.jpg길이가 없습니다 19, 그것은되어야한다 17.

직렬화 된 문자열 손상은 잘못된 바이트 / 문자 수 번호로 제한되므로 다음은 올바른 바이트 수 값으로 손상된 문자열을 업데이트하는 좋은 작업을 수행합니다.

다음 정규식 기반 교체는 바이트 수를 수정하는 데만 효과적이며 그 이상은 아닙니다.

많은 이전 게시물이 다른 사람의 정규식 패턴을 복사하여 붙여 넣은 것처럼 보입니다. 대체에 사용되지 않을 경우 잠재적으로 손상된 바이트 수를 캡처 할 이유가 없습니다. 또한 s패턴 수정자를 추가하는 것은 문자열 값에 줄 바꿈 / 줄 반환이 포함 된 경우 합당한 포함입니다.

* 직렬화로 멀티 바이트 문자의 처리를 인식 하지 못하는 경우 문자 수가 아닌 바이트 수가 저장되므로 사용자 정의 콜백에서 사용해서는 안됩니다.mb_strlen() 내 출력을 참조하십시오.

코드 : ( OP 데이터가 포함 된 데모 ) ( 임의 샘플 데이터가 포함 된 데모 ) ( 조건이 교체 된 데모 )

$corrupted = <<<STRING
a:4:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1
newline2";i:3;s:6:"garçon";}
STRING;

$repaired = preg_replace_callback(
        '/s:\d+:"(.*?)";/s',
        //  ^^^- matched/consumed but not captured because not used in replacement
        function ($m) {
            return "s:" . strlen($m[1]) . ":\"{$m[1]}\";";
        },
        $corrupted
    );

echo $corrupted , "\n" , $repaired;
echo "\n---\n";
var_export(unserialize($repaired));

산출:

a:4:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1
Newline2";i:3;s:6:"garçon";}
a:4:{i:0;s:5:"three";i:1;s:4:"five";i:2;s:17:"newline1
Newline2";i:3;s:7:"garçon";}
---
array (
  0 => 'three',
  1 => 'five',
  2 => 'newline1
Newline2',
  3 => 'garçon',
)

One leg down the rabbit hole... The above works fine even if double quotes occur in a string value, but if a string value contains "; or some other monkeywrenching sbustring, you'll need to go a little further and implement "lookarounds". My new pattern

checks that the leading s is:

  • the start of the entire input string or
  • preceded by ;

and checks that the "; is:

  • at the end of the entire input string or
  • followed by } or
  • followed by a string or integer declaration s: or i:

I haven't test each and every possibility; in fact, I am relatively unfamiliar with all of the possibilities in a serialized string because I never elect to work with serialized data -- always json in modern applications. If there are additional possible leading or trailing characters, leave a comment and I'll extend the lookarounds.

Extended snippet: (Demo)

$corrupted_byte_counts = <<<STRING
a:12:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1
newline2";i:3;s:6:"garçon";i:4;s:111:"double " quote \"escaped";i:5;s:1:"a,comma";i:6;s:9:"a:colon";i:7;s:0:"single 'quote";i:8;s:999:"semi;colon";s:5:"assoc";s:3:"yes";i:9;s:1:"monkey";wrenching doublequote-semicolon";s:3:"s:";s:9:"val s: val";}
STRING;

$repaired = preg_replace_callback(
        '/(?<=^|;)s:\d+:"(.*?)";(?=$|}|[si]:)/s',
        //^^^^^^^^--------------^^^^^^^^^^^^^-- some additional validation
        function ($m) {
            return 's:' . strlen($m[1]) . ":\"{$m[1]}\";";
        },
        $corrupted_byte_counts
    );

echo "corrupted serialized array:\n$corrupted_byte_counts";
echo "\n---\n";
echo "repaired serialized array:\n$repaired";
echo "\n---\n";
print_r(unserialize($repaired));

Output:

corrupted serialized array:
a:12:{i:0;s:3:"three";i:1;s:5:"five";i:2;s:2:"newline1
newline2";i:3;s:6:"garçon";i:4;s:111:"double " quote \"escaped";i:5;s:1:"a,comma";i:6;s:9:"a:colon";i:7;s:0:"single 'quote";i:8;s:999:"semi;colon";s:5:"assoc";s:3:"yes";i:9;s:1:"monkey";wrenching doublequote-semicolon";s:3:"s:";s:9:"val s: val";}
---
repaired serialized array:
a:12:{i:0;s:5:"three";i:1;s:4:"five";i:2;s:17:"newline1
newline2";i:3;s:7:"garçon";i:4;s:24:"double " quote \"escaped";i:5;s:7:"a,comma";i:6;s:7:"a:colon";i:7;s:13:"single 'quote";i:8;s:10:"semi;colon";s:5:"assoc";s:3:"yes";i:9;s:39:"monkey";wrenching doublequote-semicolon";s:2:"s:";s:10:"val s: val";}
---
Array
(
    [0] => three
    [1] => five
    [2] => newline1
newline2
    [3] => garçon
    [4] => double " quote \"escaped
    [5] => a,comma
    [6] => a:colon
    [7] => single 'quote
    [8] => semi;colon
    [assoc] => yes
    [9] => monkey";wrenching doublequote-semicolon
    [s:] => val s: val
)

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


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.


Here is an Online Tool for fixing a corrupted serialized string.

I'd like to add that this mostly happens due to a search and replace done on the DB and the serialization data(specially the key length) doesn't get updated as per the replace and that causes the "corruption".

Nonetheless, The above tool uses the following logic to fix the serialization data (Copied From Here).

function error_correction_serialise($string){
    // at first, check if "fixing" is really needed at all. After that, security checkup.
    if ( @unserialize($string) !== true &&  preg_match('/^[aOs]:/', $string) ) {
         $string = preg_replace_callback( '/s\:(\d+)\:\"(.*?)\";/s',    function($matches){return 's:'.strlen($matches[2]).':"'.$matches[2].'";'; },   $string );
    }
    return $string;
} 

참고URL : https://stackoverflow.com/questions/10152904/how-to-repair-a-serialized-string-which-has-been-corrupted-by-an-incorrect-byte

반응형