This repository has been archived on 2026-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-01-21 13:12:04 -05:00
..
2026-01-21 13:12:04 -05:00

Weak PRNG for Security Tokens

Summary

BDServer uses Python's random module to generate session tokens, email verification codes, and other security-sensitive values. The random module uses the Mersenne Twister (MT19937) algorithm, which is not cryptographically secure. In theory, an attacker who observes enough consecutive PRNG outputs can recover the internal state and predict future tokens. In practice, several factors make this attack difficult to execute.

Vulnerability Details

Property Value
Type Use of Cryptographically Weak PRNG
CVSS 7.4 (High)
CWE CWE-338 (Use of Cryptographically Weak PRNG)

Root Cause

The RandString() function generates random strings using random.choice():

import random

def RandString(n=50):
    # this will make a random string
    s = ""
    good = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"

    for i in range(n):
        s = s + random.choice(good)

    return s

This function is used for security-sensitive purposes throughout the codebase:

Usage Location Length
Session tokens Render.py:67 200 chars
Email verification Email.py:102, Email.py:204 10 chars
Invite codes Render.py:5179 50 chars
Private key password ActivityPub.py:124 200 chars

The Theoretical Attack

MT19937 maintains 624 32-bit integers as its internal state. Each time the PRNG generates a number, it transforms one of these integers through a series of bitwise operations before outputting it. These operations are reversible: given an output, you can compute the original state integer.

If an attacker collects 624 consecutive outputs, they can reverse each one to recover the full internal state. With the state recovered, they can clone the PRNG and predict all future outputs.

Each call to random.choice() consumes PRNG state, so a 200-character token requires 200 calls. The exact number of underlying MT19937 outputs consumed depends on Python's implementation details and rejection sampling, but collecting several consecutive tokens would provide enough data for state recovery.

Practical Difficulties

Several factors make this attack harder to execute in the wild:

Other PRNG consumers: The codebase has multiple other calls to random that consume PRNG state:

Location Function When Called
Common.py:311-313 IDcolor() Every new session (3 calls)
Render.py:362 random.choice() Article selection
Render.py:383, Render.py:458 random.uniform() Content scoring (L383 uses alsorandom, an alias for random)
ActivityPub.py:185 random.uniform() Federation timing
Analyse.py:554 random.random() User sorting

Any request that triggers these functions will consume PRNG outputs, breaking the "consecutive outputs" requirement.

Rejection sampling uncertainty: When random.choice() selects from 36 characters, it generates random numbers and discards any that fall outside the valid range. The attacker observes only the characters that were selected, not the discarded values. This creates uncertainty when trying to map observed tokens back to the underlying PRNG outputs, requiring specialized constraint-solving software to work through the possibilities.

Server restarts: Any server restart reseeds the PRNG, invalidating collected data.

Timing requirements: The attacker must collect tokens, perform state recovery, and predict the next token before another user or background process consumes PRNG state.

Realistic Assessment

On a low-traffic BDServer instance with no other activity, this attack is feasible. On an active server with multiple users and background processes, unpredictable PRNG state consumption makes the attack significantly harder.

The vulnerability represents a real cryptographic weakness, but practical exploitation requires favorable conditions.

Reproducing the Vulnerability

In a controlled environment with direct access to the PRNG, state recovery works reliably: collect 624 outputs, reverse the bitwise transformations on each, and use the recovered state to predict future values.

Against a live BDServer instance, the practical difficulties described above (other PRNG consumers, rejection sampling, timing) make successful exploitation unlikely without custom tooling and favorable conditions.

Remediation

Replace random with secrets in Common.py:

import secrets
import string

def RandString(n=50):
    alphabet = string.ascii_letters + string.digits
    return ''.join(secrets.choice(alphabet) for _ in range(n))

The secrets module uses os.urandom(), a cryptographically secure random source with no recoverable state.

Disclaimer

This assessment was performed on a best-effort basis against BDServer commit dc86854 and reflects the state of the software at the time of testing. The findings and remediations are provided for informational purposes and should be independently validated before implementation. This report does not guarantee all vulnerabilities have been identified, nor does it guarantee the suggested fixes will be effective in all environments.