Skip to content
This repository has been archived by the owner on May 16, 2018. It is now read-only.

Commit

Permalink
Merge branch 'security/ZF2015-09'
Browse files Browse the repository at this point in the history
Fix for ZF2015-09.
  • Loading branch information
weierophinney committed Nov 23, 2015
2 parents 6ee4fa2 + 4a41392 commit 9f0bbc8
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 21 deletions.
29 changes: 17 additions & 12 deletions library/Zend/Captcha/Word.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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");
/**#@-*/

/**
Expand Down Expand Up @@ -175,7 +178,7 @@ public function setWordlen($wordlen)
*
* @return string
*/
public function getId ()
public function getId()
{
if (null === $this->_id) {
$this->_setId($this->_generateRandomId());
Expand All @@ -189,7 +192,7 @@ public function getId ()
* @param string $id
* @return Zend_Captcha_Word
*/
protected function _setId ($id)
protected function _setId($id)
{
$this->_id = $id;
return $this;
Expand Down Expand Up @@ -250,7 +253,7 @@ public function setUseNumbers($_useNumbers)
$this->_useNumbers = $_useNumbers;
return $this;
}

/**
* Get session object
*
Expand Down Expand Up @@ -280,7 +283,7 @@ public function getSession()
public function setSession(Zend_Session_Namespace $session)
{
$this->_session = $session;
if($session) {
if ($session) {
$this->_keepSession = true;
}
return $this;
Expand Down Expand Up @@ -326,10 +329,12 @@ protected function _generateWord()
$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;
}

Expand All @@ -347,7 +352,7 @@ protected function _generateWord()
*/
public function generate()
{
if(!$this->_keepSession) {
if (!$this->_keepSession) {
$this->_session = null;
}
$id = $this->_generateRandomId();
Expand All @@ -359,7 +364,7 @@ public function generate()

protected function _generateRandomId()
{
return md5(mt_rand(0, 1000) . microtime(true));
return md5(Zend_Crypt_Math::randBytes(32));
}

/**
Expand Down
100 changes: 94 additions & 6 deletions library/Zend/Crypt/Math.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,109 @@ public function rand($minimum, $maximum)
}
$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;
}
Expand All @@ -84,7 +172,8 @@ public function btwoc($long) {
* @param string $binary
* @return string
*/
public function fromBinary($binary) {
public function fromBinary($binary)
{
return $this->_math->binaryToInteger($binary);
}

Expand All @@ -98,5 +187,4 @@ public function toBinary($integer)
{
return $this->_math->integerToBinary($integer);
}

}
75 changes: 72 additions & 3 deletions tests/Zend/Crypt/MathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*/

require_once 'Zend/Crypt/Math.php';

require_once 'Zend/Crypt/Exception.php';

/**
* @category Zend
Expand All @@ -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');
}

Expand All @@ -59,4 +58,74 @@ public function testRand()
$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');
}
}
}

2 comments on commit 9f0bbc8

@jonnott
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zend_Version::VERSION constant is incorrect (1.12.17dev) for this release tag.. :(

@jonnott
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..also incorrect in README.md

Please sign in to comment.