不安全的随机数 (Insecure Randomness)
不安全的随机数是指计算中与随机数生成相关的弱点,特别是当这种随机性被用于安全关键目的时。随机数生成器 (RNG) 中的漏洞可能导致可预测的输出,攻击者可以利用这些输出,从而导致潜在的数据泄露或未经授权的访问。
摘要 (Summary)
- 方法论 (#methodology)
- 基于时间的种子 (#time-based-seeds)
- GUID / UUID (#guid--uuid)
- Mongo ObjectId (#mongo-objectid)
- Uniqid (#uniqid)
- mt_rand (#mt_rand)
- 自定义算法 (#custom-algorithms)
- 参考资料 (#references)
方法论 (Methodology)
当随机源或生成随机值的方法不够不可预测时,就会出现不安全的随机数。这可能导致可预测的输出,攻击者可以利用这些输出。下面,我们检查一些容易产生不安全随机数的常见方法,包括基于时间的种子、GUID、UUID、MongoDB ObjectId 以及 uniqid() 函数。
基于时间的种子 (Time-Based Seeds)
许多随机数生成器 (RNG) 使用当前系统时间(例如,自纪元以来的毫秒数)作为种子。这种方法可能是不安全的,因为种子值很容易被预测,特别是在自动化或脚本环境中。
RNG 使用当前时间作为种子,这使得任何知道或可以估计种子值的人都可以预测它。
通过知道确切的时间,攻击者可以重新生成正确的随机值,这里有一个日期为 2024-11-10 13:37 的示例。
import random
import time
# 基于提供的字符串时间戳设置种子
seed = int(time.mktime(time.strptime('2024-11-10 13:37', '%Y-%m-%d %H:%M')))
random.seed(seed)
# 生成随机数
print(random.randint(1, 100))
GUID / UUID
GUID (全局唯一标识符) 或 UUID (通用唯一标识符) 是一个 128 位数字,用于在计算机系统中唯一地标识信息。它们通常表示为一串十六进制数字,分为五组,由连字符分隔,例如 550e8400-e29b-41d4-a716-446655440000。GUID/UUID 旨在跨空间和时间保持唯一,即使由不同的系统系统或在不同时间生成,也能降低重复的可能性。
GUID 版本 (GUID Versions)
版本标识:xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
四位的 M 和 1 到 3 位的 N 字段编码了 UUID 本身的格式。
| 版本 | 备注 |
|---|---|
| 0 | 仅限 00000000-0000-0000-0000-000000000000 |
| 1 | 基于时间或时钟序列 |
| 2 | 在 RFC 4122 中保留,但在许多实现中被省略 |
| 3 | 基于 MD5 哈希 |
| 4 | 随机生成 |
| 5 | 基于 SHA1 哈希 |
工具 (Tools)
-
intruder-io/guidtool - 一个检查和攻击版本 1 GUID 的工具
$ guidtool -i 95f6e264-bb00-11ec-8833-00155d01ef00 UUID version: 1 UUID time: 2022-04-13 08:06:13.202186 UUID timestamp: 138691299732021860 UUID node: 91754721024 UUID MAC address: 00:15:5d:01:ef:00 UUID clock sequence: 2099 $ guidtool 1b2d78d0-47cf-11ec-8d62-0ff591f2a37c -t '2021-11-17 18:03:17' -p 10000
Mongo ObjectId
Mongo ObjectId 是以可预测的方式生成的,12 字节的 ObjectId 值由以下部分组成:
- 时间戳 (Timestamp) (4 字节):代表 ObjectId 的创建时间,以自 Unix 纪元(1970年1月1日)以来的秒数衡量。
- 机器标识符 (Machine Identifier) (3 字节):标识生成 ObjectId 的机器。通常源自机器的主机名或 IP 地址,使得在同一台机器上创建的文档具有可预测性。
- 进程 ID (Process ID) (2 字节):标识生成 ObjectId 的进程。通常是 MongoDB 服务器进程的进程 ID,使得由同一进程创建的文档具有可预测性。
- 计数器 (Counter) (3 字节):一个唯一的计数器值,针对生成的每个新 ObjectId 递增。进程启动时初始化为随机值,但随后的值是可预测的,因为它们是按顺序生成的。
令牌示例
5ae9b90a2c144b9def01ec37,5ae9bac82c144b9def01ec39
工具 (Tools)
-
andresriancho/mongo-objectid-predict - 预测 Mongo ObjectId
-
用于恢复
时间戳 (timestamp),进程 (process)和计数器 (counter)的 Python 脚本def MongoDB_ObjectID(timestamp, process, counter): return "%08x%10x%06x" % ( timestamp, process, counter, ) def reverse_MongoDB_ObjectID(token): timestamp = int(token[0:8], 16) process = int(token[8:18], 16) counter = int(token[18:24], 16) return timestamp, process, counter def check(token): (timestamp, process, counter) = reverse_MongoDB_ObjectID(token) return token == MongoDB_ObjectID(timestamp, process, counter) tokens = ["5ae9b90a2c144b9def01ec37", "5ae9bac82c144b9def01ec39"] for token in tokens: (timestamp, process, counter) = reverse_MongoDB_ObjectID(token) print(f"{token}: {timestamp} - {process} - {counter}")
Uniqid
使用 uniqid 派生的令牌是基于时间戳的,因此可以被逆向。
- Riamse/python-uniqid 是基于时间戳的
- php/uniqid
令牌示例
- uniqid:
6659cea087cd6,6659cea087cea - sha256(uniqid):
4b26d474c77daf9a94d82039f4c9b8e555ad505249437c0987f12c1b80de0bf4,ae72a4c4cdf77f39d1b0133394c0cb24c33c61c4505a9fe33ab89315d3f5a1e4
工具 (Tools)
import math
import datetime
def uniqid(timestamp: float) -> str:
sec = math.floor(timestamp)
usec = round(1000000 * (timestamp - sec))
return "%8x%05x" % (sec, usec)
def reverse_uniqid(value: str) -> float:
sec = int(value[:8], 16)
usec = int(value[8:], 16)
return float(f"{sec}.{usec}")
tokens = ["6659cea087cd6" , "6659cea087cea"]
for token in tokens:
t = float(reverse_uniqid(token))
d = datetime.datetime.fromtimestamp(t)
print(f"{token} - {t} => {d}")
mt_rand
通过两个输出值且无需暴力破解即可破解 mt_rand()。
- ambionics/mt_rand-reverse - 仅使用两个输出且无需任何暴力破解即可恢复 mt_rand() 种子的脚本。
./display_mt_rand.php 12345678 123
712530069 674417379
./reverse_mt_rand.py 712530069 674417379 123 1
自定义算法 (Custom Algorithms)
通常不建议创建自己的随机数算法。下面是一些在 GitHub 或 StackOverflow 上发现的示例,它们有时被用于生产环境,但可能不可靠或不安全。
$token = md5($emailId).rand(10,9999);$token = md5(time()+123456789 % rand(4000, 55000000));
工具 (Tools)
通用识别和三明治攻击 (Sandwich Attack):
-
AethliosIK/reset-tolkien - 不安全的基于时间的秘密利用和三明治攻击实现资源
参考资料 (References)
- 通过 2 个值且无需暴力破解即可破解 PHP 的 mt_rand() - Charles Fol - 2020年1月6日
- 破解基于时间的令牌:来自 leHACK 2025-Singularity 期间研讨会的见证 - 4m1d0n - 2025年6月30日
- 利用 PHP 的 rand 和 srand 函数中薄弱的伪随机数生成 - Jacob Moore - 2023年10月18日
- 通过 MongoDB ObjectId 预测实现 IDOR - Amey Anekar - 2020年8月25日
- 我们信任 GUID - Daniel Thatcher - 2022年10月11日
- 基于 MongoDB ObjectId 的多重三明治攻击或 Web 应用程序邀请实时监控场景:三明治攻击的新用例 - Tom CHAMBARETAUD (@AethliosIK) - 2024年7月18日
- Secret basé sur le temps non sécurisé et attaque par sandwich - Analyse de mes recherches et publication de l’outil “Reset Tolkien” - Tom CHAMBARETAUD (@AethliosIK) - 2024年4月2日 (法语)
- 不安全的基于时间的秘密和三明治攻击 - 我的研究分析和 “Reset Tolkien” 工具发布 - Tom CHAMBARETAUD (@AethliosIK) - 2024年4月2日 (英语)