September 26, 2026
Predictable Account-Activation Token: Email-Ownership Verification Bypass
Email verification is supposed to establish one simple security property:
By redhunter01
7 min read
The person creating the account controls the email address associated with it.
During security research on a live venue-account platform, I discovered that this property could be bypassed because the account-activation token was completely predictable.
The activation token was not random, server-secret-derived, or tied to a server-side secret.
Instead, it was deterministically generated as:
activation_token = SHA1(lowercase(email_address))activation_token = SHA1(lowercase(email_address))Because the email address is already known to the attacker, the activation token could be calculated entirely offline.
The companion identifier required to redeem the token was also returned directly by the public registration API.
This created a complete email-verification bypass:
Attacker knows email
โ
Register pending account
โ
Receive membership identifier
โ
Calculate activation token locally
โ
Redeem activation URL
โ
Account becomes activated
โ
Authenticated session issuedAttacker knows email
โ
Register pending account
โ
Receive membership identifier
โ
Calculate activation token locally
โ
Redeem activation URL
โ
Account becomes activated
โ
Authenticated session issuedNo access to the email inbox was required.
The Initial Discovery
The application exposed a public registration endpoint similar to:
POST /api/account/registerPOST /api/account/registerA normal registration request created an account requiring activation.
The response contained a membership identifier:
{
"resultCode": 1,
"membershipGuid": "<membership-guid>",
"communityMemberId": "<member-id>",
"accessToken": null,
"refreshToken": null
}{
"resultCode": 1,
"membershipGuid": "<membership-guid>",
"communityMemberId": "<member-id>",
"accessToken": null,
"refreshToken": null
}The important observation was:
accessToken: nullaccessToken: nullThe account was pending activation.
The response, however, also exposed the identifier required by the activation endpoint.
That led to the next question:
What makes the activation token secret?
Investigating the Activation Token
The activation functionality accepted two important values:
mid = membership identifier
act = activation tokenmid = membership identifier
act = activation tokenI collected multiple server-generated activation tokens using controlled test accounts.
I then compared them against locally calculated SHA-1 values of the corresponding lowercase email addresses.
The relationship was exact:
SHA1(lowercase(email))
=
server-issued activation tokenSHA1(lowercase(email))
=
server-issued activation tokenThis was reproduced across independent server-issued examples.
For example:
Test email
โ
lowercase(email)
โ
SHA-1
โ
40-character hexadecimal value
โ
matches server-issued activation tokenTest email
โ
lowercase(email)
โ
SHA-1
โ
40-character hexadecimal value
โ
matches server-issued activation tokenThis was the critical discovery.
The activation token was not functioning as a secret capability.
It was simply a deterministic transformation of a value already known to the attacker.
Why SHA-1 Does Not Provide Security Here
The problem is not merely that SHA-1 is an old hashing algorithm.
Even replacing SHA-1 with SHA-256 would not fix the underlying design.
For example:
SHA256(lowercase(email))SHA256(lowercase(email))would still be completely reproducible.
The problem is that there is no secret entropy involved.
An attacker who knows:
victim@example.comvictim@example.comcan calculate:
SHA1("victim@example.com")SHA1("victim@example.com")without interacting with the server.
A secure verification token should instead contain unpredictable server-generated entropy.
Conceptually:
Email
+
Random secret
โ
Verification tokenEmail
+
Random secret
โ
Verification tokenrather than:
Email
โ
Deterministic hash
โ
Verification tokenEmail
โ
Deterministic hash
โ
Verification tokenEnd-to-End Proof of Concept
All validation was performed using tester-controlled accounts.
No third-party inbox was accessed and no existing user's active account was modified.
Step 1 โ Register a Pending Account
I created a registration request using a tester-controlled email address and an attacker-selected password.
POST /api/account/register HTTP/2
Content-Type: application/jsonPOST /api/account/register HTTP/2
Content-Type: application/jsonThe request included the email address and:
{
"requiresActivation": true
}{
"requiresActivation": true
}The server returned a pending account together with the membership identifier:
{
"resultCode": 1,
"membershipGuid": "<membership-guid>",
"communityMemberId": "<member-id>",
"accessToken": null,
"refreshToken": null
}{
"resultCode": 1,
"membershipGuid": "<membership-guid>",
"communityMemberId": "<member-id>",
"accessToken": null,
"refreshToken": null
}At this point, no authentication session had been issued.
Step 2 โ Calculate the Activation Token Offline
Using the email address supplied during registration, I calculated:
SHA1(lowercase(email))SHA1(lowercase(email))The resulting 40-character hexadecimal value exactly matched the activation token generated by the server.
Importantly, this calculation required:
No inbox access
No verification email
No additional server request
No secretNo inbox access
No verification email
No additional server request
No secretThe token was generated entirely offline.
Step 3 โ Test an Invalid Token
Before using the calculated value, I tested an intentionally incorrect activation token.
The activation request redirected to the email-verification resend flow rather than activating the account.
This established that the activation parameter was actually being validated.
Step 4 โ Redeem the Offline-Generated Token
I then supplied the calculated token together with the membership identifier.
The application accepted it and redirected to the activated-account flow.
The server issued authentication cookies including:
access_token
refresh_tokenaccess_token
refresh_tokenThe resulting access token represented a real authenticated user principal.
The decoded claims included identifiers corresponding to the newly created account and community.
The important result was:
Pending account
โ
Activation token calculated offline
โ
Activation accepted
โ
Authenticated session issuedPending account
โ
Activation token calculated offline
โ
Activation accepted
โ
Authenticated session issuedAt no point was the email inbox accessed.
What Was Actually Bypassed?
The intended security model was:
Register
โ
Verification email
โ
User receives secret link
โ
User clicks link
โ
Email ownership established
โ
Account activatedRegister
โ
Verification email
โ
User receives secret link
โ
User clicks link
โ
Email ownership established
โ
Account activatedThe vulnerable implementation effectively became:
Register
โ
Know the email
โ
Calculate SHA-1(email)
โ
Construct activation request
โ
Account activatedRegister
โ
Know the email
โ
Calculate SHA-1(email)
โ
Construct activation request
โ
Account activatedThe second flow provides no evidence that the registrant controls the mailbox.
That defeats the purpose of email verification.
Pre-Account-Hijacking Scenario
The more interesting consequence is what happens when the email belongs to someone who has not yet registered an account.
Consider:
victim@example.comvictim@example.comIf the address is not yet registered, an attacker could potentially:
1. Register the email
โ
2. Choose the password
โ
3. Receive membershipGuid
โ
4. Calculate SHA1(email)
โ
5. Activate the account
โ
6. Obtain an authenticated session1. Register the email
โ
2. Choose the password
โ
3. Receive membershipGuid
โ
4. Calculate SHA1(email)
โ
5. Activate the account
โ
6. Obtain an authenticated sessionThe resulting account is associated with the target email address while being controlled by the attacker.
This is a classic pre-account-hijacking scenario involving predictable verification credentials.
The important limitation is that this applies to an email address that has not already been registered.
User Enumeration
The registration endpoint also provided an additional security signal.
Submitting an already-registered email resulted in a distinct response:
400 Bad Request400 Bad Requestwith an error equivalent to:
AlreadyRegisteredAlreadyRegisteredThis creates a reliable registration-status oracle:
Email
โ
POST /api/account/register
โ
AlreadyRegistered?
โโโ Yes โ account exists
โโโ No โ registration can proceedEmail
โ
POST /api/account/register
โ
AlreadyRegistered?
โโโ Yes โ account exists
โโโ No โ registration can proceedThis is separate from the activation-token vulnerability but increases the ability to determine whether an email address is already associated with an account.
A Separate Registration Weakness
During testing, I also observed a related but distinct behavior.
When the client omitted:
requiresActivationrequiresActivationfrom the registration request, the server could automatically activate the account and return an access token immediately.
That means the server did not consistently enforce the email-verification requirement independently of the client-supplied registration state.
This should be treated as a separate weakness from the predictable activation token.
The fundamental issue is:
Security-sensitive verification state
โ
controlled or influenced by client inputSecurity-sensitive verification state
โ
controlled or influenced by client inputVerification requirements should be enforced server-side rather than relying on a client-provided activation flag.
Important Boundaries
To keep the finding precise, several attack scenarios were specifically excluded.
This is not direct takeover of an existing active account
Re-registering an email address that already belongs to an active account was rejected with an AlreadyRegistered response.
Therefore, the predictable activation token does not allow an attacker to simply overwrite the password of an existing active account through registration.
The demonstrated impact is instead:
- email-ownership verification bypass
- pre-account-hijacking of not-yet-registered addresses
- authenticated account creation without inbox access
- user enumeration
The password-reset token is separate
I also verified that the password-reset mechanism does not use the same predictable construction.
The password-reset token was a random UUIDv4 rather than a value derived from the email address.
Therefore, the predictable-token issue is specific to the account activation mechanism.
It should not be described as a password-reset-token prediction vulnerability.
Why the Root Cause Is Architectural
The underlying problem is not simply:
SHA-1 is weakSHA-1 is weakThe deeper issue is:
Verification secret
=
public identifier
+
deterministic transformationVerification secret
=
public identifier
+
deterministic transformationAn email address is not a secret.
Therefore, any activation credential derived exclusively from that email is reproducible.
The server needs to introduce unpredictable entropy that an attacker cannot calculate.
A secure model looks more like:
Registration
โ
Generate random 128+ bit value
โ
Store token server-side
โ
Send token to email
โ
User presents token
โ
Verify + invalidate
โ
Activate accountRegistration
โ
Generate random 128+ bit value
โ
Store token server-side
โ
Send token to email
โ
User presents token
โ
Verify + invalidate
โ
Activate accountMethodology Lesson
This finding demonstrates why security testing should not stop at the obvious endpoint behavior.
The registration API initially appeared to follow a normal flow:
Register
โ
Pending
โ
Email verificationRegister
โ
Pending
โ
Email verificationThe interesting behavior emerged by asking several questions:
What does the server return?
The registration response exposed the membership identifier needed for activation.
What makes the activation token unpredictable?
Comparing multiple server-generated tokens against known registration inputs revealed the deterministic relationship.
Can the token be reproduced offline?
Once the relationship was identified, independent test cases confirmed it.
Does the forged token actually produce meaningful state?
The activation request was validated using a wrong token first, followed by the calculated token.
The calculated token produced an authenticated session.
What happens at the boundaries?
Testing already-registered emails and the password-reset flow helped establish what the vulnerability could and could not do.
This distinction is important in responsible vulnerability research:
A strong report does not just demonstrate the attack. It also demonstrates where the attack stops.
Secure Design
A proper activation mechanism should use a high-entropy, unpredictable, single-use token.
For example:
Random token
โ
At least 128 bits of entropy
โ
Stored server-side
โ
Associated with account + purpose
โ
Time limited
โ
Invalidated after successful useRandom token
โ
At least 128 bits of entropy
โ
Stored server-side
โ
Associated with account + purpose
โ
Time limited
โ
Invalidated after successful useThe token must not be derived solely from:
email
username
member ID
account ID
timestampemail
username
member ID
account ID
timestampor another predictable identifier.
If a keyed construction is used, it should incorporate server-controlled secret material and appropriate lifecycle controls.
Most importantly, activation should be treated as a server-side security state transition rather than something the client can downgrade or control through parameters such as:
requiresActivationrequiresActivationRecommended Remediation
1. Replace the deterministic activation token
Generate a cryptographically secure random token with at least 128 bits of entropy.
2. Store the activation state server-side
Associate the token with:
- account
- activation purpose
- expiration
- single-use state
3. Invalidate tokens after use
An activation token should not remain reusable after the account has been activated.
4. Do not derive verification secrets from email addresses
Using SHA-1, SHA-256, or another unkeyed hash of the email does not solve the problem.
The input itself is predictable.
5. Enforce activation server-side
The requirement for email verification should not depend on whether the client sends:
requiresActivation=truerequiresActivation=trueThe server should determine whether activation is required based on its own security policy.
6. Review registration-state enumeration
Consider returning consistent responses for registered and unregistered addresses where practical, while preserving legitimate registration functionality.
Final Takeaway
Email verification is only meaningful if the verification credential proves access to the mailbox.
A token such as:
SHA1(lowercase(email))SHA1(lowercase(email))does not prove anything about mailbox ownership.
It proves only that someone knows the email address.
The vulnerability can therefore be summarized as:
Known email address
+
Predictable activation token
+
Public membership identifier
โ
Offline token generation
โ
Email verification bypass
โ
Authenticated account activationKnown email address
+
Predictable activation token
+
Public membership identifier
โ
Offline token generation
โ
Email verification bypass
โ
Authenticated account activationFor not-yet-registered addresses, this can further create a pre-account-hijacking scenario where an attacker establishes an account using an email address they do not control.
The broader lesson is simple:
Verification tokens must be secrets, not deterministic transformations of public identifiers.
A secure activation flow should rely on unpredictable server-generated entropy, server-side state, expiration, and one-time use โ not on the secrecy of an email address.