CVE-2026-22351

FullCalendar <= 1.6 - Missing Authorization

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

Description

The FullCalendar plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.6. This makes it possible for unauthenticated attackers to perform an unauthorized action.

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.6
PublishedFebruary 11, 2026
Last updatedFebruary 16, 2026
Affected pluginwp-fullcalendar
Research Plan
Unverified

This research plan targets a **Missing Authorization** vulnerability in **WP FullCalendar <= 1.6**. The vulnerability allows unauthenticated attackers to perform unauthorized actions or access sensitive data through the plugin's AJAX handlers. ### 1. Vulnerability Summary The WP FullCalendar plugin…

Show full research plan

This research plan targets a Missing Authorization vulnerability in WP FullCalendar <= 1.6. The vulnerability allows unauthenticated attackers to perform unauthorized actions or access sensitive data through the plugin's AJAX handlers.

1. Vulnerability Summary

The WP FullCalendar plugin provides an AJAX endpoint to fetch events and posts for display in a calendar. In versions up to 1.6, the handler for the wpfc_calendar action (and potentially others like wpfc_move_event) fails to validate user capabilities before processing query parameters like post_status. This allows unauthenticated users to query and retrieve private, draft, or password-protected posts that should not be visible to them.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpfc_calendar (via wp_ajax_nopriv_wpfc_calendar)
  • Vulnerable Parameter: post_status
  • Authentication: None (Unauthenticated)
  • Preconditions: A valid WordPress nonce for the wpfc_ajax_nonce action must be obtained from the frontend.

3. Code Flow

  1. Entry Point: An unauthenticated user sends a request to admin-ajax.php?action=wpfc_calendar.
  2. Hook Registration: The plugin registers the handler in wp-fullcalendar.php:
    add_action('wp_ajax_wpfc_calendar', 'wpfc_ajax_calendar');
    add_action('wp_ajax_nopriv_wpfc_calendar', 'wpfc_ajax_calendar');
    
  3. Vulnerable Handler (wpfc_ajax_calendar):
    • The function calls check_ajax_referer('wpfc_ajax_nonce', 'nonce').
    • It then extracts query arguments directly from $_REQUEST without capability checks:
      $args = array(
          'post_type' => $_REQUEST['post_type'],
          'post_status' => $_REQUEST['post_status'], // Vulnerable Sink: No check if user can read_private_posts
          'posts_per_page' => -1,
          // ... date range logic ...
      );
      $query = new WP_Query($args);
      
  4. Data Exposure: The results of the WP_Query are encoded as JSON and returned to the attacker.

4. Nonce Acquisition Strategy

The plugin exposes the required nonce via wp_localize_script whenever the calendar is rendered (usually via shortcode).

  1. Identify Shortcode: The plugin uses [wp_fullcalendar].
  2. Preparation: Create a public page containing this shortcode.
  3. Execution:
    • Navigate to the page.
    • Use browser_eval to extract the nonce from the global JavaScript object wpfc_ajax_vars.
    • Target Variable: window.wpfc_ajax_vars?.nonce

5. Exploitation Strategy

  1. Information Gathering:
    • Locate or create a page with [wp_fullcalendar].
    • Extract the nonce using browser_eval("wpfc_ajax_vars.nonce").
  2. Craft Payload:
    • Construct a POST request to admin-ajax.php.
    • Parameters:
      • action: wpfc_calendar
      • nonce: [EXTRACTED_NONCE]
      • post_type: post (or any)
      • post_status: private (to target private posts)
      • start: A Unix timestamp for a date far in the past (e.g., 0).
      • end: A Unix timestamp for a date far in the future (e.g., 2147483647).
  3. Execute Request:
    • Use http_request with Content-Type: application/x-www-form-urlencoded.

6. Test Data Setup

  1. Target Content:
    • Create a private post: wp post create --post_title="Sensitive Private Event" --post_status=private --post_content="This is secret data."
    • Create a draft post: wp post create --post_title="Internal Draft Note" --post_status=draft --post_content="Draft content here."
  2. Nonce Source:
    • Create a public page for nonce extraction: wp post create --post_type=page --post_title="Calendar Page" --post_status=publish --post_content='[wp_fullcalendar]'

7. Expected Results

  • The AJAX response should return a JSON array.
  • For an unauthenticated request with post_status=private, the array should contain an object representing the "Sensitive Private Event".
  • Example JSON:
    [
      {
        "id": 123,
        "title": "Sensitive Private Event",
        "start": "2023-10-27 10:00:00",
        "url": "http://localhost:8080/?p=123",
        "allDay": false
      }
    ]
    

8. Verification Steps

  1. Verify Response: Confirm the title of the private/draft post appears in the JSON output of the http_request.
  2. Check Authentication: Ensure the request was sent without any Cookie headers representing a logged-in user.
  3. Verify Post Status via CLI:
    wp post get [ID_FROM_JSON] --field=post_status
    # Should return 'private' or 'draft'
    

9. Alternative Approaches

If the wpfc_calendar action is restricted in the specific version tested, attempt the Unauthorized Modification vector:

  • Action: wpfc_move_event
  • Payload: action=wpfc_move_event&nonce=[NONCE]&id=[POST_ID]&dayDelta=10&minuteDelta=0
  • Goal: Change the date of a post/event without authorization.
  • Verification: Use wp post get [ID] to check if the post_date or event metadata has shifted by 10 days.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP FullCalendar plugin for WordPress is vulnerable to unauthorized data exposure in versions up to 1.6. The AJAX handler for fetching calendar events fails to perform capability checks before executing a query with user-supplied post statuses, allowing unauthenticated attackers to retrieve private, draft, or password-protected posts.

Vulnerable Code

// wp-fullcalendar.php

add_action('wp_ajax_wpfc_calendar', 'wpfc_ajax_calendar');
add_action('wp_ajax_nopriv_wpfc_calendar', 'wpfc_ajax_calendar');

function wpfc_ajax_calendar() {
    check_ajax_referer('wpfc_ajax_nonce', 'nonce');

    $args = array(
        'post_type' => $_REQUEST['post_type'],
        'post_status' => $_REQUEST['post_status'], // Vulnerable Sink: No capability check on post_status
        'posts_per_page' => -1,
        // ... (truncated)
    );
    
    $query = new WP_Query($args);
    // ... (truncated)
    echo json_encode($events);
    exit;
}

Security Fix

--- wp-fullcalendar.php
+++ wp-fullcalendar.php
@@ -102,7 +102,14 @@
 
     $args = array(
         'post_type' => $_REQUEST['post_type'],
-        'post_status' => $_REQUEST['post_status'],
+        'post_status' => 'publish',
         'posts_per_page' => -1,
     );
+
+    if ( !empty($_REQUEST['post_status']) && current_user_can('read_private_posts') ) {
+        $args['post_status'] = $_REQUEST['post_status'];
+    } elseif ( !empty($_REQUEST['post_status']) ) {
+        $args['post_status'] = 'publish';
+    }
+

Exploit Outline

1. Locate a public-facing page on the target site that utilizes the [wp_fullcalendar] shortcode. 2. Extract the 'wpfc_ajax_nonce' value from the page source, typically found within the global 'wpfc_ajax_vars' JavaScript object. 3. Construct an unauthenticated POST request to /wp-admin/admin-ajax.php. 4. Set the 'action' parameter to 'wpfc_calendar' and provide the extracted 'nonce'. 5. Set the 'post_status' parameter to 'private' (or 'draft') and 'post_type' to 'any'. 6. Provide 'start' and 'end' Unix timestamps covering a broad range to ensure all historical and future posts are captured. 7. Execute the request and observe the JSON response, which will contain the IDs, titles, and metadata of the targeted unauthorized content.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.