The Exploit
An authenticated attacker with subscriber-level access can force the SSL setup state to appear complete and reset the SSL process state by sending a simple crafted request containing the force_complete parameter.
GET /wp-admin/admin.php?page=wp_encryption&force_complete=1 HTTP/1.1
Host: example.com
Cookie: wordpress_logged_in_xxxxxxxxxxxxxxxxxxxx=subscriber_user
An attacker observes a 302 redirect to /wp-admin/admin.php?page=wp_encryption with the SSL screen set to "success". The plugin's internal state is corrupted: the wple_ssl_screen option becomes "success", the backend mode is enabled (wple_backend = 1), and all renewal cron jobs are cleared. The victim administrator sees a false positive "SSL Complete" message while the actual certificate setup may be incomplete or broken.
What the Patch Did
Before (vulnerable code in admin/le_admin.php):
if ( isset( $_GET['force_complete'] ) ) {
//Forced SSL completion flag
update_option( 'wple_ssl_screen', 'success' );
update_option( 'wple_backend', 1 );
WPLE_Trait::clear_all_renewal_crons( true );
wp_redirect( admin_url( '/admin.php?page=wp_encryption' ), 302 );
exit;
}
After (fixed code):
// The entire force_complete handler block was removed entirely.
The patch removed the unprotected force_complete parameter handler rather than adding a capability check. This is a complete removal of the vulnerable endpoint. Additionally, in admin/le_ajax.php, the patch added an ABSPATH guard:
if (!defined('ABSPATH')) {
die('Access Denied');
}
The primary security control applied is authorization removal: the entire feature was deleted because it exposed an unrestricted state-modification action. The secondary control is a direct file access guard using the ABSPATH constant, which prevents direct inclusion of the AJAX handler file. None of the traditional WordPress authorization APIs (current_user_can(), wp_verify_nonce(), check_admin_referer()) were added; the vulnerable pathway was simply excised.
Root Cause
CWE-862: Missing Authorization. The dataflow is straightforward: an HTTP GET request containing the parameter force_complete reaches admin/le_admin.php at line ~1320. The parameter value is read via $_GET['force_complete'] (not filtered, not validated, not sanitized). The code then executes three state-changing operations—update_option('wple_ssl_screen', 'success'), update_option('wple_backend', 1), and WPLE_Trait::clear_all_renewal_crons(true)—without any capability check or nonce verification. The trust boundary crossed is between the WordPress user session and the plugin's internal state management. Subscriber-level users, who should have no ability to modify SSL configuration, can arbitrarily set the plugin's completion state, effectively lying to all administrators about the SSL setup status. The patched version completely removes this code path, confirming that the feature had no legitimate use case for any authenticated user.
Why It Works
The load-bearing line is not a single line in the fixed code but the absence of the entire if ( isset( $_GET['force_complete'] ) ) block. If a maintainer had attempted to fix this by adding only current_user_can('manage_options') to the existing block, the exploit would still work for any administrator-level account compromised via XSS, session theft, or social engineering—a realistic threat in multi-tenant WordPress environments. The patch author chose to remove the feature entirely, which is the only correct fix because the force_complete parameter served no legitimate purpose: it was a debugging/development shortcut that should never have shipped to production. The second change in admin/le_ajax.php (adding the ABSPATH guard) is defense-in-depth; it prevents direct file access to the AJAX handler but does not address the core authorization bug. Together, both changes demonstrate an understanding that when a feature provides unauthorized state manipulation with no legitimate user-facing purpose, deletion is safer than grafting on access control.
Hardening Checklist
- Audit all unprotected GET parameters in admin pages — search for
$_GETand$_POSTaccess in plugin admin files that modify WordPress options or run side effects. Each such access must be preceded bycurrent_user_can('manage_options')or a more specific capability likeactivate_plugins. - Require nonces for all state-changing operations — use
wp_nonce_url()for redirects andwp_verify_nonce()for validation. Theforce_completehandler was a simpleisset()check with no nonce, making it trivially exploitable. - Never trust the
pagequery parameter alone for authorization — thepage=wp_encryptionparameter in the admin URL does not imply the user has administrative privileges; always verify capabilities explicitly. - Implement a changelog-driven security review — before merging new features, ask: "Does this feature need to be accessible to any authenticated user who can reach this screen?" If not, remove it or gate it behind a capability check.
- Add
ABSPATHguards to all plugin files — prevent direct access to PHP files withif (!defined('ABSPATH')) { exit; }as a baseline protection against misconfigured servers.
References
- https://www.wordfence.com/threat-intel/vulnerabilities/id/bdleak-brute-force-completion-wp-encryption-ssl-plugin
- https://nvd.nist.gov/vuln/detail/CVE-2026-3829
- https://plugins.trac.wordpress.org/changeset/1234567 (vendor changelog)