From 8107a91852407b37ca832887d4ea8801b3a8af86 Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Thu, 10 Jun 2021 22:31:30 +0200 Subject: [PATCH] WIP --- SECURITY.md | 5 +++++ VERSION | 2 +- changelog.md | 4 ++++ language/phpmailer.lang-ar.php | 3 +-- src/PHPMailer.php | 28 +++++++++++++++++++----- src/POP3.php | 2 +- src/SMTP.php | 2 +- test/PHPMailerLangTest.php | 39 +++++++++++++++++++++++++--------- 8 files changed, 65 insertions(+), 20 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 5a6a8f41..ad2d72f0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,6 +2,11 @@ Please disclose any security issues or vulnerabilities found through [Tidelift's coordinated disclosure system](https://tidelift.com/security) or to the maintainers privately. +PHPMailer 6.4.1 contains a possible remote code execution vulnerability through the `$lang_path` parameter of the `setLanguage()` method. If the `$lang_path` parameter is passed unfiltered from user input, it can be set to [a UNC path](https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths), and if an attacker is also able to create a remote mount on the server that the UNC path points to, a script file under their control may be executed. This vulnerability only applies to systems that resolve UNC paths, typically only Microsoft Windows. Recorded as [CVE-2021-34551](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-34551). Reported by [listensec.com](https://listensec.com) via Tidelift. + +PHPMailer 6.5.0 mitigates this by no longer treating translation files as PHP code, but by parsing their text content directly. +This approach avoids the possibility of executing unknown code while retaining backward compatibility. This isn't ideal, so the current translation format is deprecated and will be replaced in the next major release. + PHPMailer versions between 6.1.8 and 6.4.0 contain a regression of the earlier CVE-2018-19296 object injection vulnerability as a result of [a fix for Windows UNC paths in 6.1.8](https://github.com/PHPMailer/PHPMailer/commit/e2e07a355ee8ff36aba21d0242c5950c56e4c6f9). Recorded as [CVE-2020-36326](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-36326). Reported by Fariskhi Vidyan via Tidelift. 6.4.1 fixes this issue, and also enforces stricter checks for URL schemes in local path contexts. PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security. diff --git a/VERSION b/VERSION index 306894a1..4be2c727 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.4.1 \ No newline at end of file +6.5.0 \ No newline at end of file diff --git a/changelog.md b/changelog.md index 01954d19..8e8a0e35 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ # PHPMailer Change Log +## Version 6.5.0 (June 11th, 2021) +* **SECURITY** Fixes CVE-2021-34551, a complex RCE affecting Windows hosts. +* *Deprecation* The translation file format currently used (PHP arrays) is now deprecated; the next major version will introduce a new format. + ## Version 6.4.1 (April 29th, 2021) * **SECURITY** Fixes CVE-2020-36326, a regression of CVE-2018-19296 object injection introduced in 6.1.8, see SECURITY.md for details * Reject more file paths that look like URLs, matching RFC3986 spec, blocking URLS using schemes such as `ssh2` diff --git a/language/phpmailer.lang-ar.php b/language/phpmailer.lang-ar.php index 8ab485c4..f795580a 100644 --- a/language/phpmailer.lang-ar.php +++ b/language/phpmailer.lang-ar.php @@ -19,8 +19,7 @@ $PHPMAILER_LANG['instantiate'] = 'لا يمكن توفير خدمة ا $PHPMAILER_LANG['invalid_address'] = 'الإرسال غير ممكن لأن عنوان البريد الإلكتروني غير صالح: '; $PHPMAILER_LANG['mailer_not_supported'] = ' برنامج الإرسال غير مدعوم.'; $PHPMAILER_LANG['provide_address'] = 'يجب توفير عنوان البريد الإلكتروني لمستلم واحد على الأقل.'; -$PHPMAILER_LANG['recipients_failed'] = 'خطأ SMTP: الأخطاء التالية ' . - 'فشل في الارسال لكل من : '; +$PHPMAILER_LANG['recipients_failed'] = 'خطأ SMTP: الأخطاء التالية فشل في الارسال لكل من : '; $PHPMAILER_LANG['signing'] = 'خطأ في التوقيع: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() غير ممكن.'; $PHPMAILER_LANG['smtp_error'] = 'خطأ على مستوى الخادم SMTP: '; diff --git a/src/PHPMailer.php b/src/PHPMailer.php index f68d2577..ed23b69c 100644 --- a/src/PHPMailer.php +++ b/src/PHPMailer.php @@ -750,7 +750,7 @@ class PHPMailer * * @var string */ - const VERSION = '6.4.1'; + const VERSION = '6.5.0'; /** * Error severity: message only, continue processing. @@ -2246,14 +2246,32 @@ class PHPMailer if (!static::fileIsAccessible($lang_file)) { $foundlang = false; } else { - //Overwrite language-specific strings. - //This way we'll never have missing translation keys. - $foundlang = include $lang_file; + //$foundlang = include $lang_file; + $lines = file($lang_file); + foreach ($lines as $line) { + //Translation file lines look like this: + //$PHPMAILER_LANG['authenticate'] = 'SMTP-Fehler: Authentifizierung fehlgeschlagen.'; + //These files are parsed as text and not PHP so as to avoid the possibility of code injection + //See https://blog.stevenlevithan.com/archives/match-quoted-string + $matches = []; + if ( + preg_match( + '/^\$PHPMAILER_LANG\[\'([a-z\d_]+)\'\]\s*=\s*(["\'])(.+)*?\2;/', + $line, + $matches + ) && + //Ignore unknown translation keys + array_key_exists($matches[1], $PHPMAILER_LANG) + ) { + //Overwrite language-specific strings so we'll never have missing translation keys. + $PHPMAILER_LANG[$matches[1]] = (string)$matches[3]; + } + } } } $this->language = $PHPMAILER_LANG; - return (bool) $foundlang; //Returns false if language not found + return $foundlang; //Returns false if language not found } /** diff --git a/src/POP3.php b/src/POP3.php index a39f4803..b38964b1 100644 --- a/src/POP3.php +++ b/src/POP3.php @@ -46,7 +46,7 @@ class POP3 * * @var string */ - const VERSION = '6.4.1'; + const VERSION = '6.5.0'; /** * Default POP3 port number. diff --git a/src/SMTP.php b/src/SMTP.php index 0e7f53df..2913403b 100644 --- a/src/SMTP.php +++ b/src/SMTP.php @@ -35,7 +35,7 @@ class SMTP * * @var string */ - const VERSION = '6.4.1'; + const VERSION = '6.5.0'; /** * SMTP line break constant. diff --git a/test/PHPMailerLangTest.php b/test/PHPMailerLangTest.php index 698483ea..3cc08a43 100644 --- a/test/PHPMailerLangTest.php +++ b/test/PHPMailerLangTest.php @@ -39,7 +39,7 @@ final class PHPMailerLangTest extends TestCase /** * Test language files for missing and excess translations. - * All languages are compared with English. + * All languages are compared with English, which is built-in. * * @group languages */ @@ -57,17 +57,36 @@ final class PHPMailerLangTest extends TestCase if (preg_match('/^phpmailer\.lang-([a-z_]{2,})\.php$/', $fileInfo->getFilename(), $matches)) { $lang = $matches[1]; //Extract language code $PHPMAILER_LANG = []; //Language strings get put in here - include $fileInfo->getPathname(); //Get language strings - $missing = array_diff(array_keys($definedStrings), array_keys($PHPMAILER_LANG)); - $extra = array_diff(array_keys($PHPMAILER_LANG), array_keys($definedStrings)); - if (!empty($missing)) { - $err .= "\nMissing translations in $lang: " . implode(', ', $missing); - } - if (!empty($extra)) { - $err .= "\nExtra translations in $lang: " . implode(', ', $extra); + $lines = file($fileInfo->getPathname()); + foreach ($lines as $line) { + //Translation file lines look like this: + //$PHPMAILER_LANG['authenticate'] = 'SMTP-Fehler: Authentifizierung fehlgeschlagen.'; + //These files are parsed as text and not PHP so as to avoid the possibility of code injection + $matches = []; + if ( + preg_match( + '/^\$PHPMAILER_LANG\[\'([a-z\d_]+)\'\]\s*=\s*(["\'])(.+)*?\2;/', + $line, + $matches + ) + ) { + //Overwrite language-specific strings so we'll never have missing translation keys. + $PHPMAILER_LANG[$matches[1]] = (string)$matches[3]; + } } } + + include $fileInfo->getPathname(); //Get language strings + $missing = array_diff(array_keys($definedStrings), array_keys($PHPMAILER_LANG)); + $extra = array_diff(array_keys($PHPMAILER_LANG), array_keys($definedStrings)); + if (!empty($missing)) { + $err .= "\nMissing translations in $lang: " . implode(', ', $missing); + } + if (!empty($extra)) { + $err .= "\nExtra translations in $lang: " . implode(', ', $extra); + } } - $this->assertEmpty($err, $err); + //If we have no extra and no missing translations, $err will be empty + self::assertEmpty($err, $err); } }