> 백엔드 개발 > PHP 튜토리얼 > PHP의 mt_rand() 난수 보안에 대한 심층적인 이해

PHP의 mt_rand() 난수 보안에 대한 심층적인 이해

黄舟
풀어 주다: 2023-03-16 16:18:01
원래의
4302명이 탐색했습니다.

mt_rand()는 mersennetwister 알고리즘을 사용하여 임의의 정수를 반환합니다. 모두가 알고 있지만 다음 기사에서는 주로 PHP의 mt_rand() 난수 보안에 대한 관련 정보를 소개합니다. 필요하신 분은 가능합니다. 참고로 아래 에디터와 함께 배워보겠습니다.

머리말

얼마 전 mt_rand()와 관련된 보안 취약점을 많이 발견했는데, 이는 기본적으로 난수 사용에 대한 오해로 인해 발생했습니다. 여기서는 php 공식 웹사이트 매뉴얼의 또 다른 함정에 대해 언급하고 싶습니다. mt_rand()의 소개를 살펴보겠습니다: 중국어 버전 ^cn 영어 버전 ^en 주의 사항. warning


This function does not generate cryptographically secure values, and should not be used for cryptographic purposes. If you need a cryptographically secure value, consider using random_int(), random_bytes(), or openssl_random_pseudo_bytes() instead.
로그인 후 복사

많은 국내 개발자들은 아마도 중국어 버전의 서문을 읽고 프로그램에서 mt_rand()를 사용하여 보안 토큰, 핵심 암호화 및 복호화 키 등을 생성하여 심각한 보안 문제를 일으켰을 것입니다.

의사 난수

mt_rand()는 실제 난수 생성 함수가 아닙니다. 실제로 대부분의 프로그래밍 언어의 난수 함수는 의사 난수를 생성합니다. 실제 난수와 의사 난수의 차이점은 여기서 설명하지 않습니다. 의사 난수는 일반적으로 사용되는 시계를 통해 결정 가능한 함수(일반적으로 사용되는 선형 합동)에 의해 생성됩니다. ). 즉, 시드나 생성된 난수를 알면 다음 난수열에 대한 정보를 얻을 수 있다는 뜻이다(예측성).

mt_rand()에서 내부적으로 난수를 생성하는 함수는 다음과 같다고 간단히 가정해 보세요.

여기서 seed는 난수 시드이고 i는 이 난수 함수가 호출되는 횟수입니다. i와 rand의 두 값을 동시에 알면 시드의 값을 쉽게 계산할 수 있습니다. 예를 들어, rand=21 및 i=2는 21=seed+(2*10) 함수로 대체되어 seed=1을 얻습니다. 아주 간단하지 않나요? 시드를 얻은 후에 i가 임의의 값일 때 랜드 값을 계산할 수 있습니다.

rand = seed+(i*10)

PHP의 자동 시드
이전 섹션에서 우리는 mt_rand()가 호출될 때마다 시드와 현재 호출 수 i를 기반으로 의사 난수가 계산된다는 것을 이미 알고 있습니다. 그리고 시드는 자동으로 시드됩니다:

참고: PHP 4.2.0부터 난수 생성기를 시드하기 위해 srand() 또는 mt_srand()를 사용할 필요가 없습니다. 이제 시스템에서 자동으로 시드가 수행되기 때문입니다.

그러면 시스템은 언제 자동으로 시드를 완료합니까? mt_rand()가 호출될 때마다 자동으로 시드를 생성한다면 시드를 크래킹할 필요가 없습니다. 매뉴얼에서는 이 점에 대한 자세한 정보를 제공하지 않습니다. 인터넷을 여기저기 찾아보았지만 믿을만한 답변을 찾을 수 없어서 소스코드를 확인해야만 했습니다 ^mtrand:

PHPAPI void php_mt_srand(uint32_t seed)
{
 /* Seed the generator with a simple uint32 */
 php_mt_initialize(seed, BG(state));
 php_mt_reload();

 /* Seed only once */
 BG(mt_rand_is_seeded) = 1; 
}
/* }}} */

/* {{{ php_mt_rand
 */
PHPAPI uint32_t php_mt_rand(void)
{
 /* Pull a 32-bit integer from the generator state
 Every other access function simply transforms the numbers extracted here */

 register uint32_t s1;

 if (UNEXPECTED(!BG(mt_rand_is_seeded))) {
 php_mt_srand(GENERATE_SEED());
 }

 if (BG(left) == 0) {
 php_mt_reload();
 }
 --BG(left);

 s1 = *BG(next)++;
 s1 ^= (s1 >> 11);
 s1 ^= (s1 << 7) & 0x9d2c5680U;
 s1 ^= (s1 << 15) & 0xefc60000U;
 return ( s1 ^ (s1 >> 18) );
}
로그인 후 복사

mt_rand()가 호출될 때마다 먼저 해당 내용이 맞는지 확인하는 것을 볼 수 있습니다 파종되었습니다. 파종된 경우에는 난수를 직접 생성하고, 그렇지 않으면 php_mt_srand를 호출하여 파종합니다. 즉, 각 php cgi 프로세스 중에 mt_rand()에 대한 첫 번째 호출만 자동으로 시드됩니다. 다음으로, 처음으로 뿌려진 시드를 기반으로 난수가 생성됩니다. CGI를 제외한 php의 여러 작동 모드 중(각 요청은 cgi 프로세스를 시작하고 요청이 완료된 후 닫힙니다. php.ini 환경 변수는 매번 다시 읽어야 하므로 효율성이 낮으며, 그렇게 해서는 안 됩니다. 현재 많이 사용됨) 기본적으로 하나의 프로세스가 요청을 처리한 후 대기는 다음 프로세스를 기다리며 여러 요청이 처리될 때까지 재활용되지 않습니다(타임아웃 후에도 재활용됨). intest 테스트를위한 스크립트를 작성합니다. 하나의 프로세스에 의해 처리되는 요청 수, 자동 시딩에 대한 지금의 결론을 검증해 보겠습니다.

<?php
//pid.php
echo getmypid();
로그인 후 복사


<?php
//test.php
$old_pid = file_get_contents(&#39;http://localhost/pid.php&#39;);
$i=1;
while(true){
 $i++;
 $pid = file_get_contents(&#39;http://localhost/pid.php&#39;);
 if($pid!=$old_pid){
 echo $i;
 break;
 }
}
로그인 후 복사


<?php
//pid1.php
if(isset($_GET[&#39;rand&#39;])){
 echo mt_rand();
}else{
 echo getmypid();
}
로그인 후 복사

pid로 판단하면 새 프로세스가 시작되면 다음 중 하나의 mt_rand를 무작위로 얻습니다. 두 페이지( ) 출력:

<?php
//pid2.php
echo mt_rand();
로그인 후 복사

첫 번째 난수 1513334371을 선택하여 씨앗을 폭발시키세요:

<?php
//test.php
$old_pid = file_get_contents(&#39;http://localhost/pid1.php&#39;);
echo "old_pid:{$old_pid}\r\n";
while(true){
 $pid = file_get_contents(&#39;http://localhost/pid1.php&#39;);
 if($pid!=$old_pid){
 echo "new_pid:{$pid}\r\n";
 for($i=0;$i<20;$i++){
  $random = mt_rand(1,2);
  echo file_get_contents("http://localhost/pid".$random.".php?rand=1")." ";
 }

 break;
 }
}
로그인 후 복사

3개의 가능한 씨앗을 폭발시켰습니다. 숫자는 하나씩 수동으로 테스트하세요.


old_pid:972 new_pid:7752 1513334371 2014450250 1319669412 499559587 117728762 1465174656 1671827592 1703046841 464496438 1974338231 46646067 981271768 1070717272 571887250 922467166 606646473 134605134 857256637 1971727275 2104203195
로그인 후 복사

출력:

처음 20자리는 위 스크립트에서 얻은 숫자와 정확히 동일합니다. 확인된 시드는 1513334371입니다. 시드를 사용하면 mt_rand()를 여러 번 호출하여 생성된 난수를 계산할 수 있습니다. 예를 들어 이 스크립트에서는 21자리 숫자를 생성했는데 마지막 숫자는 1515656265입니다. 지금 스크립트를 실행한 후 해당 사이트를 방문하지 않았다면 http://localhost/pid2.php를 열어도 동일한 1515656265를 보실 수 있습니다.

그래서 우리는 결론을 내렸습니다:


php의 자동 시딩은 php cgi 프로세스에서 mt_rand()가 처음으로 호출될 때 발생합니다. 방문한 페이지에 관계없이 요청이 동일한 프로세스에 의해 처리되는 한 처음에 자동으로 뿌려진 동일한 시드를 공유합니다.

php_mt_seed

우리는 난수 생성이 특정 함수에 따라 다르다는 것을 이미 알고 있습니다. 위의 가정은 rand = seed+(i*10)  。对于这样一个简单的函数,我们当然可以直接计算(口算)出一个(组)解来,但 mt_rand() 实际使用的函数可是相当复杂且无法逆运算的。有效的破解方法其实是穷举所有的种子并根据种子生成随机数序列再跟已知的随机数序列做比对来验证种子是否正确。php_mt_seed^phpmtseed就是这么一个工具,它的速度非常快,跑完2^32位seed也就几分钟。它可以根据单次mt_rand()的输出结果直接爆破出可能的种子(上面有示例),当然也可以爆破类似mt_rand(1,100)이것은 MIN MAX 출력의 시드를 제한합니다(아래 예에서 사용됨).

보안 문제

그렇게 많이 말했는데 왜 난수는 안전하지 않습니까? 실제로 기능 자체에는 아무런 문제가 없으며, 생성된 난수를 보안 암호화 목적으로 사용해서는 안 된다는 점을 관계자도 분명히 밝혔습니다(중국어 버전 매뉴얼에는 이런 내용이 적혀있지 않지만). 문제는 개발자가 이것이 진정한 난수가 아니라는 것을 깨닫지 못한다는 것입니다. 우리는 알려진 일련의 난수에서 씨앗이 폭발할 수 있다는 것을 이미 알고 있습니다. 즉, 임의의 페이지에 출력 난수 또는 그 파생 값(가역적 난수 값)이 있는 한 다른 페이지의 난수는 더 이상 "난수"가 아닙니다. 난수 출력의 일반적인 예로는 인증 코드, 임의의 파일 이름 등이 있습니다. 암호화 키 등 비밀번호 확인 값을 가져오는 등 보안 확인을 위해 일반적인 난수를 사용합니다. 이상적인 공격 시나리오:

한밤 중에 아파치(nginx)가 모든 PHP 프로세스를 다시 가져오기를 기다리고(다음 방문 시 다시 시드되도록 하기 위해) 확인 코드 페이지를 한 번 방문하고 인증번호 문자를 기준으로 난수를 생성한 후, 난수를 기준으로 폭발 난수 시드를 생성합니다. 그런 다음 비밀번호 검색 페이지를 방문하면 생성된 비밀번호 검색 링크는 임의의 숫자를 기반으로 합니다. 우리는 이 링크를 쉽게 계산하고 관리자 비밀번호를 검색할 수 있습니다... XXOO

Example

PHPCMS MT_RAND SEED CRACK이 인증 키 유출을 유발합니다. Yuniu가 저보다 글을 더 잘 씁니다. 그의

Discuz x3.2 인증 키 유출을 보세요. 이것은 실제로 비슷합니다. 공식 패치가 공개되었으며, 관심 있는 분들은 직접 분석해 보시기 바랍니다.

요약

위 내용은 PHP의 mt_rand() 난수 보안에 대한 심층적인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿