mt_rand()는 mersennetwister 알고리즘을 사용하여 임의의 정수를 반환합니다. 모두가 알고 있지만 다음 기사에서는 주로 PHP의 mt_rand() 난수 보안에 대한 관련 정보를 소개합니다. 누가 필요하면 참고하세요
머리말
저는 얼마 전에 mt_rand()와 관련된 보안 취약점을 많이 발견했는데, 이는 기본적으로 난수 사용에 대한 오해에서 비롯되었습니다. 여기에서는 PHP 공식 웹사이트의 매뉴얼에 있는 또 다른 함정에 대해 언급하고 싶습니다. mt_rand()의 소개를 살펴보겠습니다. 중국어 버전 ^cn 영어 버전 ^en을 보면 영어 버전이 노란색으로 표시되어 있는 것을 볼 수 있습니다. 주의 경고
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 환경 변수는 매번 다시 읽어야 하므로 효율성이 낮으며, 그렇게 해서는 안 됩니다. 현재 많이 사용됨) 기본적으로 하나의 프로세스가 요청을 처리한 후 대기 프로세스는 다음 프로세스를 기다리며 여러 요청이 처리될 때까지 재활용되지 않습니다(시간 초과 후에도 재활용됩니다).
테스트할 스크립트 작성
<?php //pid.php echo getmypid();
<?php //test.php $old_pid = file_get_contents('http://localhost/pid.php'); $i=1; while(true){ $i++; $pid = file_get_contents('http://localhost/pid.php'); if($pid!=$old_pid){ echo $i; break; } }
테스트 결과: (windows+phpstudy)
apache 1000 요청
nginx 500 요청
물론 이 테스트는 Apache와 ng inx a process 처리할 수 있는 요청 수, 자동 시드에 대한 결론을 지금 확인해 보겠습니다.
<?php //pid1.php if(isset($_GET['rand'])){ echo mt_rand(); }else{ echo getmypid(); }
<?php //pid2.php echo mt_rand();
<?php //test.php $old_pid = file_get_contents('http://localhost/pid1.php'); echo "old_pid:{$old_pid}\r\n"; while(true){ $pid = file_get_contents('http://localhost/pid1.php'); 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; } }
pid로 판단하면 새 프로세스가 시작되면 두 페이지가 됩니다. 무작위로 획득 mt_rand() 중 하나의 출력:
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
첫 번째 난수 1513334371을 사용하여 씨앗을 폭파합니다.
smldhz@vm:~/php_mt_seed-3.2$ ./php_mt_seed 1513334371 Found 0, trying 704643072 - 738197503, speed 28562751 seeds per second seed = 735487048 Found 1, trying 1308622848 - 1342177279, speed 28824291 seeds per second seed = 1337331453 Found 2, trying 3254779904 - 3288334335, speed 28811010 seeds per second seed = 3283082581 Found 3, trying 4261412864 - 4294967295, speed 28677071 seeds per second Found 3
3개의 가능한 씨앗을 폭발시켰습니다. 숫자는 매우 작으며 하나를 수동으로 테스트했습니다. by one :
<?php mt_srand(735487048);//手工播种 for($i=0;$i<21;$i++){ echo mt_rand()." "; }
출력:
처음 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!