Skip to content

Donor Stub Account Takeover via Brute-Forceable Email Confirmation Token

Moderate
mlissner published GHSA-638g-xf9h-6qcg Aug 15, 2026

Package

courtlistener (git)

Affected versions

main

Patched versions

None

Description

Summary

sha1_activation_key() derived email-confirmation tokens from a 20-bit salt (sha1(str(random.random()))[:5]) combined with an attacker-known value (the attacker's own chosen username, or the victim's known email address).

Because the confirmation endpoint (GET /email/confirm/<key>/) was completely unauthenticated, unthrottled, and returns a response that distinguishes valid from invalid keys, an attacker could register using a donor's email address to upgrade their pre-existing "stub" account, then brute-force the resulting 2^20 keyspace online (~524,288 requests on average) to confirm the account and log in as the victim.

Our logs indicate no bulk crawling of this endpoint over the past three years, giving us high-confidence this wasn't exploited during that time.

Severity

Medium (CVSS 3.1: 6.5)

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

  • Attack Vector: Network — entirely over unauthenticated HTTP requests to public endpoints.
  • Attack Complexity: Low — no race condition, no MITM, no privileged vantage point required; a straightforward, deterministic online brute-force script against an endpoint with zero rate limiting.
  • Privileges Required: None — both register and confirm_email are reachable by anonymous users.
  • User Interaction: None — the victim (donor) need not click anything or take any action.
  • Scope: Unchanged.
  • Confidentiality Impact: Low. A taken-over stub account exposes only a mailing address. It does not expose a name (create_stub_account() has a separate bug that dropped first_name/last_name before they're ever saved), donation history (the account's donations page only links out to Neon's separately-authenticated donor portal — it renders no transaction data itself), or a pre-existing API token (a stub account, never having been logged into before, never generated one; an attacker could only mint a new one under the hijacked identity).
  • Integrity Impact: Low. The attacker does gain the ability to log in and to change the account's email/password, permanently displacing the original donor from that record — but there's essentially no other legitimate data on a bare stub account for them to corrupt and such accounts could be restored by customer support.
  • Availability Impact: None.

CWE

  • CWE-330: Use of Insufficiently Random Values (20-bit token entropy from a non-CSPRNG source)
  • CWE-307: Improper Restriction of Excessive Authentication Attempts (no rate limiting on confirm_email)
  • CWE-203: Observable Discrepancy (the confirm.html template renders distinguishable content for invalid vs. valid/expired keys, enabling an online oracle)

Execution chain

  1. Attacker POST /register/ with email=<victim_donor_email>, username=<attacker_username>, password1/2=<attacker_password> (solves hCaptcha once). The existing donor stub account is upgraded in place; activation_key = sha1(salt + attacker_username) is stored with a 5-day expiry.
  2. Attacker precomputes all 2^20 candidate keys offline: sha1(f"{salt:05x}" + attacker_username) for salt in 0..0xFFFFF.
  3. Attacker sends GET /email/confirm/<candidate_key>/ for each candidate, unthrottled. On average ~524,288 requests are needed before the response body is no longer the "invalid" template.
  4. The matching request sets email_confirmed = True on the (now attacker-controlled) account.
  5. Attacker POST /sign-in/ with their chosen username/password — succeeds because is_active=True and email_confirmed=True. The attacker now controls the account — including the mailing address it holds — and can change its email/password, permanently locking the original donor out of their own record.

Impact

  • Full takeover of any donor stub account whose email address is known to the attacker (donor emails are frequently obtainable — public filings, leaked lists, direct knowledge of a specific target).
  • No exposure of donation history nor donor name.
  • Exposure of donor address.
  • No CAPTCHA, no rate limit, and no anomaly detection stands between the attacker and success — the attack is fully automatable and requires no elevated network position.

Remediation

  • sha1_activation_key() is now replaced with a CSPRNG token (e.g. secrets.token_hex(20).
  • confirm_email is now rate-limited per IP.
  • activation_keys are now invalidated immediately after a successful confirmation so keys are single-use.

Credit

This vulnerability was discovered and reported by bugbunny.ai.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

CVE ID

No known CVE

Weaknesses

No CWEs

Credits