✓ Human-authored analysis; AI used for formatting and proofreading.
Your security team enabled MFA on the Cognito user pool. The compliance dashboard shows green. The SOC 2 auditor checked the box. MFA is deployed.
Except the account recovery flow doesn't require it. A user who clicks "Forgot password" authenticates with just an email verification code without TOTP, SMS second factor or hardware key. The recovery flow is an unauthenticated path around the MFA your team spent three sprints deploying.
Your scanner reports "MFA: Enabled." It's telling the truth. It's also missing the point.
How the bypass works
Cognito user pools have two authentication paths:
Path A: Normal login (MFA enforced)
Username + Password → MFA Challenge → TOTP/SMS code → Authenticated session
This is security team configured. This is auditor verified. This works correctly.
Path B: Account recovery (MFA bypassed)
"Forgot password" → Email verification code → New password set → Authenticated session
No MFA challenge. The user proves they own the email address (a single factor), sets a new password, and gets a fully authenticated session. The MFA enforcement applies to Path A only. Path B is a parallel entry point that skips it.
An attacker who compromises a user's email (phishing, credential stuffing, email provider breach) can:
- Go to the app's login page
- Click "Forgot password"
- Receive the verification code at the compromised email
- Set a new password
- Log in without MFA challenge
The MFA that was supposed to protect the account is never invoked. The attacker has a fully authenticated session with whatever permissions that user's Cognito group grants. This potentially includes IAM role assumption via the identity pool.
Why this is worse than "MFA not enabled"
When MFA is not enabled, everyone knows it. The scanner reports it. The risk register lists it. The team plans to fix it.
When MFA is enabled but bypassable, the organization has false confidence. The scanner says green. The auditor says compliant. The security team moves on to other priorities. Meanwhile, the recovery flow is a wide-open door that nobody is watching because the dashboard says it's locked.
This is the "MFA is theatre" pattern where security controls that exist but don't protect against the attack path they're supposed to block.
The three settings that create the bypass
{
"MfaConfiguration": "ON",
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Priority": 1, "Name": "verified_email" }
]
},
"UserPoolAddOns": {
"AdvancedSecurityMode": "OFF"
}
}
Setting 1: MFA is ON. Correct. This forces MFA on the normal login path.
Setting 2: Recovery via email. This is the default. AWS documentation recommends email-based recovery. It works and it bypasses MFA because the recovery flow doesn't invoke the MFA challenge.
Setting 3: Advanced security is OFF. Cognito's Advanced Security Features (ASF) include adaptive authentication that can require MFA even during recovery-like flows. When ASF is off, recovery is unprotected. When ASF is on with full enforcement, Cognito can challenge suspicious recovery attempts. Most deployments leave ASF off because it costs $0.050 per MAU.
Each setting is individually defensible. MFA is on. That's the CIS benchmark recommendation. Email recovery is the AWS default. ASF costs money and most orgs defer it. No individual setting is wrong. The interaction between all three creates the bypass.
What your scanner checks vs. what matters
| What the scanner checks | Result | What actually matters |
|---|---|---|
| Is MFA enabled? | ✅ ON | Can a user authenticate WITHOUT MFA? |
| Is the password policy strong? | ✅ 12 chars, mixed case | Can a user set a new password without MFA? |
| Is the user pool encrypted? | ✅ KMS | Does the recovery flow invoke MFA? |
| Are advanced security features on? | ❌ OFF | Does the recovery flow detect anomalies? |
The scanner checks four settings independently. The bypass exists in the interaction between MFA enforcement (which applies to login) and recovery (which doesn't). No single-setting check asks "is there an authentication path that bypasses MFA?"
What compound detection finds
Analyzing this configuration with multiple reasoning engines on the same snapshot reveals the bypass that single-setting checks miss.
CEL predicate evaluation fires two individual findings: advanced security is off, and MFA recovery bypass is possible. The second finding is not "MFA is disabled". MFA is enabled. The finding is "a recovery path exists that doesn't invoke MFA."
The compound chain cognito_recoverybypass fires when both conditions are true simultaneously: MFA is enforced (so the team thinks they're protected) AND the recovery flow bypasses it (so they're not). The compound is the finding. Neither individual finding alone tells the story.
SMT satisfiability checking asks: "Does there exist an authentication path that produces a valid session without requiring a second factor?" Answer: sat. The witness is the recovery flow: email verification only, no TOTP challenge. After enabling ASF with full enforcement, the same query returns unsat. The adaptive authentication layer challenges suspicious recovery attempts.
Temporal drift analysis asks: "After enabling ASF, how far is the configuration from regressing?" Answer: 1 change. Disabling ASF (to save $0.050/MAU) reopens the bypass immediately. The drift margin quantifies the fragility of the fix.
Game-theoretic cost analysis asks: "What does the attack cost?" Answer: the cost of compromising one email address. With credential stuffing services selling access for single-digit dollars, the attacker's investment is minimal. The defender's fix: enable ASF at $0.050 per monthly active user. For a 10,000-user pool, that's $500/month. The ROI depends on what the authenticated session grants access to.
The fix
Two changes, in priority order:
Fix 1: Enable Advanced Security Features (immediate)
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_xxxxx \
--user-pool-add-ons AdvancedSecurityMode=ENFORCED
With ASF in ENFORCED mode, Cognito applies adaptive authentication to all authentication events, including recovery flows. Suspicious recovery attempts (new device, new location, rapid succession) trigger additional challenges.
Cost: $0.050 per MAU. For most applications, this is small compared to the cost of a compromised account.
Fix 2: Restrict recovery mechanisms (defense in depth)
If your application supports it, change the recovery mechanism from email to admin-only:
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_xxxxx \
--admin-create-user-config AllowAdminCreateUserOnly=true
This removes self-service recovery entirely. Users who lose access contact support. More friction, more security. Not appropriate for all applications, but the strongest closure of the bypass.
How to check your own environment
# List your user pools
aws cognito-idp list-user-pools --max-results 20
# For each pool, check the three settings
aws cognito-idp describe-user-pool \
--user-pool-id <pool-id> \
| jq '{
mfa: .UserPool.MfaConfiguration,
recovery: .UserPool.AccountRecoverySetting,
advanced_security: .UserPool.UserPoolAddOns.AdvancedSecurityMode
}'
If you see:
{
"mfa": "ON",
"recovery": { "RecoveryMechanisms": [{ "Name": "verified_email" }] },
"advanced_security": "OFF"
}
You have the bypass. MFA is protecting the front door. The recovery flow is the unlocked side entrance.
The pattern beyond Cognito
MFA bypass via recovery appears in:
- Azure AD: Password reset with email verification bypasses MFA unless Conditional Access policies explicitly cover the reset flow.
- Okta: Self-service recovery with email factor only. MFA policy applies to authentication, not to account recovery, unless explicitly configured.
- Custom applications: Any app where "forgot password" and "login" have different authentication requirements.
The pattern is architectural: login and recovery are separate flows with separate security policies. Security teams configure the login flow and assume the recovery flow inherits the same protections. It doesn't. Each flow needs its own security configuration.
The tools that check "is MFA enabled?" answer the wrong question. The right question is "do all authentication paths require the same level of assurance?" That's a compound question spanning multiple flows, settings, and service configurations. No single-setting check can ask it.
The scenarios in this article are modeled on real configurations found in production Cognito deployments. The analysis uses Stave, an open-source static analysis tool that evaluates cloud configurations via CEL predicates and exports standardized facts for consumption by external reasoning engines. All from air-gapped snapshots with no cloud credentials required.
Top comments (0)