FullCalendar <= 1.6 - Missing Authorization
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:NTechnical Details
<=1.6This 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(viawp_ajax_nopriv_wpfc_calendar) - Vulnerable Parameter:
post_status - Authentication: None (Unauthenticated)
- Preconditions: A valid WordPress nonce for the
wpfc_ajax_nonceaction must be obtained from the frontend.
3. Code Flow
- Entry Point: An unauthenticated user sends a request to
admin-ajax.php?action=wpfc_calendar. - 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'); - Vulnerable Handler (
wpfc_ajax_calendar):- The function calls
check_ajax_referer('wpfc_ajax_nonce', 'nonce'). - It then extracts query arguments directly from
$_REQUESTwithout 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);
- The function calls
- Data Exposure: The results of the
WP_Queryare 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).
- Identify Shortcode: The plugin uses
[wp_fullcalendar]. - Preparation: Create a public page containing this shortcode.
- Execution:
- Navigate to the page.
- Use
browser_evalto extract the nonce from the global JavaScript objectwpfc_ajax_vars. - Target Variable:
window.wpfc_ajax_vars?.nonce
5. Exploitation Strategy
- Information Gathering:
- Locate or create a page with
[wp_fullcalendar]. - Extract the nonce using
browser_eval("wpfc_ajax_vars.nonce").
- Locate or create a page with
- Craft Payload:
- Construct a POST request to
admin-ajax.php. - Parameters:
action:wpfc_calendarnonce:[EXTRACTED_NONCE]post_type:post(orany)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).
- Construct a POST request to
- Execute Request:
- Use
http_requestwithContent-Type: application/x-www-form-urlencoded.
- Use
6. Test Data Setup
- 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."
- Create a private post:
- 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]'
- Create a public page for nonce extraction:
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
- Verify Response: Confirm the title of the private/draft post appears in the JSON output of the
http_request. - Check Authentication: Ensure the request was sent without any
Cookieheaders representing a logged-in user. - 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 thepost_dateor event metadata has shifted by 10 days.
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
@@ -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.