Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Commit 9e03a3a

Browse files
committed
Merge branch 'security/zf2015-04'
ZF2015-04 patch
2 parents 712b7ec + 5dbff43 commit 9e03a3a

21 files changed

+1083
-68
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ Released on MMMMMMMM DD, YYYY.
1313
IMPORTANT FIXES FOR 1.12.12
1414
---------------------------
1515

16+
**This release contains security updates:**
17+
18+
- **ZF2015-04:** `Zend_Mail` and `Zend_Http` were both susceptible to CRLF Injection
19+
Attack vectors (for HTTP, this is often referred to as HTTP Response
20+
Splitting). Both components were updated to perform header value validations
21+
to ensure no values contain characters not detailed in their corresponding
22+
specifications, and will raise exceptions on detection. Each also provides new
23+
facilities for both validating and filtering header values prior to injecting
24+
them into header classes. If you use either `Zend_Mail` or `Zend_Http`,
25+
we recommend upgrading immediately.
26+
1627
See http://framework.zend.com/changelog for full details.
1728

1829
NEW FEATURES

library/Zend/Http/Client.php

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
require_once 'Zend/Http/Client/Adapter/Interface.php';
4040

4141

42+
/**
43+
* @see Zend_Http_Header_HeaderValue
44+
*/
45+
require_once 'Zend/Http/Header/HeaderValue.php';
46+
47+
4248
/**
4349
* @see Zend_Http_Response
4450
*/
@@ -431,38 +437,40 @@ public function setHeaders($name, $value = null)
431437
foreach ($name as $k => $v) {
432438
if (is_string($k)) {
433439
$this->setHeaders($k, $v);
434-
} else {
435-
$this->setHeaders($v, null);
440+
continue;
436441
}
442+
$this->setHeaders($v, null);
437443
}
438-
} else {
439-
// Check if $name needs to be split
440-
if ($value === null && (strpos($name, ':') > 0)) {
441-
list($name, $value) = explode(':', $name, 2);
442-
}
444+
return $this;
445+
}
443446

444-
// Make sure the name is valid if we are in strict mode
445-
if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) {
446-
/** @see Zend_Http_Client_Exception */
447-
require_once 'Zend/Http/Client/Exception.php';
448-
throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name");
449-
}
447+
// Check if $name needs to be split
448+
if ($value === null && (strpos($name, ':') > 0)) {
449+
list($name, $value) = explode(':', $name, 2);
450+
}
450451

451-
$normalized_name = strtolower($name);
452+
// Make sure the name is valid if we are in strict mode
453+
if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) {
454+
require_once 'Zend/Http/Client/Exception.php';
455+
throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name");
456+
}
452457

453-
// If $value is null or false, unset the header
454-
if ($value === null || $value === false) {
455-
unset($this->headers[$normalized_name]);
458+
$normalized_name = strtolower($name);
456459

457-
// Else, set the header
458-
} else {
459-
// Header names are stored lowercase internally.
460-
if (is_string($value)) {
461-
$value = trim($value);
462-
}
463-
$this->headers[$normalized_name] = array($name, $value);
464-
}
460+
// If $value is null or false, unset the header
461+
if ($value === null || $value === false) {
462+
unset($this->headers[$normalized_name]);
463+
return $this;
464+
}
465+
466+
// Validate value
467+
$this->_validateHeaderValue($value);
468+
469+
// Header names are stored lowercase internally.
470+
if (is_string($value)) {
471+
$value = trim($value);
465472
}
473+
$this->headers[$normalized_name] = array($name, $value);
466474

467475
return $this;
468476
}
@@ -1568,4 +1576,27 @@ protected static function _flattenParametersArray($parray, $prefix = null)
15681576
return $parameters;
15691577
}
15701578

1579+
/**
1580+
* Ensure a header value is valid per RFC 7230.
1581+
*
1582+
* @see http://tools.ietf.org/html/rfc7230#section-3.2
1583+
* @param string|object|array $value
1584+
* @param bool $recurse
1585+
*/
1586+
protected function _validateHeaderValue($value, $recurse = true)
1587+
{
1588+
if (is_array($value) && $recurse) {
1589+
foreach ($value as $v) {
1590+
$this->_validateHeaderValue($v, false);
1591+
}
1592+
return;
1593+
}
1594+
1595+
if (! is_string($value) && (! is_object($value) || ! method_exists($value, '__toString'))) {
1596+
require_once 'Zend/Http/Exception.php';
1597+
throw new Zend_Http_Exception('Invalid header value detected');
1598+
}
1599+
1600+
Zend_Http_Header_HeaderValue::assertValid($value);
1601+
}
15711602
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
/**
3+
* Zend Framework
4+
*
5+
* LICENSE
6+
*
7+
* This source file is subject to the new BSD license that is bundled
8+
* with this package in the file LICENSE.txt.
9+
* It is also available through the world-wide-web at this URL:
10+
* http://framework.zend.com/license/new-bsd
11+
* If you did not receive a copy of the license and are unable to
12+
* obtain it through the world-wide-web, please send an email
13+
* to [email protected] so we can send you a copy immediately.
14+
*
15+
* @category Zend
16+
* @package Zend_Http
17+
* @subpackage Header
18+
* @version $Id$
19+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
20+
* @license http://framework.zend.com/license/new-bsd New BSD License
21+
*/
22+
23+
24+
/**
25+
* @category Zend
26+
* @package Zend_Http
27+
* @subpackage Header
28+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
29+
* @license http://framework.zend.com/license/new-bsd New BSD License
30+
*/
31+
final class Zend_Http_Header_HeaderValue
32+
{
33+
/**
34+
* Private constructor; non-instantiable.
35+
*/
36+
private function __construct()
37+
{
38+
}
39+
40+
/**
41+
* Filter a header value
42+
*
43+
* Ensures CRLF header injection vectors are filtered.
44+
*
45+
* Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal
46+
* tabs are allowed in values; only one whitespace character is allowed
47+
* between visible characters.
48+
*
49+
* @see http://en.wikipedia.org/wiki/HTTP_response_splitting
50+
* @param string $value
51+
* @return string
52+
*/
53+
public static function filter($value)
54+
{
55+
$value = (string) $value;
56+
$length = strlen($value);
57+
$string = '';
58+
for ($i = 0; $i < $length; $i += 1) {
59+
$ascii = ord($value[$i]);
60+
61+
// Non-visible, non-whitespace characters
62+
// 9 === horizontal tab
63+
// 32-126, 128-254 === visible
64+
// 127 === DEL
65+
// 255 === null byte
66+
if (($ascii < 32 && $ascii !== 9)
67+
|| $ascii === 127
68+
|| $ascii > 254
69+
) {
70+
continue;
71+
}
72+
73+
$string .= $value[$i];
74+
}
75+
76+
return $string;
77+
}
78+
79+
/**
80+
* Validate a header value.
81+
*
82+
* Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal
83+
* tabs are allowed in values; only one whitespace character is allowed
84+
* between visible characters.
85+
*
86+
* @see http://en.wikipedia.org/wiki/HTTP_response_splitting
87+
* @param string $value
88+
* @return bool
89+
*/
90+
public static function isValid($value)
91+
{
92+
$value = (string) $value;
93+
$length = strlen($value);
94+
for ($i = 0; $i < $length; $i += 1) {
95+
$ascii = ord($value[$i]);
96+
97+
// Non-visible, non-whitespace characters
98+
// 9 === horizontal tab
99+
// 32-126, 128-254 === visible
100+
// 127 === DEL
101+
// 255 === null byte
102+
if (($ascii < 32 && $ascii !== 9)
103+
|| $ascii === 127
104+
|| $ascii > 254
105+
) {
106+
return false;
107+
}
108+
}
109+
110+
return true;
111+
}
112+
113+
/**
114+
* Assert a header value is valid.
115+
*
116+
* @param string $value
117+
* @throws Exception\RuntimeException for invalid values
118+
* @return void
119+
*/
120+
public static function assertValid($value)
121+
{
122+
if (! self::isValid($value)) {
123+
require_once 'Zend/Http/Header/Exception/InvalidArgumentException.php';
124+
throw new Zend_Http_Header_Exception_InvalidArgumentException('Invalid header value');
125+
}
126+
}
127+
}

library/Zend/Http/Header/SetCookie.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
*/
3232
require_once "Zend/Http/Header/Exception/RuntimeException.php";
3333

34+
/**
35+
* @see Zend_Http_Header_HeaderValue
36+
*/
37+
require_once "Zend/Http/Header/HeaderValue.php";
38+
3439
/**
3540
* Zend_Http_Client is an implementation of an HTTP client in PHP. The client
3641
* supports basic features like sending different HTTP requests and handling
@@ -311,6 +316,7 @@ public function getName()
311316
*/
312317
public function setValue($value)
313318
{
319+
Zend_Http_Header_HeaderValue::assertValid($value);
314320
$this->value = $value;
315321
return $this;
316322
}
@@ -405,6 +411,7 @@ public function getExpires($inSeconds = false)
405411
*/
406412
public function setDomain($domain)
407413
{
414+
Zend_Http_Header_HeaderValue::assertValid($domain);
408415
$this->domain = $domain;
409416
return $this;
410417
}
@@ -422,6 +429,7 @@ public function getDomain()
422429
*/
423430
public function setPath($path)
424431
{
432+
Zend_Http_Header_HeaderValue::assertValid($path);
425433
$this->path = $path;
426434
return $this;
427435
}

0 commit comments

Comments
 (0)