From 49e8789cfd8cc39673ffaa8e4f449588edd2906a Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 12 May 2021 22:35:03 +0000 Subject: [PATCH] External libraries: Improve attachment handling in PHPMailer Props: audrasjb, ayeshrajans, desrosj, peterwilsoncc, xknown. Partially merges [50799] to the 4.2 branch. Built from https://develop.svn.wordpress.org/branches/4.2@50862 git-svn-id: http://core.svn.wordpress.org/branches/4.2@50471 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-phpmailer.php | 54 ++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/wp-includes/class-phpmailer.php b/wp-includes/class-phpmailer.php index 0e48d7269f..4e05a4340b 100644 --- a/wp-includes/class-phpmailer.php +++ b/wp-includes/class-phpmailer.php @@ -1294,9 +1294,12 @@ class PHPMailer // Sign with DKIM if enabled if (!empty($this->DKIM_domain) - && !empty($this->DKIM_selector) - && (!empty($this->DKIM_private_string) - || (!empty($this->DKIM_private) && file_exists($this->DKIM_private)) + and !empty($this->DKIM_selector) + and (!empty($this->DKIM_private_string) + or (!empty($this->DKIM_private) + and self::isPermittedPath($this->DKIM_private) + and file_exists($this->DKIM_private) + ) ) ) { $header_dkim = $this->DKIM_Add( @@ -1461,6 +1464,39 @@ class PHPMailer return true; } + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * @param string $path A relative or absolute path to a file. + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!self::isPermittedPath($path)) { + return false; + } + $readable = file_exists($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + /** * Send mail using the PHP mail() function. * @param string $header The message headers @@ -1517,7 +1553,7 @@ class PHPMailer public function getSMTPInstance() { if (!is_object($this->smtp)) { - require_once( 'class-smtp.php' ); + require_once( 'class-smtp.php' ); $this->smtp = new SMTP; } return $this->smtp; @@ -1784,7 +1820,7 @@ class PHPMailer // There is no English translation file if ($langcode != 'en') { // Make sure language file path is readable - if (!is_readable($lang_file)) { + if (!self::fileIsAccessible($lang_file)) { $foundlang = false; } else { // Overwrite language-specific strings. @@ -2495,6 +2531,8 @@ class PHPMailer * Add an attachment from a path on the filesystem. * Never use a user-supplied path to a file! * Returns false if the file could not be found or read. + * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client. + * If you need to do that, fetch the resource yourself and pass it in via a local file or string. * @param string $path Path to the attachment. * @param string $name Overrides the attachment name. * @param string $encoding File encoding (see $Encoding). @@ -2506,7 +2544,7 @@ class PHPMailer public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') { try { - if (!@is_file($path)) { + if (!self::fileIsAccessible($path)) { throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); } @@ -2687,7 +2725,7 @@ class PHPMailer protected function encodeFile($path, $encoding = 'base64') { try { - if (!is_readable($path)) { + if (!self::fileIsAccessible($path)) { throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); } $magic_quotes = get_magic_quotes_runtime(); @@ -3031,7 +3069,7 @@ class PHPMailer */ public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') { - if (!@is_file($path)) { + if (!self::fileIsAccessible($path)) { $this->setError($this->lang('file_access') . $path); return false; }