اذهب إلى المحتوى

السلسلة Serialization في PHP


سارة محمد2

الدالة serialize()‎ في PHP لها الشكل العام التالي:

serialize ( mixed $value )

يعبّر المعامل value عن القيمة التي نريد سَلسَلتها، تعالج الدالة serialize()‎ كل الأنواع، عدا الأنواع الموردية (resource-type)، كما يمكنك سَلسَلة المصفوفات التي تحتوي مراجع إلى نفسها، المراجع الدائرية داخل المصفوفة/الكائن التي تُسلسلها ستُخزَّن أيضًا وأي مرجع آخر سيُفقد. تحاول PHP عند سَلسَلة الكائن استدعاء الدالة ‎__sleep()‎ قبل القيام بالسَلسَلة وذلك للسماح للكائن بالقيام بالتنظيف حتى اللحظة الأخيرة قبل بدء السَلسَلة. وكذلك عند استعادة كائن باستخدام unserialize()‎ تُستدعى الدالة ‎__wakeup()‎. يكون لأعضاء الكائن الخاصة اسم الصنف ملحقًا باسم العضو، وللأعضاء المحمية العلامة '*' ملحقًا باسم العضو. تحتوي هذه القيم المُلحقة على وحدات بايت فارغة على كلا الجانبين.

سَلسَلة أنواع مختلفة

تولّد تمثيلًا قابلًا للتخزين لقيمة ما، ويعدّ هذا مفيدًا لتخزين أو تمرير قيم PHP دون خسارة نوعها وبنيتها.

نستخدم unserialize()‎ لنعيد السلسلة النصية المُسَلسَلة إلى قيمة PHP من جديد.

سَلسَلة سلسلة نصية

$string = "Hello world";
echo serialize($string);

// s:11:"Hello world";

سَلسَلة قيمة عشرية

$double = 1.5;
echo serialize($double);

// d:1.5;

سَلسَلة عدد صحيح

$integer = 65;
echo serialize($integer);

// i:65;

سَلسَلة قيمة منطقية

$boolean = true;
echo serialize($boolean);

// b:1;

$boolean = false;
echo serialize($boolean);

// b:0;

سَلسَلة القيمة null

$null = null;
echo serialize($null);

// N;

سَلسَلة مصفوفة

$array = array(
    25,
    'String',
    'Array'=> ['Multi Dimension','Array'],
    'boolean'=> true,
    // ‫‎⁢$obj من المثال في الأعلى
    'Object'=>$obj,
    null,
    3.445
);

/*
سيرمي هذا خطأً فادحًا 
$array['function'] = function() { return "function"; };
*/

echo serialize($array);

// a:7:{i:0;i:25;i:1;s:6:"String";s:5:"Array";a:2:{i:0;s:15:"Multi
Dimension";i:1;s:5:"Array";}s:7:"boolean";b:1;s:6:"Object";O:3:"abc":1:{s:1:"i";i:1;}i:2;N;i:3;d:3.4449999999999998;}

سَلسَلة كائن

تحاول PHP عند سَلسَلة كائن استدعاء الدالة ‎__sleep()‎ قبل القيام بالسَلسَلة وذلك للسماح للكائن بالقيام بالتنظيف حتى اللحظة الأخيرة قبل بدء السَلسَلة. وكذلك عند استعادة كائن باستخدام unserialize()‎ تُستدعى الدالة ‎__wakeup()‎.

class abc {
    var $i = 1;

    function foo() {
        return 'hello world';
    }
}

$object = new abc();
echo serialize($object);

// O:3:"abc":1:{s:1:"i";i:1;}

لاحظ أنّه لا يمكن سَلسَلة المغلِّفات (Closures)

$function = function () { echo 'Hello World!'; };
$function(); // prints "hello!"
$serializedResult = serialize($function); 

// Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed'

مشاكل الحماية مع فك السَلسَلة

يمكن أن يكون استخدام الدالة unserialize لفك سَلسَلة دخل المستخدم خطيرًا.

تحذير: لا تمرّر دخل المستخدم غير الموثوق به للدالة unserialize، يمكن أن يؤدي فك السَلسَلة إلى تحميل التعليمات البرمجية وتنفيذها بسبب استنساخ الكائن والتحميل التلقائي وقد يتمكن المستخدم الضار من استغلال ذلك. استخدم تنسيق تبادل بيانات قياسي مثل JSON (باستخدام json_decode()‎ وjson_encode()‎) إذا كنت تحتاج إلى تمرير بيانات مسَلسَلة إلى المستخدم.

الهجمات الممكنة: حقن كائن PHP

ثغرة على مستوى التطبيق يمكن أن تسمح للمهاجم بأداء أنواع مختلفة من الهجمات الخبيثة مثل حقن الشيفرة وحقن SQL واجتياز المسار (Path Traversal) وتطبيق حجب الخدمة (Denial of Service) بالاعتماد على السياق. تحدث الثغرة عندما لا يُعقَّم دخل المستخدم قبل تمريره إلى دالة unserialize. بما أنّ PHP تسمح بسَلسَلة الكائن، يمكن للمهاجمين تمرير سلاسل نصية مسَلسَلة مخصصة إلى استدعاء الدالة unserialize المعرضة للخطر مما يؤدي إلى حقن كائن (أو كائنات) PHP عشوائي في نطاق التطبيق.

يجب تحقيق شرطين لاستغلال ثغرة حقن كائن PHP بنجاح:

  • أن يكون للتطبيق صنف ينفّذ تابع PHP سحري (مثل ‎__wakeup أو ‎__destruct) يمكن أن يُستخدم لتنفيذ هجمات ضارة أو بدء سلسلة POP (‏Property Oriented Programming).
  • أن يُصرَّح عن جميع الأصناف المستخدمة خلال الهجوم عند استدعاء الدالة unserialize المعرضة للخطر وإلا يجب دعم التحميل التلقائي للصنف لمثل هذه الكائنات.

مثال1 - هجوم اجتياز المسار (Path Traversal Attack):

يظهر المثال التالي صنف PHP مع تابع ‎__destruct القابل للاستغلال:

class Example1
{
    public $cache_file;

    function __construct()
    {
        // ‫بعض الشيفرة هنا...
    }

    function __destruct()
    {
        $file = "/var/www/cache/tmp/{$this->cache_file}";
        if (file_exists($file)) @unlink($file);
    }
}

// ‫بعض الشيفرة هنا…

$user_data = unserialize($_GET['data']);

// ‫بعض الشيفرة هنا...

قد يكون المهاجم في هذا المثال قادرًا على حذف ملف عشوائي عبر هجوم اجتياز المسار مثل طلب المسار التالي:

http://testsite.com/vuln.php?data=O:8:"Example1":1:{s:10:"cache_file";s:15:"../../index.php";}

مثال2 - هجوم حقن الشيفرة (Code Injection attack):

يظهر المثال التالي صنف PHP مع تابع ‎__wakeup القابل للاستغلال:

class Example2
{
    private $hook;

    function __construct()
    {
        // ‫بعض الشيفرة هنا...
    }

    function __wakeup()
    {
        if (isset($this->hook)) eval($this->hook);
    }
}

// ‫بعض الشيفرة هنا…

$user_data = unserialize($_COOKIE['data']);

// ‫بعض الشيفرة هنا...

يمكن أن يكون المهاجم في هذا المثال قادرًا على القيام بهجوم حقن الشيفرة بإرسال طلب HTTP مثل الطلب التالي:

GET /vuln.php HTTP/1.0
Host: testsite.com
Cookie:
data=O%3A8%3A%22Example2%22%3A1%3A%7Bs%3A14%3A%22%00Example2%00hook%22%3Bs%3A10%3A%22phpinfo%28%29%
3B%22%3B%7D
Connection: close

حيث أُنشئ معامل ملف تعريف الارتباط "data" من قِبل السكربت التالي:

class Example2
{
    private $hook = "phpinfo();";
}

print urlencode(serialize(new Example2));

سَلسَلة/ فك سَلسَلة كائن

تعيد الدالة serialize()‎ سلسلة نصية تتضمن تمثيل مجرى بايتات لأي قيمة يمكن تخزينها في PHP. يمكنك استخدام الدالة unserialize()‎ لإعادة تكوين قيمة المتغير الأصلية.

نكتب الشيفرة التالية لسَلسَلة (serialize) كائن:

serialize($object);

نكتب الشيفرة التالية لفك سَلسَلة (Unserialize) كائن:

unserialize($object)

مثال:

$array = array();
$array["a"] = "Foo";
$array["b"] = "Bar";
$array["c"] = "Baz";
$array["d"] = "Wom";

$serializedArray = serialize($array);
echo $serializedArray;

/*
a:4:{s:1:"a";s:3:"Foo";s:1:"b";s:3:"Bar";s:1:"c";s:3:"Baz";s:1:"d";s:3:"Wom";}
*/

الواجهة القابلة للسَلسَلة

لم تعد الأصناف التي تنفّذ هذه الواجهة تدعم التوابع السحرية ‎__sleep()‎ و‎__sleep()‎. يُستدعى تابع السَلسَلة عندما يحتاج كائن إلى السَلسَلة، وهذا لا يشغّل التابع ‎__destruct()‎ وليس له آثار جانبية أخرى ما لم يُبرمج داخل تابع السَلسَلة، عند فك سَلسَلة البيانات يُعرف الصف ويُستدعى تابع unserialize()‎ المناسب كبانٍ بدلًا من استدعاء ‎__construct()‎. إذا كنت تحتاج إلى تنفيذ الباني القياسي فيمكنك القيام بذلك في التابع.

الاستخدام الأساسي

class obj implements Serializable {
    private $data;

    public function __construct() {
        $this->data = "My private data";
    }

    public function serialize() {
        return serialize($this->data);
    }

    public function unserialize($data) {
        $this->data = unserialize($data);
    }

    public function getData() {
        return $this->data;
    }
}

$obj = new obj;
$ser = serialize($obj);

var_dump($ser); 
// string(38) "C:3:"obj":23:{s:15:"My private data";}"

$newobj = unserialize($ser);
var_dump($newobj->getData()); 
// string(15) "My private data"

ترجمة -وبتصرف- للفصول [Object Serialization - Serialization] من كتاب PHP Notes for Professionals book

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...