Skip to content

CWE-330: Use of Insufficiently Random Values - C# / .NET

Overview

System.Random is a seeded pseudo-random number generator (PRNG) that produces a deterministic sequence of values. Given knowledge of the seed - or a few observed outputs - the entire sequence can be reconstructed. This makes it unsuitable for any security-sensitive purpose such as session tokens, password reset tokens, API keys, OTP codes, or cryptographic nonces.

The seed for new Random() (without an explicit seed) defaults to Environment.TickCount, which is the number of milliseconds since system boot. An attacker who knows the approximate time the server started, or who can make many requests to narrow down the seed, can predict all generated tokens. Random.Shared suffers from the same property.

Primary Defence: Replace all System.Random usage in security contexts with System.Security.Cryptography.RandomNumberGenerator, which sources entropy from the operating system's cryptographic provider (CNG on Windows, /dev/urandom on Linux/macOS).

Common Vulnerable Patterns

Token Generation with System.Random

// VULNERABLE - System.Random produces predictable values
private static readonly Random _random = new Random();

public string GeneratePasswordResetToken()
{
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    return new string(Enumerable.Repeat(chars, 32)
        .Select(s => s[_random.Next(s.Length)])
        .ToArray());
}

Why this is vulnerable:

  • System.Random is seeded from Environment.TickCount. An attacker who approximates the server's start time can brute-force the seed in minutes and predict the reset token before the legitimate user uses it.

OTP Generation with Random.Shared

// VULNERABLE - shared Random instance still uses the same weak algorithm
public int GenerateOtp()
{
    return Random.Shared.Next(100_000, 1_000_000); // 6-digit OTP
}

Why this is vulnerable:

  • Random.Shared is a static convenience property that shares a single System.Random instance. It is thread-safe, but the underlying algorithm is still deterministic. Observing a sequence of OTP values allows prediction of future values.

Guid as Security Token

// VULNERABLE - Guid.NewGuid() is not a cryptographically secure random source
public string GenerateSessionId()
{
    return Guid.NewGuid().ToString("N"); // e.g., "d8e8fca2-dc0f-4b57-9dc1-b57f9bb70eac"
}

Why this is vulnerable:

  • UUID v4 (used by Guid.NewGuid() on .NET) does use some random bits, but the format reserves fixed bits for version and variant fields, reducing the entropy. The standard does not guarantee cryptographic security properties.

Secure Patterns

256-bit URL-Safe Token (.NET 6+)

using System.Security.Cryptography;

public static string GenerateToken()
{
    // SECURE: 32 bytes = 256 bits of OS-sourced entropy
    byte[] bytes = RandomNumberGenerator.GetBytes(32);
    // URL-safe Base64 encoding (no padding)
    return Convert.ToBase64String(bytes)
        .TrimEnd('=')
        .Replace('+', '-')
        .Replace('/', '_');
}

Why this works:

  • RandomNumberGenerator.GetBytes() calls the OS cryptographic provider, which uses hardware entropy and a CSPRNG. The output is not predictable regardless of when it was generated.

Hex Token (.NET 5+)

using System.Security.Cryptography;

public static string GenerateHexToken(int byteLength = 32)
{
    // SECURE: hex encoding of 32 cryptographically random bytes = 64-char hex string
    byte[] bytes = RandomNumberGenerator.GetBytes(byteLength);
    return Convert.ToHexString(bytes).ToLowerInvariant();
}

Why this works:

  • Hex tokens are easier to store in databases (no special character handling). 32 bytes provides 256 bits of entropy, far exceeding the minimum of 128 bits recommended by NIST SP 800-132.

6-Digit OTP (.NET 6+)

using System.Security.Cryptography;

public static int GenerateOtp()
{
    // SECURE: cryptographically random integer in [100000, 999999]
    return RandomNumberGenerator.GetInt32(100_000, 1_000_000);
}

Why this works:

  • RandomNumberGenerator.GetInt32(fromInclusive, toExclusive) uses rejection sampling to produce an unbiased random integer within the specified range, sourced from the OS CSPRNG.

Legacy .NET Framework / .NET 5 Fallback

using System.Security.Cryptography;

public static string GenerateTokenLegacy()
{
    // SECURE: works on .NET Framework and .NET 5
    using var rng = RandomNumberGenerator.Create();
    byte[] buffer = new byte[32];
    rng.GetBytes(buffer);
    return Convert.ToBase64String(buffer)
        .TrimEnd('=')
        .Replace('+', '-')
        .Replace('/', '_');
}

Why this works:

  • RandomNumberGenerator.Create() returns the platform's CSPRNG implementation. This pattern is compatible with older .NET targets where the static helper methods are unavailable.

Remediation Steps

Locate the Finding

  • Source: Token generation, OTP creation, session ID generation, key generation
  • Sink: new Random(), Random.Shared.Next(), Guid.NewGuid() used as security identifiers

Apply the Fix

  • PRIORITY 1: Replace new Random() and Random.Shared with RandomNumberGenerator in all security contexts
  • PRIORITY 2: Replace Guid.NewGuid().ToString() used as session tokens with RandomNumberGenerator.GetHexString(32)
  • PRIORITY 3: Keep System.Random only for non-security uses (simulations, shuffling non-sensitive lists)

Verify the Fix

  • Generate 100 tokens and confirm no two are identical and none follow an obvious pattern
  • Run the scanner again to confirm System.Random is no longer flagged in security contexts
  • Rescan with the security scanner to confirm the finding is resolved

Check for Similar Issues

Search for: new Random(), Random.Shared, Guid.NewGuid() in token/session/key generation paths

Testing

  • Normal input: generate session tokens, reset tokens, OTPs, and API keys through the application paths that use the new generator.
  • Boundary input: request minimum and maximum supported token lengths and confirm encoding, storage, and URL handling still work.
  • Malicious input: attempt to predict or replay several generated values; verify old deterministic seeds and System.Random paths are gone.

Additional Resources