Predictable Security Tokens Generated with UUID v1
Summary
OneUptime generates password reset tokens, email verification tokens, and various security-sensitive identifiers using UUID v1 (uuid.v1()), which produces values based on the current timestamp and server MAC address. These tokens are predictable if an attacker knows the approximate generation time, enabling password reset token guessing and account takeover.
Affected Component
- File:
Common/Utils/UUID.ts
- Lines: 1-7
- Used by:
Common/Types/ObjectID.ts:51-53 (ObjectID.generate())
- Versions: OneUptime <= 10.0.22
Vulnerable Code
// Common/Utils/UUID.ts
import { v1 as uuidv1 } from "uuid";
export default class UUID {
public static generate(): string {
return uuidv1();
}
}
// Common/Types/ObjectID.ts
public static generate(): ObjectID {
return new this(UUID.generate());
}
Where Security Tokens Are Generated
Password reset flow (Authentication.ts:340):
const token: string = ObjectID.generate().toString();
// ... saved as resetPasswordToken
Email verification (Authentication.ts:236):
const emailVerificationToken: ObjectID = ObjectID.generate();
UUID v1 Structure
UUID v1 format: xxxxxxxx-xxxx-1xxx-yxxx-xxxxxxxxxxxx
| Component |
Bits |
Source |
| time_low |
32 |
Current timestamp (100ns intervals since 1582-10-15) |
| time_mid |
16 |
Current timestamp (continued) |
| time_hi_and_version |
16 |
Timestamp + version nibble (always 1) |
| clock_seq |
14 |
Counter (increments on clock regression) |
| node |
48 |
Server MAC address (or random if no MAC) |
Key weakness: 60 bits of the 122 variable bits come from the timestamp. If the attacker knows when the password reset was requested (within a few seconds), they only need to guess:
- ~10 bits of sub-second precision
- 14 bits of clock sequence
- 48 bits of node (static per server, can be learned from any other UUID)
Proof of Concept
import uuid
import struct
import datetime
def extract_timestamp_from_uuid1(uuid_str):
"""Extract the timestamp from a UUID v1."""
u = uuid.UUID(uuid_str)
# UUID v1 timestamp is 100-nanosecond intervals since 1582-10-15
timestamp = u.time
epoch = datetime.datetime(1582, 10, 15)
delta = datetime.timedelta(microseconds=timestamp // 10)
return epoch + delta
def extract_node(uuid_str):
"""Extract the MAC/node from a UUID v1."""
u = uuid.UUID(uuid_str)
return hex(u.node)
# If attacker can obtain ANY UUID from the system (e.g., from a resource ID
# visible in API responses), they learn the node (MAC address)
known_uuid = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
node = extract_node(known_uuid)
# With the node known and approximate timestamp, brute-force is feasible:
# - Timestamp window: ~1 second = 10,000,000 possibilities (100ns intervals)
# - Clock sequence: 16,384 possibilities (14 bits)
# Total: ~163 billion combinations
# At 1M guesses/sec against API: ~163,000 seconds (impractical over network)
# But: clock_seq rarely changes, often 0 or a small value
# Realistic: ~10M timestamp variants * ~10 clock_seq values = 100M guesses
Practical Attack Scenario
- Attacker requests password reset for victim:
POST /identity/forgot-password
- Attacker notes the exact time of the request
- Attacker obtains the server's node ID from any visible UUID (resource IDs, request IDs, etc.)
- Attacker generates candidate reset tokens for the ~1 second window around the request time
- Attacker tries candidates against
POST /identity/reset-password
Impact
- Severity: MEDIUM (CVSS ~5.9)
- Type: CWE-330 (Use of Insufficiently Random Values)
- Password reset tokens are guessable if:
- Attacker knows approximate generation time (triggered by attacker, so exact time known)
- Server's MAC/node can be learned from any exposed UUID
- Email verification tokens are similarly predictable
- Session IDs (ObjectID-based) are predictable
Suggested Fix
Replace UUID v1 with crypto.randomUUID() (UUID v4) or crypto.randomBytes():
import crypto from 'crypto';
export default class UUID {
public static generate(): string {
return crypto.randomUUID(); // UUID v4 — 122 bits of cryptographic randomness
}
}
Or for maximum entropy:
public static generateToken(): string {
return crypto.randomBytes(32).toString('hex'); // 256 bits
}
Predictable Security Tokens Generated with UUID v1
Summary
OneUptime generates password reset tokens, email verification tokens, and various security-sensitive identifiers using UUID v1 (
uuid.v1()), which produces values based on the current timestamp and server MAC address. These tokens are predictable if an attacker knows the approximate generation time, enabling password reset token guessing and account takeover.Affected Component
Common/Utils/UUID.tsCommon/Types/ObjectID.ts:51-53(ObjectID.generate())Vulnerable Code
Where Security Tokens Are Generated
Password reset flow (
Authentication.ts:340):Email verification (
Authentication.ts:236):UUID v1 Structure
UUID v1 format:
xxxxxxxx-xxxx-1xxx-yxxx-xxxxxxxxxxxx1)Key weakness: 60 bits of the 122 variable bits come from the timestamp. If the attacker knows when the password reset was requested (within a few seconds), they only need to guess:
Proof of Concept
Practical Attack Scenario
POST /identity/forgot-passwordPOST /identity/reset-passwordImpact
Suggested Fix
Replace UUID v1 with
crypto.randomUUID()(UUID v4) orcrypto.randomBytes():Or for maximum entropy: