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

Commit 9f0bbc8

Browse files
committed
Merge branch 'security/ZF2015-09'
Fix for ZF2015-09.
2 parents 6ee4fa2 + 4a41392 commit 9f0bbc8

File tree

3 files changed

+183
-21
lines changed

3 files changed

+183
-21
lines changed

library/Zend/Captcha/Word.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
/** @see Zend_Captcha_Base */
2323
require_once 'Zend/Captcha/Base.php';
2424

25+
/** @see Zend_Crypt_Math */
26+
require_once 'Zend/Crypt/Math.php';
27+
2528
/**
2629
* Word-based captcha adapter
2730
*
@@ -39,10 +42,10 @@ abstract class Zend_Captcha_Word extends Zend_Captcha_Base
3942
/**#@+
4043
* @var array Character sets
4144
*/
42-
static $V = array("a", "e", "i", "o", "u", "y");
43-
static $VN = array("a", "e", "i", "o", "u", "y","2","3","4","5","6","7","8","9");
44-
static $C = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z");
45-
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");
45+
static public $V = array("a", "e", "i", "o", "u", "y");
46+
static public $VN = array("a", "e", "i", "o", "u", "y","2","3","4","5","6","7","8","9");
47+
static public $C = array("b","c","d","f","g","h","j","k","m","n","p","q","r","s","t","u","v","w","x","z");
48+
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");
4649
/**#@-*/
4750

4851
/**
@@ -175,7 +178,7 @@ public function setWordlen($wordlen)
175178
*
176179
* @return string
177180
*/
178-
public function getId ()
181+
public function getId()
179182
{
180183
if (null === $this->_id) {
181184
$this->_setId($this->_generateRandomId());
@@ -189,7 +192,7 @@ public function getId ()
189192
* @param string $id
190193
* @return Zend_Captcha_Word
191194
*/
192-
protected function _setId ($id)
195+
protected function _setId($id)
193196
{
194197
$this->_id = $id;
195198
return $this;
@@ -250,7 +253,7 @@ public function setUseNumbers($_useNumbers)
250253
$this->_useNumbers = $_useNumbers;
251254
return $this;
252255
}
253-
256+
254257
/**
255258
* Get session object
256259
*
@@ -280,7 +283,7 @@ public function getSession()
280283
public function setSession(Zend_Session_Namespace $session)
281284
{
282285
$this->_session = $session;
283-
if($session) {
286+
if ($session) {
284287
$this->_keepSession = true;
285288
}
286289
return $this;
@@ -326,10 +329,12 @@ protected function _generateWord()
326329
$vowels = $this->_useNumbers ? self::$VN : self::$V;
327330
$consonants = $this->_useNumbers ? self::$CN : self::$C;
328331

332+
$totIndexCon = count($consonants) - 1;
333+
$totIndexVow = count($vowels) - 1;
329334
for ($i=0; $i < $wordLen; $i = $i + 2) {
330335
// generate word with mix of vowels and consonants
331-
$consonant = $consonants[array_rand($consonants)];
332-
$vowel = $vowels[array_rand($vowels)];
336+
$consonant = $consonants[Zend_Crypt_Math::randInteger(0, $totIndexCon, true)];
337+
$vowel = $vowels[Zend_Crypt_Math::randInteger(0, $totIndexVow, true)];
333338
$word .= $consonant . $vowel;
334339
}
335340

@@ -347,7 +352,7 @@ protected function _generateWord()
347352
*/
348353
public function generate()
349354
{
350-
if(!$this->_keepSession) {
355+
if (!$this->_keepSession) {
351356
$this->_session = null;
352357
}
353358
$id = $this->_generateRandomId();
@@ -359,7 +364,7 @@ public function generate()
359364

360365
protected function _generateRandomId()
361366
{
362-
return md5(mt_rand(0, 1000) . microtime(true));
367+
return md5(Zend_Crypt_Math::randBytes(32));
363368
}
364369

365370
/**

library/Zend/Crypt/Math.php

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,109 @@ public function rand($minimum, $maximum)
5757
}
5858
$rand = '';
5959
$i2 = strlen($maximum) - 1;
60-
for ($i = 1;$i < $i2;$i++) {
61-
$rand .= mt_rand(0,9);
60+
for ($i = 1; $i < $i2; $i++) {
61+
$rand .= mt_rand(0, 9);
6262
}
63-
$rand .= mt_rand(0,9);
63+
$rand .= mt_rand(0, 9);
6464
return $rand;
6565
}
6666

67+
/**
68+
* Return a random strings of $length bytes
69+
*
70+
* @param integer $length
71+
* @param boolean $strong
72+
* @return string
73+
*/
74+
public static function randBytes($length, $strong = false)
75+
{
76+
$length = (int) $length;
77+
if ($length <= 0) {
78+
return false;
79+
}
80+
if (function_exists('openssl_random_pseudo_bytes')) {
81+
$bytes = openssl_random_pseudo_bytes($length, $usable);
82+
if ($strong === $usable) {
83+
return $bytes;
84+
}
85+
}
86+
if (function_exists('mcrypt_create_iv')) {
87+
$bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
88+
if ($bytes !== false && strlen($bytes) === $length) {
89+
return $bytes;
90+
}
91+
}
92+
if (file_exists('/dev/urandom') && is_readable('/dev/urandom')) {
93+
$frandom = fopen('/dev/urandom', 'r');
94+
if ($frandom !== false) {
95+
return fread($frandom, $length);
96+
}
97+
}
98+
if (true === $strong) {
99+
require_once 'Zend/Crypt/Exception.php';
100+
throw new Zend_Crypt_Exception(
101+
'This PHP environment doesn\'t support secure random number generation. ' .
102+
'Please consider installing the OpenSSL and/or Mcrypt extensions'
103+
);
104+
}
105+
$rand = '';
106+
for ($i = 0; $i < $length; $i++) {
107+
$rand .= chr(mt_rand(0, 255));
108+
}
109+
return $rand;
110+
}
111+
112+
/**
113+
* Return a random integer between $min and $max
114+
*
115+
* @param integer $min
116+
* @param integer $max
117+
* @param boolean $strong
118+
* @return integer
119+
*/
120+
public static function randInteger($min, $max, $strong = false)
121+
{
122+
if ($min > $max) {
123+
require_once 'Zend/Crypt/Exception.php';
124+
throw new Zend_Crypt_Exception(
125+
'The min parameter must be lower than max parameter'
126+
);
127+
}
128+
$range = $max - $min;
129+
if ($range == 0) {
130+
return $max;
131+
} elseif ($range > PHP_INT_MAX || is_float($range)) {
132+
require_once 'Zend/Crypt/Exception.php';
133+
throw new Zend_Crypt_Exception(
134+
'The supplied range is too great to generate'
135+
);
136+
}
137+
// calculate number of bits required to store range on this machine
138+
$r = $range;
139+
$bits = 0;
140+
while ($r) {
141+
$bits++;
142+
$r >>= 1;
143+
}
144+
$bits = (int) max($bits, 1);
145+
$bytes = (int) max(ceil($bits / 8), 1);
146+
$filter = (int) ((1 << $bits) - 1);
147+
do {
148+
$rnd = hexdec(bin2hex(self::randBytes($bytes, $strong)));
149+
$rnd &= $filter;
150+
} while ($rnd > $range);
151+
return ($min + $rnd);
152+
}
153+
67154
/**
68155
* Get the big endian two's complement of a given big integer in
69156
* binary notation
70157
*
71158
* @param string $long
72159
* @return string
73160
*/
74-
public function btwoc($long) {
161+
public function btwoc($long)
162+
{
75163
if (ord($long[0]) > 127) {
76164
return "\x00" . $long;
77165
}
@@ -84,7 +172,8 @@ public function btwoc($long) {
84172
* @param string $binary
85173
* @return string
86174
*/
87-
public function fromBinary($binary) {
175+
public function fromBinary($binary)
176+
{
88177
return $this->_math->binaryToInteger($binary);
89178
}
90179

@@ -98,5 +187,4 @@ public function toBinary($integer)
98187
{
99188
return $this->_math->integerToBinary($integer);
100189
}
101-
102190
}

tests/Zend/Crypt/MathTest.php

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*/
2222

2323
require_once 'Zend/Crypt/Math.php';
24-
24+
require_once 'Zend/Crypt/Exception.php';
2525

2626
/**
2727
* @category Zend
@@ -36,8 +36,7 @@ class Zend_Crypt_MathTest extends PHPUnit_Framework_TestCase
3636

3737
public function testRand()
3838
{
39-
if (!extension_loaded('bcmath'))
40-
{
39+
if (!extension_loaded('bcmath')) {
4140
$this->markTestSkipped('Extension bcmath not loaded');
4241
}
4342

@@ -59,4 +58,74 @@ public function testRand()
5958
$this->assertTrue(bccomp($result, $lower) !== '-1');
6059
}
6160

61+
public function testRandBytes()
62+
{
63+
for ($length = 1; $length < 4096; $length++) {
64+
$rand = Zend_Crypt_Math::randBytes($length);
65+
$this->assertTrue(false !== $rand);
66+
$this->assertEquals($length, strlen($rand));
67+
}
68+
}
69+
70+
public function testRandInteger()
71+
{
72+
for ($i = 0; $i < 1024; $i++) {
73+
$min = rand(1, PHP_INT_MAX/2);
74+
$max = $min + rand(1, PHP_INT_MAX/2 - 1);
75+
$rand = Zend_Crypt_Math::randInteger($min, $max);
76+
$this->assertGreaterThanOrEqual($min, $rand);
77+
$this->assertLessThanOrEqual($max, $rand);
78+
}
79+
}
80+
81+
public static function provideRandInt()
82+
{
83+
return [
84+
[2, 1, 10000, 100, 0.9, 1.1, false],
85+
[2, 1, 10000, 100, 0.8, 1.2, true]
86+
];
87+
}
88+
89+
/**
90+
* A Monte Carlo test that generates $cycles numbers from 0 to $tot
91+
* and test if the numbers are above or below the line y=x with a
92+
* frequency range of [$min, $max]
93+
*
94+
* @dataProvider provideRandInt
95+
*/
96+
public function testMontecarloRandInteger($num, $valid, $cycles, $tot, $min, $max, $strong)
97+
{
98+
try {
99+
$test = Zend_Crypt_Math::randBytes(1, $strong);
100+
} catch (Zend_Crypt_Exception $e) {
101+
$this->markTestSkipped($e->getMessage());
102+
}
103+
104+
$i = 0;
105+
$count = 0;
106+
do {
107+
$up = 0;
108+
$down = 0;
109+
for ($i = 0; $i < $cycles; $i++) {
110+
$x = Zend_Crypt_Math::randInteger(0, $tot, $strong);
111+
$y = Zend_Crypt_Math::randInteger(0, $tot, $strong);
112+
if ($x > $y) {
113+
$up++;
114+
} elseif ($x < $y) {
115+
$down++;
116+
}
117+
}
118+
$this->assertGreaterThan(0, $up);
119+
$this->assertGreaterThan(0, $down);
120+
$ratio = $up / $down;
121+
if ($ratio > $min && $ratio < $max) {
122+
$count++;
123+
}
124+
$i++;
125+
} while ($i < $num && $count < $valid);
126+
127+
if ($count < $valid) {
128+
$this->fail('The random number generator failed the Monte Carlo test');
129+
}
130+
}
62131
}

0 commit comments

Comments
 (0)