Skip to content

Commit 6687a96

Browse files
committed
Add test for line breaks in addresses vulnerability
Don't allow line breaks in addresses Don't allow line breaks in SMTP commands Rearrange tests so slowest tests run last
1 parent 1cd906b commit 6687a96

File tree

4 files changed

+91
-74
lines changed

4 files changed

+91
-74
lines changed

Diff for: changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Allow addresses with IDN (Internationalized Domain Name) in PHP 5.3+, thanks to @fbonzon
44
* Allow access to POP3 errors
55
* Make all POP3 private properties and methods protected
6+
* **SECURITY** Fix vulnerability that allowed email addresses with line breaks (valid in RFC5322) to pass to SMTP, permitting message injection at the SMTP level. Mitigated in both the address validator and in the lower-level SMTP class. Thanks to Takeshi Terada.
7+
* Updated Brazilian Portuguese translations (Thanks to @phelipealves)
68

79
## Version 5.2.13 (Sep 14th 2015)
810
* Rename internal oauth class to avoid name clashes

Diff for: class.phpmailer.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -1028,10 +1028,10 @@ public function getLastMessageID()
10281028
* Check that a string looks like an email address.
10291029
* @param string $address The email address to check
10301030
* @param string $patternselect A selector for the validation pattern to use :
1031-
* * `auto` Pick strictest one automatically;
1031+
* * `auto` Pick best pattern automatically;
10321032
* * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
10331033
* * `pcre` Use old PCRE implementation;
1034-
* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; same as pcre8 but does not allow 'dotless' domains;
1034+
* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
10351035
* * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
10361036
* * `noregex` Don't use a regex: super fast, really dumb.
10371037
* @return boolean
@@ -1040,6 +1040,10 @@ public function getLastMessageID()
10401040
*/
10411041
public static function validateAddress($address, $patternselect = 'auto')
10421042
{
1043+
//Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1044+
if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
1045+
return false;
1046+
}
10431047
if (!$patternselect or $patternselect == 'auto') {
10441048
//Check this constant first so it works when extension_loaded() is disabled by safe mode
10451049
//Constant was added in PHP 5.2.4

Diff for: class.smtp.php

+8-3
Original file line numberDiff line numberDiff line change
@@ -814,15 +814,15 @@ public function quit($close_on_error = true)
814814
* Sets the TO argument to $toaddr.
815815
* Returns true if the recipient was accepted false if it was rejected.
816816
* Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
817-
* @param string $toaddr The address the message is being sent to
817+
* @param string $address The address the message is being sent to
818818
* @access public
819819
* @return boolean
820820
*/
821-
public function recipient($toaddr)
821+
public function recipient($address)
822822
{
823823
return $this->sendCommand(
824824
'RCPT TO',
825-
'RCPT TO:<' . $toaddr . '>',
825+
'RCPT TO:<' . $address . '>',
826826
array(250, 251)
827827
);
828828
}
@@ -853,6 +853,11 @@ protected function sendCommand($command, $commandstring, $expect)
853853
$this->setError("Called $command without being connected");
854854
return false;
855855
}
856+
//Reject line breaks in all commands
857+
if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
858+
$this->setError("Command '$command' contained line breaks");
859+
return false;
860+
}
856861
$this->client_send($commandstring . self::CRLF);
857862

858863
$this->last_reply = $this->get_lines();

Diff for: test/phpmailerTest.php

+75-69
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,6 @@ public function testValidate()
361361
'"Fred\ Bloggs"@iana.org',
362362
'"Joe.\Blow"@iana.org',
363363
'"Abc@def"@iana.org',
364-
'"Fred Bloggs"@iana.org',
365364
'user+mailbox@iana.org',
366365
'customer/department=shipping@iana.org',
367366
'$A12345@iana.org',
@@ -467,7 +466,7 @@ public function testValidate()
467466
'first.last@[IPv6:a1::b2:11.22.33.44]',
468467
'test@test.com',
469468
'test@xn--example.com',
470-
'test@example.com',
469+
'test@example.com'
471470
);
472471
$invalidaddresses = array(
473472
'first.last@sub.do,com',
@@ -608,6 +607,7 @@ public function testValidate()
608607
'first.last@[IPv6:a1:a2:a3:a4:b1:b2:b3:]',
609608
'first.last@[IPv6::a2:a3:a4:b1:b2:b3:b4]',
610609
'first.last@[IPv6:a1:a2:a3:a4::b1:b2:b3:b4]',
610+
"(\r\n RCPT TO:websec02@d.mbsd.jp\r\n DATA \\\nSubject: spam10\\\n\r\n Hello,\r\n this is a spam mail.\\\n.\r\n QUIT\r\n ) a@gmail.com" //This is valid RCC5322, but we don't want to allow it
611611
);
612612
// IDNs in Unicode and ASCII forms.
613613
$unicodeaddresses = array(
@@ -1825,73 +1825,11 @@ public function testMiscellaneous()
18251825
$this->assertEquals($q['extension'], 'mp3', 'Windows extension not matched');
18261826
$this->assertEquals($q['filename'], '飛兒樂 團光茫', 'Windows filename not matched');
18271827
}
1828-
1829-
/**
1830-
* Use a fake POP3 server to test POP-before-SMTP auth.
1831-
* With a known-good login
1832-
*/
1833-
public function testPopBeforeSmtpGood()
1834-
{
1835-
//Start a fake POP server
1836-
$pid = shell_exec('nohup ./runfakepopserver.sh >/dev/null 2>/dev/null & printf "%u" $!');
1837-
$this->pids[] = $pid;
1838-
1839-
sleep(2);
1840-
//Test a known-good login
1841-
$this->assertTrue(
1842-
POP3::popBeforeSmtp('localhost', 1100, 10, 'user', 'test', $this->Mail->SMTPDebug),
1843-
'POP before SMTP failed'
1844-
);
1845-
//Kill the fake server
1846-
shell_exec('kill -TERM '.escapeshellarg($pid));
1847-
sleep(2);
1848-
}
1849-
1850-
/**
1851-
* Use a fake POP3 server to test POP-before-SMTP auth.
1852-
* With a known-bad login
1853-
*/
1854-
public function testPopBeforeSmtpBad()
1855-
{
1856-
//Start a fake POP server on a different port
1857-
//so we don't inadvertently connect to the previous instance
1858-
$pid = shell_exec('nohup ./runfakepopserver.sh 1101 >/dev/null 2>/dev/null & printf "%u" $!');
1859-
$this->pids[] = $pid;
1860-
1861-
sleep(2);
1862-
//Test a known-bad login
1863-
$this->assertFalse(
1864-
POP3::popBeforeSmtp('localhost', 1101, 10, 'user', 'xxx', $this->Mail->SMTPDebug),
1865-
'POP before SMTP should have failed'
1866-
);
1867-
shell_exec('kill -TERM '.escapeshellarg($pid));
1868-
sleep(2);
1869-
}
1870-
1871-
/**
1872-
* Test SMTP host connections.
1873-
* This test can take a long time, so run it last
1874-
*/
1875-
public function testSmtpConnect()
1828+
public function testBadSMTP()
18761829
{
1877-
$this->Mail->SMTPDebug = 4; //Show connection-level errors
1878-
$this->assertTrue($this->Mail->smtpConnect(), 'SMTP single connect failed');
1879-
$this->Mail->smtpClose();
1880-
$this->Mail->Host = "ssl://localhost:12345;tls://localhost:587;10.10.10.10:54321;localhost:12345;10.10.10.10";
1881-
$this->assertFalse($this->Mail->smtpConnect(), 'SMTP bad multi-connect succeeded');
1882-
$this->Mail->smtpClose();
1883-
$this->Mail->Host = "localhost:12345;10.10.10.10:54321;" . $_REQUEST['mail_host'];
1884-
$this->assertTrue($this->Mail->smtpConnect(), 'SMTP multi-connect failed');
1885-
$this->Mail->smtpClose();
1886-
$this->Mail->Host = " localhost:12345 ; " . $_REQUEST['mail_host'] . ' ';
1887-
$this->assertTrue($this->Mail->smtpConnect(), 'SMTP hosts with stray spaces failed');
1888-
$this->Mail->smtpClose();
1889-
$this->Mail->Host = $_REQUEST['mail_host'];
1890-
//Need to pick a harmless option so as not cause problems of its own! socket:bind doesn't work with Travis-CI
1891-
$this->assertTrue(
1892-
$this->Mail->smtpConnect(array('ssl' => array('verify_depth' => 10))),
1893-
'SMTP connect with options failed'
1894-
);
1830+
$this->Mail->smtpConnect();
1831+
$smtp = $this->Mail->getSMTPInstance();
1832+
$this->assertFalse($smtp->mail("somewhere\nbad"), 'Bad SMTP command containing breaks accepted');
18951833
}
18961834

18971835
/**
@@ -2042,11 +1980,79 @@ public function testDuplicateIDNRemoved()
20421980
count($this->Mail->getReplyToAddresses()),
20431981
'Bad count of "reply-to" addresses');
20441982
}
1983+
1984+
/**
1985+
* Use a fake POP3 server to test POP-before-SMTP auth.
1986+
* With a known-good login
1987+
*/
1988+
public function testPopBeforeSmtpGood()
1989+
{
1990+
//Start a fake POP server
1991+
$pid = shell_exec('nohup ./runfakepopserver.sh >/dev/null 2>/dev/null & printf "%u" $!');
1992+
$this->pids[] = $pid;
1993+
1994+
sleep(2);
1995+
//Test a known-good login
1996+
$this->assertTrue(
1997+
POP3::popBeforeSmtp('localhost', 1100, 10, 'user', 'test', $this->Mail->SMTPDebug),
1998+
'POP before SMTP failed'
1999+
);
2000+
//Kill the fake server
2001+
shell_exec('kill -TERM ' . escapeshellarg($pid));
2002+
sleep(2);
2003+
}
2004+
2005+
/**
2006+
* Use a fake POP3 server to test POP-before-SMTP auth.
2007+
* With a known-bad login
2008+
*/
2009+
public function testPopBeforeSmtpBad()
2010+
{
2011+
//Start a fake POP server on a different port
2012+
//so we don't inadvertently connect to the previous instance
2013+
$pid = shell_exec('nohup ./runfakepopserver.sh 1101 >/dev/null 2>/dev/null & printf "%u" $!');
2014+
$this->pids[] = $pid;
2015+
2016+
sleep(2);
2017+
//Test a known-bad login
2018+
$this->assertFalse(
2019+
POP3::popBeforeSmtp('localhost', 1101, 10, 'user', 'xxx', $this->Mail->SMTPDebug),
2020+
'POP before SMTP should have failed'
2021+
);
2022+
shell_exec('kill -TERM ' . escapeshellarg($pid));
2023+
sleep(2);
2024+
}
2025+
2026+
/**
2027+
* Test SMTP host connections.
2028+
* This test can take a long time, so run it last
2029+
*/
2030+
public function testSmtpConnect()
2031+
{
2032+
$this->Mail->SMTPDebug = 4; //Show connection-level errors
2033+
$this->assertTrue($this->Mail->smtpConnect(), 'SMTP single connect failed');
2034+
$this->Mail->smtpClose();
2035+
$this->Mail->Host = "ssl://localhost:12345;tls://localhost:587;10.10.10.10:54321;localhost:12345;10.10.10.10";
2036+
$this->assertFalse($this->Mail->smtpConnect(), 'SMTP bad multi-connect succeeded');
2037+
$this->Mail->smtpClose();
2038+
$this->Mail->Host = "localhost:12345;10.10.10.10:54321;" . $_REQUEST['mail_host'];
2039+
$this->assertTrue($this->Mail->smtpConnect(), 'SMTP multi-connect failed');
2040+
$this->Mail->smtpClose();
2041+
$this->Mail->Host = " localhost:12345 ; " . $_REQUEST['mail_host'] . ' ';
2042+
$this->assertTrue($this->Mail->smtpConnect(), 'SMTP hosts with stray spaces failed');
2043+
$this->Mail->smtpClose();
2044+
$this->Mail->Host = $_REQUEST['mail_host'];
2045+
//Need to pick a harmless option so as not cause problems of its own! socket:bind doesn't work with Travis-CI
2046+
$this->assertTrue(
2047+
$this->Mail->smtpConnect(array('ssl' => array('verify_depth' => 10))),
2048+
'SMTP connect with options failed'
2049+
);
2050+
}
20452051
}
20462052

20472053
/**
20482054
* This is a sample form for setting appropriate test values through a browser
2049-
* These values can also be set using a file called testbootstrap.php (not in svn) in the same folder as this script
2055+
* These values can also be set using a file called testbootstrap.php (not in repo) in the same folder as this script
20502056
* which is probably more useful if you run these tests a lot
20512057
* <html>
20522058
* <body>

0 commit comments

Comments
 (0)