อธิบาย PHP Serialization (OWASP A8:2017-Insecure Deserialization)
ใน OWASP Top 10 มีความเสี่ยงหนึ่งที่ถูกเพิ่มเข้ามานั่นคือ A8:2017-Insecure Deserializationซึ่งเป็นช่องโหว่ที่ไม่พบมากนัก แต่ก็ถือว่าเป็นช่องโหว่อีกช่องโหว่หนึ่งที่รุนแรงมากเช่นกัน เรามาดูกันดีกว่าว่ามันคืออะไรครับ
PHP Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php classTestClass { // A variable public$variable = 'This is a string'; // A simple method public function PrintVariable() { echo$this->variable; } } // Create an object $object = new TestClass(); // Call a method $object->PrintVariable(); ?> |
โดยแต่ละ magic function นั้นจะถูกใช้งานแตกต่างกัน
- __construct จะถูกใช้เมื่อ object ถูกสร้าง (constructor)
- __destruct จะถูกใช้เมื่อ object ถูกทำลาย (destructor)
- __toString จะถูกใช้เมื่อ object ถูกใช้ไปเป็น string
ยกตัวอย่าง magic function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?php classTestClass { // A variable public $variable = 'This is a string'; // A simple method public function PrintVariable() { echo $this->variable . '<br/>'; } // Constructor public function __construct() { echo '__construct <br />'; } // Destructor public function __destruct() { echo '__destruct <br/>'; } // Call public function __toString() { return '__toString<br/>'; } } // Create an object // Will call __construct $object = new TestClass(); // Call a method // Will print 'This is a string' $object->PrintVariable(); // Object act as a string // Will call __toString echo $object; // End of PHP script // Will call __destruct ?> |
1 2 3 4 |
__construct This is a string __toString __destruct |
PHP Object Serialization
Serialization คือวิธีการในการที่เราจะ save object ที่เราสร้างขึ้นแล้วเก็บไปใช้ต่อได้ โดยการจะทำดังกล่าวจะต้องเรียกใช้ function “serialize” โดยผลลัพธ์ที่ return กลับมาจาก function ดังกล่าวจะเป็นลักษณะ string ซึ่งอยู่ในลักษณะที่เป็น serialize object และหากเราต้องการนำกลับมาให้อยู่ในรูปแบบเดิมก็ต้องใช้ function “unserialize” นั่นเอง
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php // Simple class definition class User { // Class data public$age = 0; public$name = ''; // Print data public function PrintData() { echo'User ' . $this->name . ' is ' . $this->age . ' years old. <br />'; } } // Create a user $usr = newUser(); // Set user data $usr->age = 20; $usr->name = 'John'; // Print data $usr->PrintData(); // Serialize object and print output echo serialize($usr); ?> |
ผลลัพธ์คือ
จะเห็นว่าจาก object ถูกแปลเปลี่ยนกลายเป็น O:4:”User”:2:{s:3:”age”;i:20;s:4:”name”;s:4:”John”;}
ทีนี้หากต้องการจะเอา serialize object กลับมาใช้จะเขียนเป็น
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php // Simple class definition class User { // Class data public $age = 0; public $name = ''; // Print data public function PrintData() { echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />'; } } // Create a user $usr = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}'); // Print data $usr->PrintData(); ?> |
ผลลัพธ์คือ
*** Magic number ของ Serialize object คือ “ac ed” (hex) ครับ จากนั้นจะต่อด้วย Serialization protocol version “00 05” แล้วตามด้วยประเภทของ object ใน serialize object
Serialization magic functions
ทีนี้อย่างที่เราทราบกันว่า constructor (__construct) และ destructor (__destruct) จะถูกเรียกใช้งานเมื่อ object ถูก created และ destroyed, และยังมี magic functions อีกมากมายที่จะถูกเรียกใช้เมื่อมีการถูกทำ serialized และ unserialized:
- __sleep magic method ถูกใช้เมื่อ object ถูกทำ serialized โดย __sleep จะ return กลับด้วย array ของชื่อตัวแปลที่ถูก serialize
- __wakeup magic method ถูกใช้เมื่อทำ deserialized (หรือก็คือเรียกใช้ function unserialize)
ทดสอบใช้งาน magic function เมื่อทำ serialization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?php class Test { public $variable = 'BUZZ'; public $variable2 = 'OTHER'; public function PrintVariable() { echo $this->variable . '<br />'; echo "\r\n"; } public function __construct() { echo'__construct<br />'; } public function __destruct() { echo'__destruct<br />'; } public function __wakeup() { echo'__wakeup<br />'; } public function __sleep() { echo'__sleep<br />'; return array('variable', 'variable2'); } } // Create an object, will call __construct $obj = new Test(); // Serialize object, will call __sleep $serialized = serialize($obj); echo "\r\n"; // Print serialized string print "Serialized: " . $serialized. "<br/>"; echo "\r\n"; // Unserialize string, will call __wakeup $obj2 = unserialize($serialized); // Call PintVariable, will print data (BUZZ) $obj2->PrintVariable(); echo "\r\n"; // PHP script ends, will call __destruct for both objects($obj and $obj2) ?> |
ผลลัพธ์คือ
PHP Object Injection
หลังที่เราเข้าใจ serialization และ unserialization แล้ว ทีนี้เรามาดูตัวอย่างการ exploit กันบ้าง โดยคราวนี้จะยกตัวอย่าง php code ที่จะเก็บ log ไว้ในไฟล์ชั่วคราว นั่นหมายความว่าตอนทำ object จะเรียก __wakeup function ขึ้นมา แล้วจากนั้นจะทำการสร้างไฟล์ด้วย function ดังกล่าว และเมื่อทำการ destroy object ก็จะเรียกใช้งาน __destruct เพื่อลบไฟล์ที่เคยเป็นไฟล์ชั่วคราวมาก่อนนั่นเอง
(test04-1.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php class LogFile { // Specify log filename public $filename = 'error.log'; // Some code public function LogData($text) { echo 'Log some data: ' . $text; echo "\r\n"; file_put_contents($this->filename, $text, FILE_APPEND); } // Destructor that deletes the log file public function __destruct() { echo '__destruct deletes "'. $this->filename . '" file'; echo "\r\n"; unlink(dirname(__FILE__) . '/' . $this->filename); } } ?> |
เรียกใช้งาน test04-1.php (test04-2.php)
1 2 3 4 5 6 7 8 9 |
<?php include 'test04-1.php'; // Create an object $obj = new LogFile(); // Set filename and log data $obj->filename = 'somefile.log'; $obj->LogData('Test'); // Destructor will be called and 'somefile.log' will be deleted ?> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php if (isset($argv[1])) { $_GET['usr_serialized'] = $argv[1]; } include 'test04-1.php'; // ... Some other code that uses LogFile class ... // Simple class definition class User { // Class data public $age = 0; public $name = ''; // Print data public function PrintData() { echo 'User ' . $this->name . ' is ' . $this->age . ' years old.'; echo "\r\n"; } } // Unserialize user supplied data $usr = unserialize($_GET['usr_serialized']); ?> |
แต่ถ้าหากเราเปลี่ยน object ที่ส่งไปให้ไปเป็น LogFile Object ล่ะจะเกิดอะไรขึ้น โดยเราจะเริ่มจากการสร้าง serialize object ที่ชื่อว่า LogFile เสียก่อน โดยการเขียน php เป็น
1 2 3 4 5 6 |
<?php include "test04-1.php"; $obj = new LogFile(); $obj->filename = '.htaccess'; echo serialize($obj) . "\r\n"; ?> |
ผลลัพธ์ที่ได้คือ
1 2 |
O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess";} __destruct deletes ".htaccess" file. |
ทีนี้เราย้อนกลับไป input test04-3.php ให้เป็น O:7:”LogFile”:1:{s:8:”filename”;s:9:”.htaccess”;}
1 |
php test04-3.php 'O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess";}' |
จะพบว่า file .htaccess ถูกลบทิ้ง เพราะเนื่องด้วยตอนที่ __destruct function ถูกเรียก(เมื่อ object ถูกทำลาย) เพราะเรากำหนดค่าของ filename ผ่าน serialize object ที่เราสร้างขึ้นมานั่นเอง
injection points ที่มักพบได้
อย่างที่เห็นเราจะทำการ inject object ใดๆได้ จำเป็นต้องทราบของ magic function เหล่านั้นเสียก่อนว่า class นั้นๆกำหนดไว้อย่างไร ซึ่งมันมีความเป็นไปได้ที่จะ inject ได้ในหลายๆ magic function
วิธีการแก้ไข
เปลี่ยนจากใช้ “unserialize” function กับข้อมูลที่ user input เข้ามา ไปใช้ “json_decode”แทน
ตัวอย่าง Application ที่มีช่องโหว่ PHP Object Serialization
Source:: SecurityCafe