Package: release.debian.org
Severity: normal
Tags: jessie
User: release.debian.org@packages.debian.org
Usertags: pu
Hi,
As agreed with the security team, this update aims to fix a security
issue in zendframework (request for Wheezy follows) via pu. Please find
attached the debdiff, as well as the actual patch with some formating
noise removed.
* Backport security fix from 1.12.17:
- ZF2015-09: Fixed entropy issue in word CAPTCHA
http://framework.zend.com/security/advisory/ZF2015-09
Thanks in advance for considering.
Regards
David
diff --git a/debian/changelog b/debian/changelog
index 915004f..9977720 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+zendframework (1.12.9+dfsg-2+deb8u5) jessie; urgency=medium
+
+ * Backport security fix from 1.12.17
+ - ZF2015-09: Fixed entropy issue in word CAPTCHA
+ http://framework.zend.com/security/advisory/ZF2015-09
+
+ -- David Prévot <taffit@debian.org> Tue, 24 Nov 2015 18:21:26 -0400
+
zendframework (1.12.9+dfsg-2+deb8u4) jessie-security; urgency=high
* Backport security fixes from 1.12.16:
diff --git a/debian/patches/0008-ZF2015-09-Fixed-entropy-issue-in-word-CAPTCHA.patch b/debian/patches/0008-ZF2015-09-Fixed-entropy-issue-in-word-CAPTCHA.patch
new file mode 100644
index 0000000..412b779
--- /dev/null
+++ b/debian/patches/0008-ZF2015-09-Fixed-entropy-issue-in-word-CAPTCHA.patch
@@ -0,0 +1,347 @@
+From: Enrico Zimuel <e.zimuel@gmail.com>
+Date: Mon, 9 Nov 2015 17:26:45 +0100
+Subject: ZF2015-09: Fixed entropy issue in word CAPTCHA
+
+This patch fixes a potential entropy fixation vector with `Zend_Captcha_Word`.
+Prior to the fix, when selecting letters for the CAPTCHA, `array_rand()` was
+used, which does not use sufficient entropy during randomization. The patch
+backports randomization routines from ZF2 in order to provide a more
+cryptographically secure RNG.
+
+Origin: upstream, https://github.com/zendframework/zf1/commit/4a41392f89bf510a8ab801eacb117fe7ea25b575
+---
+ library/Zend/Captcha/Word.php | 29 +++++++-----
+ library/Zend/Crypt/Math.php | 100 +++++++++++++++++++++++++++++++++++++++---
+ tests/Zend/Crypt/MathTest.php | 75 +++++++++++++++++++++++++++++--
+ 3 files changed, 183 insertions(+), 21 deletions(-)
+
+diff --git a/library/Zend/Captcha/Word.php b/library/Zend/Captcha/Word.php
+index 1f0e0fc..ba39580 100644
+--- a/library/Zend/Captcha/Word.php
++++ b/library/Zend/Captcha/Word.php
+@@ -22,6 +22,9 @@
+ /** @see Zend_Captcha_Base */
+ require_once 'Zend/Captcha/Base.php';
+
++/** @see Zend_Crypt_Math */
++require_once 'Zend/Crypt/Math.php';
++
+ /**
+ * Word-based captcha adapter
+ *
+@@ -39,10 +42,10 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+ /**#@+
+ * @var array Character sets
+ */
+- static $V = array("a", "e", "i", "o", "u", "y");
+- static $VN = array("a", "e", "i", "o", "u", "y","2","3","4","5","6","7","8","9");
+- static $C = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z");
+- static $CN = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z","2","3","4","5","6","7","8","9");
++ static public $V = array("a", "e", "i", "o", "u", "y");
++ static public $VN = array("a", "e", "i", "o", "u", "y","2","3","4","5","6","7","8","9");
++ static public $C = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z");
++ static public $CN = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z","2","3","4","5","6","7","8","9");
+ /**#@-*/
+
+ /**
+@@ -175,7 +178,7 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+ *
+ * @return string
+ */
+- public function getId ()
++ public function getId()
+ {
+ if (null === $this->_id) {
+ $this->_setId($this->_generateRandomId());
+@@ -189,7 +192,7 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+ * @param string $id
+ * @return Zend_Captcha_Word
+ */
+- protected function _setId ($id)
++ protected function _setId($id)
+ {
+ $this->_id = $id;
+ return $this;
+@@ -250,7 +253,7 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+ $this->_useNumbers = $_useNumbers;
+ return $this;
+ }
+-
++
+ /**
+ * Get session object
+ *
+@@ -280,7 +283,7 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+ public function setSession(Zend_Session_Namespace $session)
+ {
+ $this->_session = $session;
+- if($session) {
++ if ($session) {
+ $this->_keepSession = true;
+ }
+ return $this;
+@@ -326,10 +329,12 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+ $vowels = $this->_useNumbers ? self::$VN : self::$V;
+ $consonants = $this->_useNumbers ? self::$CN : self::$C;
+
++ $totIndexCon = count($consonants) - 1;
++ $totIndexVow = count($vowels) - 1;
+ for ($i=0; $i < $wordLen; $i = $i + 2) {
+ // generate word with mix of vowels and consonants
+- $consonant = $consonants[array_rand($consonants)];
+- $vowel = $vowels[array_rand($vowels)];
++ $consonant = $consonants[Zend_Crypt_Math::randInteger(0, $totIndexCon, true)];
++ $vowel = $vowels[Zend_Crypt_Math::randInteger(0, $totIndexVow, true)];
+ $word .= $consonant . $vowel;
+ }
+
+@@ -347,7 +352,7 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+ */
+ public function generate()
+ {
+- if(!$this->_keepSession) {
++ if (!$this->_keepSession) {
+ $this->_session = null;
+ }
+ $id = $this->_generateRandomId();
+@@ -359,7 +364,7 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
+
+ protected function _generateRandomId()
+ {
+- return md5(mt_rand(0, 1000) . microtime(true));
++ return md5(Zend_Crypt_Math::randBytes(32));
+ }
+
+ /**
+diff --git a/library/Zend/Crypt/Math.php b/library/Zend/Crypt/Math.php
+index 40395f5..8882259 100644
+--- a/library/Zend/Crypt/Math.php
++++ b/library/Zend/Crypt/Math.php
+@@ -57,21 +57,109 @@ class Zend_Crypt_Math extends Zend_Crypt_Math_BigInteger
+ }
+ $rand = '';
+ $i2 = strlen($maximum) - 1;
+- for ($i = 1;$i < $i2;$i++) {
+- $rand .= mt_rand(0,9);
++ for ($i = 1; $i < $i2; $i++) {
++ $rand .= mt_rand(0, 9);
+ }
+- $rand .= mt_rand(0,9);
++ $rand .= mt_rand(0, 9);
+ return $rand;
+ }
+
+ /**
++ * Return a random strings of $length bytes
++ *
++ * @param integer $length
++ * @param boolean $strong
++ * @return string
++ */
++ public static function randBytes($length, $strong = false)
++ {
++ $length = (int) $length;
++ if ($length <= 0) {
++ return false;
++ }
++ if (function_exists('openssl_random_pseudo_bytes')) {
++ $bytes = openssl_random_pseudo_bytes($length, $usable);
++ if ($strong === $usable) {
++ return $bytes;
++ }
++ }
++ if (function_exists('mcrypt_create_iv')) {
++ $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
++ if ($bytes !== false && strlen($bytes) === $length) {
++ return $bytes;
++ }
++ }
++ if (file_exists('/dev/urandom') && is_readable('/dev/urandom')) {
++ $frandom = fopen('/dev/urandom', 'r');
++ if ($frandom !== false) {
++ return fread($frandom, $length);
++ }
++ }
++ if (true === $strong) {
++ require_once 'Zend/Crypt/Exception.php';
++ throw new Zend_Crypt_Exception(
++ 'This PHP environment doesn\'t support secure random number generation. ' .
++ 'Please consider installing the OpenSSL and/or Mcrypt extensions'
++ );
++ }
++ $rand = '';
++ for ($i = 0; $i < $length; $i++) {
++ $rand .= chr(mt_rand(0, 255));
++ }
++ return $rand;
++ }
++
++ /**
++ * Return a random integer between $min and $max
++ *
++ * @param integer $min
++ * @param integer $max
++ * @param boolean $strong
++ * @return integer
++ */
++ public static function randInteger($min, $max, $strong = false)
++ {
++ if ($min > $max) {
++ require_once 'Zend/Crypt/Exception.php';
++ throw new Zend_Crypt_Exception(
++ 'The min parameter must be lower than max parameter'
++ );
++ }
++ $range = $max - $min;
++ if ($range == 0) {
++ return $max;
++ } elseif ($range > PHP_INT_MAX || is_float($range)) {
++ require_once 'Zend/Crypt/Exception.php';
++ throw new Zend_Crypt_Exception(
++ 'The supplied range is too great to generate'
++ );
++ }
++ // calculate number of bits required to store range on this machine
++ $r = $range;
++ $bits = 0;
++ while ($r) {
++ $bits++;
++ $r >>= 1;
++ }
++ $bits = (int) max($bits, 1);
++ $bytes = (int) max(ceil($bits / 8), 1);
++ $filter = (int) ((1 << $bits) - 1);
++ do {
++ $rnd = hexdec(bin2hex(self::randBytes($bytes, $strong)));
++ $rnd &= $filter;
++ } while ($rnd > $range);
++ return ($min + $rnd);
++ }
++
++ /**
+ * Get the big endian two's complement of a given big integer in
+ * binary notation
+ *
+ * @param string $long
+ * @return string
+ */
+- public function btwoc($long) {
++ public function btwoc($long)
++ {
+ if (ord($long[0]) > 127) {
+ return "\x00" . $long;
+ }
+@@ -84,7 +172,8 @@ class Zend_Crypt_Math extends Zend_Crypt_Math_BigInteger
+ * @param string $binary
+ * @return string
+ */
+- public function fromBinary($binary) {
++ public function fromBinary($binary)
++ {
+ return $this->_math->binaryToInteger($binary);
+ }
+
+@@ -98,5 +187,4 @@ class Zend_Crypt_Math extends Zend_Crypt_Math_BigInteger
+ {
+ return $this->_math->integerToBinary($integer);
+ }
+-
+ }
+diff --git a/tests/Zend/Crypt/MathTest.php b/tests/Zend/Crypt/MathTest.php
+index eeb9325..79bd02e 100644
+--- a/tests/Zend/Crypt/MathTest.php
++++ b/tests/Zend/Crypt/MathTest.php
+@@ -21,7 +21,7 @@
+ */
+
+ require_once 'Zend/Crypt/Math.php';
+-
++require_once 'Zend/Crypt/Exception.php';
+
+ /**
+ * @category Zend
+@@ -36,8 +36,7 @@ class Zend_Crypt_MathTest extends PHPUnit_Framework_TestCase
+
+ public function testRand()
+ {
+- if (!extension_loaded('bcmath'))
+- {
++ if (!extension_loaded('bcmath')) {
+ $this->markTestSkipped('Extension bcmath not loaded');
+ }
+
+@@ -59,4 +58,74 @@ class Zend_Crypt_MathTest extends PHPUnit_Framework_TestCase
+ $this->assertTrue(bccomp($result, $lower) !== '-1');
+ }
+
++ public function testRandBytes()
++ {
++ for ($length = 1; $length < 4096; $length++) {
++ $rand = Zend_Crypt_Math::randBytes($length);
++ $this->assertTrue(false !== $rand);
++ $this->assertEquals($length, strlen($rand));
++ }
++ }
++
++ public function testRandInteger()
++ {
++ for ($i = 0; $i < 1024; $i++) {
++ $min = rand(1, PHP_INT_MAX/2);
++ $max = $min + rand(1, PHP_INT_MAX/2 - 1);
++ $rand = Zend_Crypt_Math::randInteger($min, $max);
++ $this->assertGreaterThanOrEqual($min, $rand);
++ $this->assertLessThanOrEqual($max, $rand);
++ }
++ }
++
++ public static function provideRandInt()
++ {
++ return [
++ [2, 1, 10000, 100, 0.9, 1.1, false],
++ [2, 1, 10000, 100, 0.8, 1.2, true]
++ ];
++ }
++
++ /**
++ * A Monte Carlo test that generates $cycles numbers from 0 to $tot
++ * and test if the numbers are above or below the line y=x with a
++ * frequency range of [$min, $max]
++ *
++ * @dataProvider provideRandInt
++ */
++ public function testMontecarloRandInteger($num, $valid, $cycles, $tot, $min, $max, $strong)
++ {
++ try {
++ $test = Zend_Crypt_Math::randBytes(1, $strong);
++ } catch (Zend_Crypt_Exception $e) {
++ $this->markTestSkipped($e->getMessage());
++ }
++
++ $i = 0;
++ $count = 0;
++ do {
++ $up = 0;
++ $down = 0;
++ for ($i = 0; $i < $cycles; $i++) {
++ $x = Zend_Crypt_Math::randInteger(0, $tot, $strong);
++ $y = Zend_Crypt_Math::randInteger(0, $tot, $strong);
++ if ($x > $y) {
++ $up++;
++ } elseif ($x < $y) {
++ $down++;
++ }
++ }
++ $this->assertGreaterThan(0, $up);
++ $this->assertGreaterThan(0, $down);
++ $ratio = $up / $down;
++ if ($ratio > $min && $ratio < $max) {
++ $count++;
++ }
++ $i++;
++ } while ($i < $num && $count < $valid);
++
++ if ($count < $valid) {
++ $this->fail('The random number generator failed the Monte Carlo test');
++ }
++ }
+ }
diff --git a/debian/patches/series b/debian/patches/series
index 6c2191f..f9d3a80 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -5,3 +5,4 @@
0005-ZF2015-06-Fix-potential-XXE-vector-via-BOM-detection.patch
0006-ZF2015-07-Use-umask-of-0002.patch
0007-ZF2015-08-Fix-null-byte-injection-for-PDO-MsSql.patch
+0008-ZF2015-09-Fixed-entropy-issue-in-word-CAPTCHA.patch
diff --git a/library/Zend/Captcha/Word.php b/library/Zend/Captcha/Word.php
index 1f0e0fc..ba39580 100644
--- a/library/Zend/Captcha/Word.php
+++ b/library/Zend/Captcha/Word.php
@@ -22,6 +22,9 @@
/** @see Zend_Captcha_Base */
require_once 'Zend/Captcha/Base.php';
+/** @see Zend_Crypt_Math */
+require_once 'Zend/Crypt/Math.php';
+
/**
* Word-based captcha adapter
*
@@ -39,10 +42,10 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
/**#@+
* @var array Character sets
*/
- static $V = array("a", "e", "i", "o", "u", "y");
- static $VN = array("a", "e", "i", "o", "u", "y","2","3","4","5","6","7","8","9");
- static $C = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z");
- static $CN = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z","2","3","4","5","6","7","8","9");
+ static public $V = array("a", "e", "i", "o", "u", "y");
+ static public $VN = array("a", "e", "i", "o", "u", "y","2","3","4","5","6","7","8","9");
+ static public $C = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z");
+ static public $CN = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z","2","3","4","5","6","7","8","9");
/**#@-*/
/**
@@ -326,10 +329,12 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
$vowels = $this->_useNumbers ? self::$VN : self::$V;
$consonants = $this->_useNumbers ? self::$CN : self::$C;
+ $totIndexCon = count($consonants) - 1;
+ $totIndexVow = count($vowels) - 1;
for ($i=0; $i < $wordLen; $i = $i + 2) {
// generate word with mix of vowels and consonants
- $consonant = $consonants[array_rand($consonants)];
- $vowel = $vowels[array_rand($vowels)];
+ $consonant = $consonants[Zend_Crypt_Math::randInteger(0, $totIndexCon, true)];
+ $vowel = $vowels[Zend_Crypt_Math::randInteger(0, $totIndexVow, true)];
$word .= $consonant . $vowel;
}
@@ -359,7 +364,7 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
protected function _generateRandomId()
{
- return md5(mt_rand(0, 1000) . microtime(true));
+ return md5(Zend_Crypt_Math::randBytes(32));
}
/**
diff --git a/library/Zend/Crypt/Math.php b/library/Zend/Crypt/Math.php
index 40395f5..8882259 100644
--- a/library/Zend/Crypt/Math.php
+++ b/library/Zend/Crypt/Math.php
@@ -65,13 +65,101 @@ class Zend_Crypt_Math extends Zend_Crypt_Math_BigInteger
}
/**
+ * Return a random strings of $length bytes
+ *
+ * @param integer $length
+ * @param boolean $strong
+ * @return string
+ */
+ public static function randBytes($length, $strong = false)
+ {
+ $length = (int) $length;
+ if ($length <= 0) {
+ return false;
+ }
+ if (function_exists('openssl_random_pseudo_bytes')) {
+ $bytes = openssl_random_pseudo_bytes($length, $usable);
+ if ($strong === $usable) {
+ return $bytes;
+ }
+ }
+ if (function_exists('mcrypt_create_iv')) {
+ $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
+ if ($bytes !== false && strlen($bytes) === $length) {
+ return $bytes;
+ }
+ }
+ if (file_exists('/dev/urandom') && is_readable('/dev/urandom')) {
+ $frandom = fopen('/dev/urandom', 'r');
+ if ($frandom !== false) {
+ return fread($frandom, $length);
+ }
+ }
+ if (true === $strong) {
+ require_once 'Zend/Crypt/Exception.php';
+ throw new Zend_Crypt_Exception(
+ 'This PHP environment doesn\'t support secure random number generation. ' .
+ 'Please consider installing the OpenSSL and/or Mcrypt extensions'
+ );
+ }
+ $rand = '';
+ for ($i = 0; $i < $length; $i++) {
+ $rand .= chr(mt_rand(0, 255));
+ }
+ return $rand;
+ }
+
+ /**
+ * Return a random integer between $min and $max
+ *
+ * @param integer $min
+ * @param integer $max
+ * @param boolean $strong
+ * @return integer
+ */
+ public static function randInteger($min, $max, $strong = false)
+ {
+ if ($min > $max) {
+ require_once 'Zend/Crypt/Exception.php';
+ throw new Zend_Crypt_Exception(
+ 'The min parameter must be lower than max parameter'
+ );
+ }
+ $range = $max - $min;
+ if ($range == 0) {
+ return $max;
+ } elseif ($range > PHP_INT_MAX || is_float($range)) {
+ require_once 'Zend/Crypt/Exception.php';
+ throw new Zend_Crypt_Exception(
+ 'The supplied range is too great to generate'
+ );
+ }
+ // calculate number of bits required to store range on this machine
+ $r = $range;
+ $bits = 0;
+ while ($r) {
+ $bits++;
+ $r >>= 1;
+ }
+ $bits = (int) max($bits, 1);
+ $bytes = (int) max(ceil($bits / 8), 1);
+ $filter = (int) ((1 << $bits) - 1);
+ do {
+ $rnd = hexdec(bin2hex(self::randBytes($bytes, $strong)));
+ $rnd &= $filter;
+ } while ($rnd > $range);
+ return ($min + $rnd);
+ }
+
+ /**
* Get the big endian two's complement of a given big integer in
* binary notation
*
* @param string $long
* @return string
*/
- public function btwoc($long) {
+ public function btwoc($long)
+ {
if (ord($long[0]) > 127) {
return "\x00" . $long;
}
diff --git a/tests/Zend/Crypt/MathTest.php b/tests/Zend/Crypt/MathTest.php
index eeb9325..79bd02e 100644
--- a/tests/Zend/Crypt/MathTest.php
+++ b/tests/Zend/Crypt/MathTest.php
@@ -21,7 +21,7 @@
*/
require_once 'Zend/Crypt/Math.php';
-
+require_once 'Zend/Crypt/Exception.php';
/**
* @category Zend
@@ -59,4 +58,74 @@ class Zend_Crypt_MathTest extends PHPUnit_Framework_TestCase
$this->assertTrue(bccomp($result, $lower) !== '-1');
}
+ public function testRandBytes()
+ {
+ for ($length = 1; $length < 4096; $length++) {
+ $rand = Zend_Crypt_Math::randBytes($length);
+ $this->assertTrue(false !== $rand);
+ $this->assertEquals($length, strlen($rand));
+ }
+ }
+
+ public function testRandInteger()
+ {
+ for ($i = 0; $i < 1024; $i++) {
+ $min = rand(1, PHP_INT_MAX/2);
+ $max = $min + rand(1, PHP_INT_MAX/2 - 1);
+ $rand = Zend_Crypt_Math::randInteger($min, $max);
+ $this->assertGreaterThanOrEqual($min, $rand);
+ $this->assertLessThanOrEqual($max, $rand);
+ }
+ }
+
+ public static function provideRandInt()
+ {
+ return [
+ [2, 1, 10000, 100, 0.9, 1.1, false],
+ [2, 1, 10000, 100, 0.8, 1.2, true]
+ ];
+ }
+
+ /**
+ * A Monte Carlo test that generates $cycles numbers from 0 to $tot
+ * and test if the numbers are above or below the line y=x with a
+ * frequency range of [$min, $max]
+ *
+ * @dataProvider provideRandInt
+ */
+ public function testMontecarloRandInteger($num, $valid, $cycles, $tot, $min, $max, $strong)
+ {
+ try {
+ $test = Zend_Crypt_Math::randBytes(1, $strong);
+ } catch (Zend_Crypt_Exception $e) {
+ $this->markTestSkipped($e->getMessage());
+ }
+
+ $i = 0;
+ $count = 0;
+ do {
+ $up = 0;
+ $down = 0;
+ for ($i = 0; $i < $cycles; $i++) {
+ $x = Zend_Crypt_Math::randInteger(0, $tot, $strong);
+ $y = Zend_Crypt_Math::randInteger(0, $tot, $strong);
+ if ($x > $y) {
+ $up++;
+ } elseif ($x < $y) {
+ $down++;
+ }
+ }
+ $this->assertGreaterThan(0, $up);
+ $this->assertGreaterThan(0, $down);
+ $ratio = $up / $down;
+ if ($ratio > $min && $ratio < $max) {
+ $count++;
+ }
+ $i++;
+ } while ($i < $num && $count < $valid);
+
+ if ($count < $valid) {
+ $this->fail('The random number generator failed the Monte Carlo test');
+ }
+ }
}
Attachment:
signature.asc
Description: PGP signature