Updated PHPMailer due to RCE (CVE-2016-10033). The site shouldn't be affected due to the use of the custom smtp class rather than native sendmail (I think), but worth upgrading anyway.

Also modified form to use different to/from addresses.
This commit is contained in:
Tim Stallard 2016-12-30 00:51:08 +00:00
parent eaf4482dc0
commit 69b7a13553
4 changed files with 117 additions and 22 deletions

View File

@ -5,5 +5,6 @@ $mailconf = [
"password" => "", "password" => "",
"security" => "tls", "security" => "tls",
"port" => 587, "port" => 587,
"address" => "" "fromaddress" => "",
"toaddress" => ""
] ]

View File

@ -20,8 +20,8 @@ foreach($_POST as $item => $contents){
$message .= $item . ": " . $contents . "\n"; $message .= $item . ": " . $contents . "\n";
} }
$mail->setFrom($mailconf["address"], "Website Contact"); $mail->setFrom($mailconf["fromaddress"], "Website Contact Form");
$mail->addAddress($mailconf["address"], "Website Contact"); $mail->addAddress($mailconf["toaddress"], "Website Contact");
$mail->addReplyTo($_POST["contact"], $_POST["name"]); $mail->addReplyTo($_POST["contact"], $_POST["name"]);
$mail->Subject = "Website Contact Page Message"; $mail->Subject = "Website Contact Page Message";

View File

@ -31,7 +31,7 @@ class PHPMailer
* The PHPMailer Version number. * The PHPMailer Version number.
* @var string * @var string
*/ */
public $Version = '5.2.16'; public $Version = '5.2.21';
/** /**
* Email priority. * Email priority.
@ -423,6 +423,13 @@ class PHPMailer
*/ */
public $DKIM_private = ''; public $DKIM_private = '';
/**
* DKIM private key string.
* If set, takes precedence over `$DKIM_private`.
* @var string
*/
public $DKIM_private_string = '';
/** /**
* Callback Action function name. * Callback Action function name.
* *
@ -685,7 +692,7 @@ class PHPMailer
$subject = $this->encodeHeader($this->secureHeader($subject)); $subject = $this->encodeHeader($this->secureHeader($subject));
} }
//Can't use additional_parameters in safe_mode //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
//@link http://php.net/manual/en/function.mail.php //@link http://php.net/manual/en/function.mail.php
if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) { if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
$result = @mail($to, $subject, $body, $header); $result = @mail($to, $subject, $body, $header);
@ -1287,9 +1294,11 @@ class PHPMailer
// Sign with DKIM if enabled // Sign with DKIM if enabled
if (!empty($this->DKIM_domain) if (!empty($this->DKIM_domain)
&& !empty($this->DKIM_private)
&& !empty($this->DKIM_selector) && !empty($this->DKIM_selector)
&& file_exists($this->DKIM_private)) { && (!empty($this->DKIM_private_string)
|| (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
)
) {
$header_dkim = $this->DKIM_Add( $header_dkim = $this->DKIM_Add(
$this->MIMEHeader . $this->mailHeader, $this->MIMEHeader . $this->mailHeader,
$this->encodeHeader($this->secureHeader($this->Subject)), $this->encodeHeader($this->secureHeader($this->Subject)),
@ -1355,19 +1364,24 @@ class PHPMailer
*/ */
protected function sendmailSend($header, $body) protected function sendmailSend($header, $body)
{ {
if ($this->Sender != '') { // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
if ($this->Mailer == 'qmail') { if ($this->Mailer == 'qmail') {
$sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); $sendmailFmt = '%s -f%s';
} else { } else {
$sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); $sendmailFmt = '%s -oi -f%s -t';
} }
} else { } else {
if ($this->Mailer == 'qmail') { if ($this->Mailer == 'qmail') {
$sendmail = sprintf('%s', escapeshellcmd($this->Sendmail)); $sendmailFmt = '%s';
} else { } else {
$sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail)); $sendmailFmt = '%s -oi -t';
} }
} }
// TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing.
$sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
if ($this->SingleTo) { if ($this->SingleTo) {
foreach ($this->SingleToArray as $toAddr) { foreach ($this->SingleToArray as $toAddr) {
if (!@$mail = popen($sendmail, 'w')) { if (!@$mail = popen($sendmail, 'w')) {
@ -1413,6 +1427,40 @@ class PHPMailer
return true; return true;
} }
/**
* Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
*
* Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
* @param string $string The string to be validated
* @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
* @access protected
* @return boolean
*/
protected static function isShellSafe($string)
{
// Future-proof
if (escapeshellcmd($string) !== $string
or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
) {
return false;
}
$length = strlen($string);
for ($i = 0; $i < $length; $i++) {
$c = $string[$i];
// All other characters have a special meaning in at least one common shell, including = and +.
// Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
// Note that this does permit non-Latin alphanumeric characters based on the current locale.
if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
return false;
}
}
return true;
}
/** /**
* Send mail using the PHP mail() function. * Send mail using the PHP mail() function.
* @param string $header The message headers * @param string $header The message headers
@ -1432,10 +1480,13 @@ class PHPMailer
$params = null; $params = null;
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
if (!empty($this->Sender)) { if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
if (self::isShellSafe($this->Sender)) {
$params = sprintf('-f%s', $this->Sender); $params = sprintf('-f%s', $this->Sender);
} }
if ($this->Sender != '' and !ini_get('safe_mode')) { }
if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
$old_from = ini_get('sendmail_from'); $old_from = ini_get('sendmail_from');
ini_set('sendmail_from', $this->Sender); ini_set('sendmail_from', $this->Sender);
} }
@ -1489,10 +1540,10 @@ class PHPMailer
if (!$this->smtpConnect($this->SMTPOptions)) { if (!$this->smtpConnect($this->SMTPOptions)) {
throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
} }
if ('' == $this->Sender) { if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
$smtp_from = $this->From;
} else {
$smtp_from = $this->Sender; $smtp_from = $this->Sender;
} else {
$smtp_from = $this->From;
} }
if (!$this->smtp->mail($smtp_from)) { if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
@ -2127,6 +2178,14 @@ class PHPMailer
return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
} }
/**
* Create unique ID
* @return string
*/
protected function generateId() {
return md5(uniqid(time()));
}
/** /**
* Assemble the message body. * Assemble the message body.
* Returns an empty string on failure. * Returns an empty string on failure.
@ -2138,7 +2197,7 @@ class PHPMailer
{ {
$body = ''; $body = '';
//Create unique IDs and preset boundaries //Create unique IDs and preset boundaries
$this->uniqueid = md5(uniqid(time())); $this->uniqueid = $this->generateId();
$this->boundary[1] = 'b1_' . $this->uniqueid; $this->boundary[1] = 'b1_' . $this->uniqueid;
$this->boundary[2] = 'b2_' . $this->uniqueid; $this->boundary[2] = 'b2_' . $this->uniqueid;
$this->boundary[3] = 'b3_' . $this->uniqueid; $this->boundary[3] = 'b3_' . $this->uniqueid;
@ -3709,7 +3768,7 @@ class PHPMailer
} }
return ''; return '';
} }
$privKeyStr = file_get_contents($this->DKIM_private); $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
if ('' != $this->DKIM_passphrase) { if ('' != $this->DKIM_passphrase) {
$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
} else { } else {

View File

@ -30,7 +30,7 @@ class SMTP
* The PHPMailer SMTP version number. * The PHPMailer SMTP version number.
* @var string * @var string
*/ */
const VERSION = '5.2.16'; const VERSION = '5.2.21';
/** /**
* SMTP line break constant. * SMTP line break constant.
@ -81,7 +81,7 @@ class SMTP
* @deprecated Use the `VERSION` constant instead * @deprecated Use the `VERSION` constant instead
* @see SMTP::VERSION * @see SMTP::VERSION
*/ */
public $Version = '5.2.16'; public $Version = '5.2.21';
/** /**
* SMTP server port number. * SMTP server port number.
@ -150,6 +150,17 @@ class SMTP
*/ */
public $Timelimit = 300; public $Timelimit = 300;
/**
* @var array patterns to extract smtp transaction id from smtp reply
* Only first capture group will be use, use non-capturing group to deal with it
* Extend this class to override this property to fulfil your needs.
*/
protected $smtp_transaction_id_patterns = array(
'exim' => '/[0-9]{3} OK id=(.*)/',
'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
);
/** /**
* The socket for the server connection. * The socket for the server connection.
* @var resource * @var resource
@ -1211,4 +1222,28 @@ class SMTP
self::DEBUG_CONNECTION self::DEBUG_CONNECTION
); );
} }
/**
* Will return the ID of the last smtp transaction based on a list of patterns provided
* in SMTP::$smtp_transaction_id_patterns.
* If no reply has been received yet, it will return null.
* If no pattern has been matched, it will return false.
* @return bool|null|string
*/
public function getLastTransactionID()
{
$reply = $this->getLastReply();
if (empty($reply)) {
return null;
}
foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
return $matches[1];
}
}
return false;
}
} }