Skip to content

Predictable Security Tokens Generated with UUID v1

Moderate
simlarsen published GHSA-f5j9-g76m-vvp9 Mar 12, 2026

Package

npm oneuptime (npm)

Affected versions

<= 10.0.22

Patched versions

10.0.23

Description

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

  1. Attacker requests password reset for victim: POST /identity/forgot-password
  2. Attacker notes the exact time of the request
  3. Attacker obtains the server's node ID from any visible UUID (resource IDs, request IDs, etc.)
  4. Attacker generates candidate reset tokens for the ~1 second window around the request time
  5. 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
}

Severity

Moderate

CVE ID

CVE-2026-32307

Weaknesses

Use of Insufficiently Random Values

The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers. Learn more on MITRE.

Credits