diff options
author | Andrey Andreev <narf@devilix.net> | 2015-10-23 12:49:21 +0200 |
---|---|---|
committer | Andrey Andreev <narf@devilix.net> | 2015-10-31 18:02:55 +0100 |
commit | f8deea583f0cb68a83a44d361c0db3c86f387f95 (patch) | |
tree | c2f549509998c4321d94b019aa8589184d8c2499 | |
parent | 0a6b0661305f20ac1fbd219d43f59193bea90d1d (diff) |
Use proper randomness when generating CAPTCHAs
-rw-r--r-- | system/helpers/captcha_helper.php | 89 |
1 files changed, 87 insertions, 2 deletions
diff --git a/system/helpers/captcha_helper.php b/system/helpers/captcha_helper.php index 201987ac8..85bcfb5a0 100644 --- a/system/helpers/captcha_helper.php +++ b/system/helpers/captcha_helper.php @@ -125,9 +125,94 @@ if ( ! function_exists('create_captcha')) if (empty($word)) { $word = ''; - for ($i = 0, $mt_rand_max = strlen($pool) - 1; $i < $word_length; $i++) + $pool_length = strlen($pool); + $rand_max = $pool_length - 1; + + // PHP7 or a suitable polyfill + if (function_exists('random_int')) + { + try + { + for ($i = 0; $i < $word_length; $i++) + { + $word .= $pool[random_int(0, $rand_max)]; + } + } + catch (Exception $e) + { + // This means fallback to the next possible + // alternative to random_int() + $word = ''; + } + } + } + + if (empty($word)) + { + // Nobody will have a larger character pool than + // 256 characters, but let's handle it just in case ... + // + // No, I do not care that the fallback to mt_rand() can + // handle it; if you trigger this, you're very obviously + // trying to break it. -- Narf + if ($pool_length > 256) + { + return FALSE; + } + + // We'll try using the operating system's PRNG first, + // which we can access through CI_Security::get_random_bytes() + $security = get_instance()->security; + + // To avoid numerous get_random_bytes() calls, we'll + // just try fetching as much bytes as we need at once. + if (($bytes = $security->get_random_bytes($pool_length)) !== FALSE) + { + $byte_index = $word_index = 0; + while ($word_index < $word_length) + { + if (($rand_index = unpack('C', $bytes[$byte_index++])) > $rand_max) + { + // Was this the last byte we have? + // If so, try to fetch more. + if ($byte_index === $pool_length) + { + // No failures should be possible if + // the first get_random_bytes() call + // didn't return FALSE, but still ... + for ($i = 0; $i < 5; $i++) + { + if (($bytes = $security->get_random_bytes($pool_length)) === FALSE) + { + continue; + } + + $byte_index = 0; + break; + } + + if ($bytes === FALSE) + { + // Sadly, this means fallback to mt_rand() + $word = ''; + break; + } + } + + continue; + } + + $word .= $pool[$rand_index]; + $word_index++; + } + } + } + + if (empty($word)) + { + for ($i = 0; $i < $word_length; $i++) { - $word .= $pool[mt_rand(0, $mt_rand_max)]; + $word .= $pool[mt_rand(0, $rand_max)]; } } elseif ( ! is_string($word)) |