CVE-2026-100667
Executive Summary
CVE-2026-100667 is an authentication-state confusion flaw (CWE-304) in the Grav CMS Login plugin (grav-plugin-login) affecting versions 3.8.7 through 3.9.6. When two-factor authentication (2FA) is enabled, the plugin's Login::isAuthenticated() method evaluated only the session flag confirming the password step had succeeded, rather than the flag confirming the full login had completed—so a session paused at the 2FA prompt was incorrectly treated as fully authenticated. An attacker who already knows a member's password but cannot satisfy that member's second factor can therefore read member-only content rendered by the authenticated() Twig function, the [authenticated] shortcode, and related conditional-rendering primitives. The flaw is limited to information disclosure (CVSS 5.3, C:L) and was corrected in grav-plugin-login 3.9.7.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Technical Analysis
The vulnerability resides in the interaction between Grav's session-based authentication state machine and its content-gating primitives. Grav performs authentication in discrete steps: first the credential (password) check, then—if enabled—a second-factor challenge. These two states are tracked via separate session flags. Correct behavior requires that downstream authorization decisions consult the flag indicating the entire login sequence completed.
In the affected versions, Login::isAuthenticated() returned a truthy result as soon as the password-step flag was set. That method underpins several Twig/shortcode level gates:
- the no-argument
authenticated()Twig function, - the
authenticated(null, 'group')form used for group-gated content, - the
[authenticated]shortcode (and the inverse[guest]shortcode, which was consequently evaluated too early).
Because the 2FA completion flag was never consulted, any session that had merely passed the password challenge—i.e., was sitting at the "enter your 2FA code" prompt—was reported as authenticated. The bug is a classic authentication-state confusion (CWE-304, "Missing Critical Step in Authentication") in which a partial-credential state is conflated with a fully-authorized state.
Crucially, the flaw does not affect the permission-bearing form authenticated('some.permission'), which routes through UserObject::authorize() and therefore performs an independent authorization check. Access controlled by Grav's access: front-matter/page rule is also unaffected, because that path does not depend on the vulnerable isAuthenticated() shortcut. As a result, the defect is strictly a disclosure primitive: it can reveal gated content rendered by Twig conditionals, but it does not mint a fully-authenticated session, does not grant write capability, and does not allow the attacker to act as the compromised user.
Attack surface: any Grav site running grav-plugin-login in the affected range with 2FA enabled and using authenticated(), authenticated(null, 'group'), [authenticated], or [guest] to protect content. The flaw is remotely reachable (AV:N), requires no privileges on the target (PR:N), needs no user interaction (UI:N), and has low complexity (AC:L) — the attacker merely has to hold a valid password.
Exploitation Scenarios
No public PoC, template, or exploit code exists for this CVE; the chain below is inferred strictly from the NVD description and the vulnerability class (authentication-state confusion / missing critical step).
Reconnaissance and target identification. The attacker identifies a Grav CMS instance running the Login plugin within the affected version window (3.8.7–3.9.6) and determines, via normal login-page behavior, that 2FA is enforced after the password step. This is a low-noise, pre-auth observation using only the standard login flow.
Credential acquisition. The attacker must already possess a valid member password (phishing, credential stuffing, password reuse, prior breach data). This is a precondition, not a capability gained by the CVE.
Triggering the flaw. The attacker authenticates with the known password. The application accepts the credential and advances the session to the second-factor prompt, at which point the session has the password-step flag set but the login-completion flag unset. The attacker does not supply a valid 2FA code.
Bypassing content gating. Because
Login::isAuthenticated()consults only the password-step flag in affected versions, a request for content gated by the no-argumentauthenticated()Twig function or the[authenticated]shortcode is rendered as if the user were fully logged in. The[guest]shortcode is correspondingly evaluated as "not a guest" (i.e., authenticated) too early. The attacker reads content that should be restricted to fully authenticated members.Post-exploitation impact. The impact is confined to disclosure of the specific member-only content rendered by the vulnerable primitives. The attacker does not obtain a completed session, cannot reach pages protected by an
access:rule, cannot use the permission-scoped formauthenticated('some.permission')(which goes throughUserObject::authorize()), and cannot perform actions as the victim. There is no privilege escalation, no code execution, and no lateral movement enabled by this CVE.
Detection note: Exploitation leaves a distinctive fingerprint — credentialed login activity that advances to the 2FA stage followed by authenticated content requests without completion of the second factor. This originates from pre-auth (not yet privileged) traffic and requires log correlation across the login and content-serving layers.
Impact Assessment
The CVSS v3.1 base score is 5.3 (Medium), vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N.
- Confidentiality: Low — only content gated by the specific
authenticated()/[authenticated]/[guest]primitives is disclosed; permission-scoped andaccess:-protected resources remain protected. - Integrity: None — the attacker cannot modify data or state.
- Availability: None — no denial-of-service component.
- Scope: Unchanged — impact stays within the vulnerable component.
Business impact is generally limited but non-trivial for membership sites, subscription portals, internal documentation hubs, or communities that rely on authenticated()/[authenticated] to gate member-only material. Where such gating protects regulated, proprietary, or personally identifying content, even a "Low" confidentiality score can translate into compliance exposure (e.g., privacy or contractual data-handling obligations). Blast radius is bounded by (a) 2FA being enabled and (b) the site using the affected shortcodes/Twig conditionals for content gating rather than access: rules or the permission-scoped form. The precondition that the attacker must already know a member's password means this is most credibly exploited as an amplifier on top of an existing credential compromise—converting "password but not second factor" into "read member content." No CISA KEV listing and no public exploit evidence currently indicate active in-the-wild exploitation, and EPSS has not been scored by FIRST.org.
Detection Guidance
Sigma Rule
title: Grav Login Plugin 2FA Bypass - Authenticated Content Access Without 2FA Completion
id: 8f4c2a10-7b3e-4d21-9c15-grav100667
status: experimental
description: Detects requests for 2FA-gated Grav content where the session has passed only the password step and not the second-factor step (CVE-2026-100667).
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-100667
author: valtersit.com Threat Intelligence
date: 2026-01-01
logsource:
category: webserver
product: grav
detection:
selection_login:
cs-uri-stem|contains:
- '/user/login'
selection_2fa:
cs-uri-query|contains: '2fa'
selection_gated_content:
cs-uri-stem|startswith: '/'
condition: selection_login or selection_2fa
falsepositives:
- Legitimate users who complete the second factor within the same session.
- Automated health checks against public landing pages.
level: medium
tags:
- attack.t1190
- cve.2026-100667
YARA Rule
rule CVE_2026_100667_Grav_Login_2FA_Bypass
{
meta:
description = "Detects grav-plugin-login sources in affected range with isAuthenticated() logic (CVE-2026-100667)"
author = "valtersit.com Threat Intelligence"
reference = "https://nvd.nist.gov/vuln/detail/CVE-2026-100667"
date = "2026-01-01"
severity = "medium"
strings:
$login_class = "class Login" ascii wide
$auth_func = "isAuthenticated" ascii wide
$shortcode_auth = "[authenticated]" ascii wide
$shortcode_guest = "[guest]" ascii wide
$ver_377 = "3.8.7" ascii wide
$ver_396 = "3.9.6" ascii wide
condition:
(
$login_class and $auth_func
) and (
$shortcode_auth or $shortcode_guest
)
}
Suricata IDS Rule
alert http any any -> any any (msg:"CVE-2026-100667 Grav Login Plugin 2FA Bypass - Authenticated Content Fetch Without 2FA"; flow:established,to_server; http.method; content:"GET"; http.uri; content:"/user/login"; nocase; http.header; content:"Cookie"; nocase; http.cookie; content:"grav-site"; nocase; reference:cve,2026-100667; reference:url,nvd.nist.gov/vuln/detail/CVE-2026-100667; classtype:attempted-recon; sid:2026100667; rev:1;)
Elastic Detection
sequence by source.ip with maxspan=5m
[ network where event.category == "network" and url.path : "/user/login" and http.request.method == "POST" ]
[ network where event.category == "network" and http.request.method == "GET" and not url.path : "*/user/login*" and user.name == null ]
Splunk SPL Query
index=web sourcetype=access_combined
| eval is_login=if(match(uri, "/user/login.*"), 1, 0)
| eval is_2fa=if(match(uri, ".*2fa.*"), 1, 0)
| stats count values(uri) as uris by clientip, session_id
| where is_2fa=0 AND is_login=1
| table clientip, session_id, uris, count
| sort - count
Wazuh OSSEC Rule
<group name="web,grav,cve-2026-100667,">
<rule id="100667" level="7">
<decoded_as>web-accesslog</decoded_as>
<field name="url">^/user/login</field>
<description>CVE-2026-100667: Grav Login plugin 2FA bypass - client authenticated past password step while reaching 2FA-gated content</description>
<mitre>
<id>T1190</id>
</mitre>
<group>cve_2026_100667,</group>
</rule>
<rule id="100668" level="5">
<if_sid>100667</if_sid>
<field name="url">2fa</field>
<description>CVE-2026-100667: Grav 2FA challenge endpoint reached - possible partial-auth session probing</description>
</rule>
</group>
Mitigation & Patch Guidance
Patch confidence: 0/4 (UNPATCHED — no patch evidence from any tracked source). Although the NVD description states the issue is "Fixed in grav-plugin-login 3.9.7," no authoritative patch artifact, vendor advisory, changelog diff, or commit reference is currently tracked for this CVE. Treat all version claims as unverified until the vendor advisory and fixed release are independently confirmed. Do not rely on a version-number string alone as proof of remediation.
Immediate mitigations
- Prefer
authenticated('some.permission')(which routes throughUserObject::authorize()) or Grav'saccess:page/front-matter rules to gate sensitive content instead of the vulnerable no-argumentauthenticated()/authenticated(null, 'group')/[authenticated]primitives, which are the affected surfaces. This is unaffected per the description and is the most reliable stop-gap. - Replace
[authenticated]/[guest]shortcode usage for sensitive content with permission-scoped oraccess:-rule gating wherever operationally possible. - Enforce strong, unique member credentials and reduce credential-reuse exposure, since exploitation requires a known password.
- Monitor and rate-limit login attempts to disrupt credential stuffing that could supply the required precondition.
- Review logs for credential-valid sessions that abandoned the 2FA prompt yet continued requesting gated content.
- Prefer
Patch / upgrade steps
- Monitor the grav-plugin-login project (GitHub releases and CHANGELOG) for the 3.9.7 release and any accompanying security advisory; verify the advisory explicitly enumerates this CVE.
- When a validated 3.9.7 (or later) artifact is available, back up the site and database, stage-upgrade in a test environment, confirm
authenticated()/[authenticated]/[guest]behavior is corrected, then upgrade production. - Re-verify the fix by testing that a session at the 2FA prompt can no longer read content gated by the affected primitives, while
authenticated('some.permission')andaccess:-protected pages remain correctly enforced.
Long-term hardening
- Adopt a policy of gating all sensitive content via explicit permission/authorization checks rather than boolean authentication shortcuts.
- Integrate dependency scanning for Grav plugins and track plugin advisories in your vulnerability management program.
- Preserve detailed authentication logs (password-step and 2FA-step transitions) to support detection of partial-auth bypass attempts.
- Periodically review content gating code for reliance on authentication-state booleans that may conflate partial and completed login states.
References
- NVD — CVE-2026-100667: https://nvd.nist.gov/vuln/detail/CVE-2026-100667
- CWE-304 — Missing Critical Step in Authentication: https://cwe.mitre.org/data/definitions/304.html
- MITRE ATT&CK T1190 — Exploit Public-Facing Application: https://attack.mitre.org/techniques/T1190/
- grav-plugin-login project (verify 3.9.7 release and changelog): https://github.com/getgrav/grav-plugin-login
- CISA KEV Catalog (CVE-2026-100667 is NOT listed as of this report): https://www.cisa.gov/known-exploited-vulnerabilities-catalog
Threat actor / exploit-in-the-wild status: No known threat actor campaigns, no public PoC, no Nuclei template, and no exploit code are associated with CVE-2026-100667. CISA KEV listing: NO. EPSS: not yet scored by FIRST.org.
What is this? Security researchers use these exact search queries on Shodan.io (a search engine for internet-connected devices) to find vulnerable systems globally. You can copy these dorks to see if your organization's IP space is exposed.