Abstract. The Maintenance plugin for WordPress decides whether to lift its coming-soon wall by looking at a cookie the visitor controls, and it never checks that cookie against the configured secret. Any non-empty value is enough. This write-up covers the bug class (broken access control, CWE-284/CWE-639), the exact line where the check goes wrong, a conceptual account of why an anonymous request reaches it, and the remediation and detection steps that follow. The plugin is unpatched as of this writing, so the working proof of concept stays out of the public text until a fix ships — the aim is to teach the pattern, not to hand out an attack.
Keywords: WordPress security, broken access control, authorization bypass, cookie validation, CWE-284, CWE-639, secure coding.
1. Introduction
Almost every WordPress site passes through a maintenance or coming-soon phase at some point — during a build, a migration, or a deliberate takedown — and a plugin hides the real front end behind a holding page until the owner is ready. Maintenance, by WebFactory Ltd, does exactly that on more than one million sites. When a plugin that popular rests its entire security story on one guarantee, any crack in that guarantee is worth a close look.
The guarantee itself is narrow: a visitor without privileges sees the maintenance page and nothing else. It breaks the moment the "let this visitor in" decision hangs on a value the visitor can set at will. Trusting a client-supplied token as though the server had verified it is textbook broken access control — common, and clear enough to teach from — which is reason enough to document it while the patch is still pending.
2. Affected component
| Field | Value |
|---|---|
| Plugin | Maintenance |
| Slug | maintenance |
| Vendor | WebFactory Ltd |
| Plugin URL | https://wordpress.org/plugins/maintenance/ |
| Tested / Affected version | 4.32 (latest at time of testing, 2026-10-05); the vulnerable logic is long-standing and very likely present in earlier versions that ship the same skip_maintenance_mode cookie check |
| Active installs | 1,000,000+ |
| Vulnerability type | Broken Access Control — Maintenance Mode Bypass |
| CWE | CWE-284 (Improper Access Control) / CWE-639 (Authorization Bypass Through User-Controlled Key) |
| Authentication required | None (unauthenticated) |
| CVSS 3.1 (proposed) | 5.3 Medium — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N |
| Status | Unpatched (0-day at time of publication) |
3. Technical analysis
Maintenance mode is enforced through a very late template_include hook that swaps in the maintenance template for every non-admin visitor:
// line ~104
add_action('template_include', array($this, 'template_include'), 999999);There is also a "Direct Access Link" feature. The owner sets a secret token (direct_access_link); present that secret as a query-string parameter and the plugin lets you through, dropping a skip_maintenance_mode cookie so the exemption lasts 24 hours. That part is fine. The problem lives in how the exemption gets checked on later requests.
// Inside load_maintenance_page() (begins ~line 4331)
// line ~4346 — the cookie is SET only when the secret direct_access_link is supplied
$custom_url_link = $mtnc_options['direct_access_link'];
if (!empty($custom_url_link)) {
if (isset($_GET[$custom_url_link])) {
setcookie("skip_maintenance_mode", true, time() + 24 * 60 * 60, '/');
$_COOKIE['skip_maintenance_mode'] = true;
}
}
// ... later, the bypass CHECK (line ~4421) trusts the raw client cookie:
if (!empty($_COOKIE['skip_maintenance_mode'])) {
$show_maintenance_page = 0; // BUG: presence of ANY cookie value disables the wall
}The write path behaves: the cookie only lands after the right secret shows up. The read path is where it falls apart, because the authorization decision rides entirely on !empty($_COOKIE['skip_maintenance_mode']). That value is never compared against direct_access_link, never signed, and never tied to a server-side session or any stored state. A cookie is client data through and through, so a plugin that treats the presence of one as proof of authorization has handed the decision to the client — the client now decides whether the client is allowed in.
That is CWE-639 (authorization bypass through a user-controlled key) sitting on top of CWE-284 (improper access control). The secret-token design is sound; it fails because the verification step inspects the wrong property — whether the value exists — instead of whether it cryptographically equals the secret.
4. How the flaw is reached (conceptual)
Reaching the broken check takes no privilege, no account, and no knowledge of the secret. The maintenance filter fires on ordinary front-end requests from anonymous visitors — that is the entire point of it — and on each one the plugin reads the skip_maintenance_mode cookie to choose between the holding page and the live site.
Since an HTTP client owns the cookies it sends, and the check waves through any non-empty value, the question the plugin thinks it is answering ("did this visitor present the correct secret earlier?") quietly becomes one the visitor answers for themselves ("do I feel like coming in?"). The whole vulnerability is that gap between a guarded write and an unguarded read: the secret only protects the convenience of setting the cookie automatically, never the act of honoring it. A visitor who has never seen the secret lands in the same let-through state just by claiming it.
The request that demonstrates this is left out on purpose. The mechanism above tells you everything you need to gauge the risk and confirm a fix; a copy-paste attack string tells you nothing more, and it stays withheld until a patched version is out.
5. Impact assessment
An unauthenticated attacker gets past the maintenance wall and sees the live front-end content the administrator meant to keep hidden — a pre-launch site, a build in progress, or a site pulled offline on purpose. That is the one thing the plugin exists to prevent.
The blast radius is contained. The bypass only exposes what WordPress would normally serve to front-end visitors once the wall comes down; on its own it grants no access to wp-admin, plugin settings, or the database, and it changes nothing. That confidentiality-only, read-only character is what puts the proposed CVSS at 5.3 Medium (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N): reachable over the network, low complexity, no privileges or interaction, scope unchanged, limited confidentiality impact, no integrity or availability impact. The business cost can still be real when the hidden content is sensitive — unreleased products, staging data, or a site taken down precisely because nobody should be looking at it.
6. Remediation
Validate the cookie instead of trusting that it exists. Honor the bypass only when the cookie value matches the configured secret, compared in constant time:
if (
!empty($mtnc_options['direct_access_link'])
&& !empty($_COOKIE['skip_maintenance_mode'])
&& hash_equals((string) $mtnc_options['direct_access_link'], (string) $_COOKIE['skip_maintenance_mode'])
) {
$show_maintenance_page = 0;
}When the correct direct_access_link query parameter does arrive, set the cookie to the secret itself — or, better, to a signed token generated server-side — rather than to true. hash_equals() keeps the comparison free of timing side channels.
Owners should move to a fixed release the moment the vendor ships one. Until that happens, the hardening steps below apply.
7. Detection and hardening for site owners
Everything here follows directly from the mechanics above:
- Treat maintenance mode as obscurity, not access control. Content that truly must stay hidden belongs behind a layer the visitor cannot touch — HTTP Basic Auth, an IP allowlist, a firewall rule, or a non-public host — not a front-end plugin on its own.
- Watch for the tell-tale request shape. A front-end request carrying a
skip_maintenance_modecookie with no earlier request that presented the secretdirect_access_linkparameter is suspicious. Web-server and WAF logs can surface requests that show up already holding the bypass cookie. - Least privilege: turn the feature off. If you don't use "Direct Access Link," leave it unconfigured and never lean on it to protect anything sensitive.
- Virtual patching. As a stop-gap, a WAF or edge rule can strip or ignore a
skip_maintenance_modecookie that arrives without a validated secret, until the official fix is in place. It mitigates; it doesn't cure. - Keep the plugin updated and subscribe to the vendor's release notes so the corrected version goes on promptly.
8. Broader lesson
Presence is not proof. A token-based exemption is only as strong as the comparison that honors it. Any time a server decides authorization from client-supplied data — a cookie, a header, a query parameter, a hidden form field — it has to verify the value against a server-held secret or server-side state, use a constant-time comparison for secrets, and never read authorization off the mere existence of the field. Guarding the write while leaving the read open is a trap that keeps recurring. The security property lives in the check, not in how the value got set the first time.
9. Responsible disclosure
This issue was handled under coordinated disclosure. The vendor has been notified privately and in good faith, and the full operational proof of concept stays out of this public article until a fix is available. Complete technical detail will follow only after a patched release, or after the agreed disclosure window, with timing coordinated with the vendor. All verification ran on a local, isolated WordPress instance under the researcher's control; no third-party or production systems were tested or touched.
References
- CWE-284: Improper Access Control — https://cwe.mitre.org/data/definitions/284.html
- CWE-639: Authorization Bypass Through User-Controlled Key — https://cwe.mitre.org/data/definitions/639.html
- OWASP: Broken Access Control — https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- WordPress Plugin Developer Handbook — Nonces — https://developer.wordpress.org/apis/security/nonces/
- WordPress Plugin Developer Handbook — Checking User Capabilities — https://developer.wordpress.org/apis/security/user-roles-and-capabilities/
- FIRST CVSS v3.1 Calculator — https://www.first.org/cvss/calculator/3.1
Published for defensive and educational purposes. Noroxi LLC conducts authorized security research only; operational exploit detail is withheld until a fix is available.
Exploited this week · every Monday
New KEV entries, fresh PoCs, WordPress triage and what to do this week. No account needed.