在昨天了解到了基本的序列化行為後
我們來看看 PHP 中的一些魔術方法
這些方法的出現是為了處理一些關於物件生成與銷毀時或是其他特定行為的函數
一共有這麼多種
今天會舉幾個常用且可能被作為攻擊途徑的危險方法
__construct() | __set() | __toString() |
__destruct() | __isset() | __invoke() |
__call() | __unset() | __set_state() |
__callStatic() | __sleep() | __clone() |
__get() | __wakeup() | __debugInfo() |
例如一個物件被 new 起來
<?php
class Test
{
public function __construct()
{
echo "hello Test";
}
};
$obj = new Test();
?>
<?php
class Test
{
public function __destruct()
{
echo "bye Test";
}
};
$obj = new Test();
?>
serialize()
時觸發serialize()
知道要將那些變數序列化出來從這個例子可以看到
我們 return 的陣列中只有 a
這個變數名稱
因此序列化出來的字串也只包含 a
這樣的功能對於一個龐大物件來說很方便
不需要將所有變數都輸出保存
可以選擇我們自己需要的就好
<?php
class Test
{
public $a = '123';
public $b = '456';
public function __sleep()
{
return array('a');
}
};
echo serialize(new Test());
?>
unserialize()
時觸發<?php
class Test
{
public $a = '123';
public $b = '456';
public function __wakeup()
{
echo 'wakup';
}
};
unserialize('O:4:"Test":1:{s:1:"a";s:3:"123";}');
?>
<?php
class Test
{
public function __toString()
{
return 'I am not a string';
}
};
$obj = new Test();
echo $obj;
?>
<?php
class Test
{
public function __invoke($input)
{
echo $input;
}
};
$obj = new Test();
$obj(123);
?>
在 PHP 的反序列化中有個經典的漏洞 CVE-2016-7124
可利用的版本為
該漏洞的形成是因為 unserialize()
在將字串還原成物件時
如果字串中對物件描述的變數數量不相符時
導致反序列化失敗而跳過執行 __wakeup()
<?php
include("flag.php");
class Flag{
public $key = "deadbeef";
function __destruct(){
global $flag;
echo $flag;
}
function __wakeup(){
if($this->key !== $serect){
global $flag;
$flag = "You can not read the flag.";
}
}
}
show_source(__FILE__);
$your_flag = unserialize($_GET["input"]);
?>
在這個例子中我們可以看到
當物件被還原時就一定會將 flag 覆蓋掉
因此我們需要利用上述所說的漏洞跳過 __wakeup()
的執行而取得真正的 flag
O:4:"Flag":2:{s:3:"key";s:8:"deadbeef";}
原本物件中變數只有一個
我們將其修改成 2
造成解析錯誤
就可以觸發該漏洞
在真實世界的 PHP 中的確也存在這類型的漏洞
只不過通常程式碼的量都非常龐大、物件非常多
因此要找到可攻擊的一條反序列化鍊並不像上面所看到的只有短短幾行
應該說任何現實世界的漏洞都是如此
都需要大量的耐心與毅力去找出可行的目標 XD