{"dataType":"CVE_RECORD","dataVersion":"5.2","cveMetadata":{"cveId":"CVE-2026-101041","assignerOrgId":"5a6e4751-2f3f-4070-9419-94fb35b644e8","state":"PUBLISHED","assignerShortName":"CIRCL","dateReserved":"2026-09-27T14:47:50.804Z","datePublished":"2026-09-27T14:47:57.012Z","dateUpdated":"2026-09-27T14:47:57.012Z"},"containers":{"cna":{"affected":[{"modules":["website/models/user.py","website/web/views/user.py","website/lib/user_utils.py"],"product":"vulnerability-lookup","programFiles":["website/models/user.py","website/web/views/user.py","website/lib/user_utils.py"],"repo":"https://github.com/vulnerability-lookup/vulnerability-lookup","vendor":"vulnerability-lookup","versions":[{"lessThanOrEqual":"6.2.0","status":"affected","version":"0","versionType":"semver"}]}],"credits":[{"lang":"en","type":"remediation developer","value":"Alexandre Dulaunoy"},{"lang":"en","type":"remediation developer","value":"Cédric Bonhomme"},{"lang":"en","type":"remediation developer","value":"Claude Fable 5.1"},{"lang":"en","type":"finder","value":"avrlab233"}],"descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>The account recovery (password reset) functionality in the vulnerability-lookup web application contains a time-of-check-to-time-of-use (TOCTOU) race condition in the consumption of single-use recovery tokens. The original implementation verified the token nonce against the stored digest and then consumed (cleared) it in separate database operations. Two concurrent HTTP requests presenting the same valid recovery token could both pass the verification check before either transaction committed, allowing both to set their own password on the target account. The last transaction to commit overwrites the first, enabling an attacker who possesses a valid recovery token to replace the legitimate user's password with one of their choosing.</p><p>A secondary defect in the same endpoint (confirm_account) allowed a valid recovery link to be used to set an empty or trivially short password (e.g., three characters). The view handler performed only a manual equality comparison between the two password fields and never invoked the form's validation logic, bypassing the intended minimum-length and complexity constraints.</p><p>The affected component is the user account recovery endpoint (/user/confirm_account/&lt;token&gt;) and the associated token verification and consumption logic in the User model (website/models/user.py) and the view layer (website/web/views/user.py).</p>"}],"value":"The account recovery (password reset) functionality in the vulnerability-lookup web application contains a time-of-check-to-time-of-use (TOCTOU) race condition in the consumption of single-use recovery tokens. The original implementation verified the token nonce against the stored digest and then consumed (cleared) it in separate database operations. Two concurrent HTTP requests presenting the same valid recovery token could both pass the verification check before either transaction committed, allowing both to set their own password on the target account. The last transaction to commit overwrites the first, enabling an attacker who possesses a valid recovery token to replace the legitimate user's password with one of their choosing.\n\nA secondary defect in the same endpoint (confirm_account) allowed a valid recovery link to be used to set an empty or trivially short password (e.g., three characters). The view handler performed only a manual equality comparison between the two password fields and never invoked the form's validation logic, bypassing the intended minimum-length and complexity constraints.\n\nThe affected component is the user account recovery endpoint (/user/confirm_account/<token>) and the associated token verification and consumption logic in the User model (website/models/user.py) and the view layer (website/web/views/user.py)."}],"impacts":[{"capecId":"CAPEC-111","descriptions":[{"lang":"en","value":"CAPEC-111 Race Condition"}]}],"metrics":[{"cvssV4_0":{"Automatable":"NOT_DEFINED","Recovery":"NOT_DEFINED","Safety":"NOT_DEFINED","attackComplexity":"HIGH","attackRequirements":"NONE","attackVector":"NETWORK","baseScore":6.3,"baseSeverity":"MEDIUM","privilegesRequired":"NONE","providerUrgency":"NOT_DEFINED","subAvailabilityImpact":"NONE","subConfidentialityImpact":"LOW","subIntegrityImpact":"NONE","userInteraction":"NONE","valueDensity":"NOT_DEFINED","vectorString":"CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N","version":"4.0","vulnAvailabilityImpact":"NONE","vulnConfidentialityImpact":"LOW","vulnIntegrityImpact":"NONE","vulnerabilityResponseEffort":"NOT_DEFINED"},"format":"CVSS","scenarios":[{"lang":"en","value":"GENERAL"}]}],"problemTypes":[{"descriptions":[{"cweId":"CWE-362","description":"CWE-362 Concurrency: Race Condition","lang":"en","type":"CWE"}]},{"descriptions":[{"cweId":"CWE-20","description":"CWE-20 Improper Input Validation","lang":"en","type":"CWE"}]}],"providerMetadata":{"orgId":"5a6e4751-2f3f-4070-9419-94fb35b644e8","shortName":"CIRCL","dateUpdated":"2026-09-27T14:47:57.012Z"},"references":[{"name":"Security patch","tags":["patch"],"url":"https://github.com/vulnerability-lookup/vulnerability-lookup/commit/5462bab62d76df852619e01eb67da36c023c8c40"},{"name":"Security patch","tags":["patch"],"url":"https://github.com/vulnerability-lookup/vulnerability-lookup/commit/ad6f22882975516adf193a1a920aaa54025c71d4"}],"solutions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>The non-atomic check-then-consume pattern is replaced with a single conditional UPDATE statement (compare-and-set) that atomically verifies the stored SHA-256 nonce digest, updates the password hash, sets is_confirmed, and clears the token in one database operation. Only the first transaction to commit succeeds; all concurrent attempts match zero rows and are rejected with an error. The view now calls form.validate() before processing the password change, enforcing the form's length and equality validators. The dead consume_account_token() method is removed to eliminate the non-atomic consumption path entirely.</p>"}],"value":"The non-atomic check-then-consume pattern is replaced with a single conditional UPDATE statement (compare-and-set) that atomically verifies the stored SHA-256 nonce digest, updates the password hash, sets is_confirmed, and clears the token in one database operation. Only the first transaction to commit succeeds; all concurrent attempts match zero rows and are rejected with an error. The view now calls form.validate() before processing the password change, enforcing the form's length and equality validators. The dead consume_account_token() method is removed to eliminate the non-atomic consumption path entirely."}],"title":"Vulnerability-Lookup - Race Condition in Account Recovery Token Consumption Allows Password Takeover","x_gcve":[{"extensions":{"bcp-05-x-01":{"ai_annotations":[{"ai_level":"generated","description":"Draft vulnerability metadata was generated from a git-format patch using an Ollama-hosted language model. Human validation is required before publication.","gna_source":1,"models":[{"gna_source":1,"identifier":"qwen3.8:27b","name":"qwen3.8:27b","source":"ollama"}],"review_status":"partial","scope":"record","tags":["ai-computer-assisted:llm-generated","ai-computer-assisted:classification"]}]},"bcp-05-x-02":{"x_patch2vuln":{"assumptions":["The product version and exact release boundary are not specified in the patch metadata; the affected version range is recorded as unspecified.","The CAPEC-111 mapping is the closest available pattern for a database-level TOCTOU race on a single-use token; no CAPEC specifically describes concurrent compare-and-set exploitation, so this is the best available match.","The CVSS AC:H reflects the race-condition aspect; the missing-validation aspect alone would be AC:L, but the patch set is treated as one vulnerability per the caller's instruction.","The vendor is not explicitly identified in the metadata; the GitHub repository name 'vulnerability-lookup' is used as the product identifier.","No specific authentication or deployment preconditions beyond possession of a valid recovery token are stated in the patch; the token is assumed to be delivered out-of-band (e.g., email).","The Co-Authored-By line for Claude Fable 5.1 is treated as a remediation developer credit per the metadata's remediation_developers list; no finder or reporter is identified in the supplied evidence."],"capecRationale":[{"capecId":"CAPEC-111","rationale":"The attacker exploits the window between token verification and token consumption by issuing a concurrent request with the same token. Both requests pass the check; the last to commit overwrites the first's password. This is a textbook race-condition attack on a shared resource (the single-use token). CAPEC-111 is the closest available pattern; no CAPEC specifically covers TOCTOU on database-level token consumption, so this is the best match."}],"commit":"ad6f22882975516adf193a1a920aaa54025c71d4","confidence":"medium","credits":[{"lang":"en","type":"remediation developer","value":"Alexandre Dulaunoy"},{"lang":"en","type":"remediation developer","value":"Cédric Bonhomme"},{"lang":"en","type":"remediation developer","value":"Claude Fable 5.1"}],"cvssRationale":"AV:N: the recovery endpoint is reachable over the network. AC:H: exploiting the race condition requires precise timing of two concurrent requests, making the attack condition high complexity. AT:N: no active user interaction or deception is required beyond possessing a valid token. PR:N: no prior authentication is needed; the recovery token itself is the credential. UI:N: no user interaction is required. VC:N: the vulnerability does not directly expose confidential data. VI:H: the integrity of the user's password (a critical credential) is directly compromised. VA:N: no availability impact. SC:N: the impact is confined to the vulnerable component. SI:H: the integrity of the user's account (password, confirmation state) is compromised, affecting the user's ability to access their own account. SA:N: no security-authority impact.","fixSummary":"The non-atomic check-then-consume pattern is replaced with a single conditional UPDATE statement (compare-and-set) that atomically verifies the stored SHA-256 nonce digest, updates the password hash, sets is_confirmed, and clears the token in one database operation. Only the first transaction to commit succeeds; all concurrent attempts match zero rows and are rejected with an error. The view now calls form.validate() before processing the password change, enforcing the form's length and equality validators. The dead consume_account_token() method is removed to eliminate the non-atomic consumption path entirely.","generatedAt":"2026-09-27T14:43:09.207406Z","generator":"patch2vuln.py","model":"qwen3.8:27b","modelComparison":{"rankings":[{"agreementScore":9,"assumptionCount":6,"confidence":"medium","model":"qwen3.8:27b","score":4}],"selectedModel":"qwen3.8:27b","selectionMethod":"deterministic-consensus-v1","selectionNotice":"The selected result is closest to model consensus; this heuristic does not establish factual correctness and human review remains required."},"patchSha256":"ccb7572963e8b2435c2ee54cea019a60b1b70a5717cc3848ccd940232eae0b91","patchSummary":"Added reset_password_with_account_token() to the User model performing a single conditional UPDATE (WHERE id = ? AND account_token_digest = sha256(nonce)) that sets pwdhash, is_confirmed=True, and account_token_digest=None atomically. Removed the separate consume_account_token() method. Changed the confirm_account view to call form.validate() instead of only comparing password1 and password2 manually. Introduced a _account_token_digest() static helper to centralize the SHA-256 computation (previously three inline calls). Removed synchronize_session=False from the UPDATE execution options so the ORM session stays consistent. Added regression tests for concurrent token consumption (two threads, barrier-synchronized) and for rejection of passwords that fail form validation (empty, three-character, mismatched).","patchTruncated":false,"patches":[{"commit":"5462bab62d76df852619e01eb67da36c023c8c40","patchSha256":"75ed8b215e2769b5d20b161ddfbde9e0b2975f836397979a94387340b0501ec1","source":"https://github.com/vulnerability-lookup/vulnerability-lookup/commit/5462bab62d76df852619e01eb67da36c023c8c40.patch","sourceUrl":"https://github.com/vulnerability-lookup/vulnerability-lookup/commit/5462bab62d76df852619e01eb67da36c023c8c40.patch","subject":"Fix recovery token consumption race"},{"commit":"ad6f22882975516adf193a1a920aaa54025c71d4","patchSha256":"c1cecff5ac5f7804ff42f0583b156cefa2add775cb6f80506c917f84a26bf43d","source":"https://github.com/vulnerability-lookup/vulnerability-lookup/commit/ad6f22882975516adf193a1a920aaa54025c71d4.patch","sourceUrl":"https://github.com/vulnerability-lookup/vulnerability-lookup/commit/ad6f22882975516adf193a1a920aaa54025c71d4.patch","subject":"fix: [user] Validate the recovery form and harden the token"}],"source":"patch set (2 sources)","subject":"fix: [user] Validate the recovery form and harden the token","weaknessRationale":[{"cweId":"CWE-362","rationale":"The primary defect is a TOCTOU race: the token nonce is verified and then consumed in separate operations, allowing two concurrent transactions to both pass verification before either commits. The fix replaces this with an atomic compare-and-set UPDATE, confirming the race condition as the root cause."},{"cweId":"CWE-20","rationale":"The confirm_account view never called form.validate() on POST, so the form's minimum-length and equality validators were bypassed. A valid recovery link alone permitted setting an empty or three-character password. The fix adds the form.validate() call before processing."}]}}},"recordType":"advisory","vulnId":"gcve-1-2026-20252"}]}}}