# RCA Report: CVE-2026-11380 — JetWidgets For Elementor Stored XSS

## Summary

The JetWidgets For Elementor WordPress plugin (versions up to and including 1.0.21) is vulnerable to a stored cross-site scripting (Stored XSS) flaw in the Animated Box widget. The widget's `animation_effect` setting is printed directly into the HTML `class` attribute of the rendered widget without output escaping or server-side validation. An attacker who can supply a value containing a double quote can break out of the `class` attribute and inject arbitrary attributes, including event handlers such as `onmouseover="alert(1)"`. Because the payload is stored in the page and rendered whenever anyone visits the page, this is a stored XSS issue. The reproduction confirmed the payload in the published page HTML by deploying a real WordPress + Elementor + JetWidgets stack and viewing the rendered page.

## Impact

- **Product / component:** `jetwidgets-for-elementor` (Crocoblock / jetmonsters) — specifically the `jw-animated-box` widget.
- **Affected versions:** 1.0.21 and earlier (the WordPress.org repository still lists 1.0.21 as the current stable release at the time of the CVE).
- **Risk level:** Medium. The vendor/Wordfence CVSS is reported as 6.4.
- **Consequences:** Any authenticated user with at least the `author` role can save a page containing a malicious Animated Box widget. The injected JavaScript is stored in the page and executes in the browser of any visitor who views the page.

## Impact Parity

- **Disclosed / claimed maximum impact:** Stored XSS via an author-level account in the Animated Box widget's `animation_effect` setting.
- **Reproduced impact from this run:** A real WordPress environment was deployed with Elementor and JetWidgets 1.0.21. A published page was created with a malicious `animation_effect` value. The rendered page HTML contains the escaped attribute boundary break and the injected `style` and `onmouseover="alert(1)"` attributes.
- **Parity:** `full` — the reproduction reaches the same rendered-page sink described in the advisory.
- **Not demonstrated:** The reproduction does not run a full browser engine, so the JavaScript payload itself is not executed in the test environment. The proof relies on the presence of the injected event handler in the server-rendered HTML, which is the root cause of the stored XSS.

## Root Cause

The vulnerability is in the widget's render template:

```php
<!-- templates/jw-animated-box/global/index.php (v1.0.21) -->
<div class="jw-animated-box <?php $this->__html( 'animation_effect', '%s' ); ?>">
```

The helper method chain ends up in `includes/base/class-jet-widgets-base.php`:

```php
public function __render_html( $setting = null, $format = '%s' ) {
    ...
    printf( wp_kses_post( $format ), $val );
}
```

`wp_kses_post()` is applied to the format string `'%s'`, not to the value. Therefore the raw `animation_effect` value is printed verbatim inside the HTML `class` attribute. The UI control in `includes/addons/jet-widgets-animated-box.php` is a `<select>` with a fixed set of animation effects, but the server never validates the stored value before rendering it. A malicious author can supply a value such as:

```
jw-box-effect-1" style="position:fixed;width:100%;height:100%;top:0;left:0" onmouseover="alert(1)" data-x="
```

The first double quote closes the `class` attribute, the injected `style` covers the viewport, and the `onmouseover` handler will execute when a visitor moves the mouse over the page.

The vendor fixed the issue in commit `49952dd92b2bbd59e6627e7b67b0b3621c3852a0` (released as 1.0.22). The fix:

1. Introduces a server-side `sanitize_animation_effect()` method that rejects any value not in the allowed list of animation effects and falls back to the default.
2. Updates the template to use `esc_attr()` on the sanitized value:

```php
$animation_effect = $this->sanitize_animation_effect( $this->get_settings_for_display( 'animation_effect' ) );
?>
<div class="jw-animated-box <?php echo esc_attr( $animation_effect ); ?>">
```

## Reproduction Steps

The full reproduction is automated by `bundle/repro/reproduction_steps.sh`. At a high level the script:

1. Creates a Docker network and starts a `mysql:8.0` container and a `wordpress:6.7-php8.1-apache` container.
2. Waits until the WordPress install page is reachable inside the Docker network.
3. Installs WP-CLI, completes WordPress core installation, and sets the site URL to the internal Docker hostname (`http://jetwidgets-repro-wp`).
4. Installs and activates Elementor 3.27.5 and JetWidgets For Elementor 1.0.21.
5. Creates an `author` role user.
6. Runs a PHP script inside the WordPress container that inserts a published page containing an Elementor `jw-animated-box` widget whose `animation_effect` setting is the malicious payload.
7. Fetches the rendered page from inside the Docker network using `curl` and copies the body and headers to `bundle/repro/artifacts/`.
8. Checks that the rendered HTML contains `onmouseover="alert(1)"` and the injected `style` attribute.

**Expected evidence of reproduction:** `bundle/repro/artifacts/page.html` contains a line similar to:

```html
<div class="elementor-jw-animated-box jet-widgets"><div class="jw-animated-box jw-box-effect-1" style="position:fixed;width:100%;height:100%;top:0;left:0" onmouseover="alert(1)" data-x="">
```

## Evidence

- `bundle/logs/reproduction_steps.log` — step-by-step console output from the reproduction run.
- `bundle/logs/insert.log` — page ID and permalink produced by the PHP insertion script.
- `bundle/logs/wordpress.log` — Apache/WordPress container logs.
- `bundle/logs/mysql.log` — MySQL container logs.
- `bundle/repro/artifacts/page.html` — full HTML body of the rendered published page.
- `bundle/repro/artifacts/page.headers` — HTTP response headers from the page request.
- `bundle/repro/artifacts/insert_page.php` — the PHP helper used to create the malicious page.

Key excerpt from `bundle/repro/artifacts/page.html` (line 229 in the reproduced output):

```html
<div class="elementor-jw-animated-box jet-widgets"><div class="jw-animated-box jw-box-effect-1" style="position:fixed;width:100%;height:100%;top:0;left:0" onmouseover="alert(1)" data-x="">
```

This shows that the `animation_effect` value broke out of the `class` attribute and added new attributes, including the `onmouseover` event handler.

Environment details captured in the runtime manifest:

- WordPress 6.7 (PHP 8.1 / Apache)
- Elementor 3.27.5
- JetWidgets For Elementor 1.0.21
- MySQL 8.0

## Recommendations / Next Steps

1. **Upgrade:** Site owners should upgrade JetWidgets For Elementor to version 1.0.22 or later, which contains commit `49952dd92b2bbd59e6627e7b67b0b3621c3852a0`.
2. **Fix approach:**
   - Server-side validation: reject any `animation_effect` value that is not in the defined list of allowed effects (e.g., `jw-box-effect-1` through `jw-box-effect-8`).
   - Output escaping: render the value with `esc_attr()` before placing it inside the HTML `class` attribute.
3. **Testing recommendations:**
   - Add unit/integration tests that save payloads containing `"`, `<`, `>`, and `javascript:` in the `animation_effect` setting and assert that the rendered HTML does not contain an unescaped attribute break or event handler.
   - Test with the Elementor editor save flow as well as direct post-meta injection to ensure both paths are sanitized.
4. **Further verification:** The script could be extended to include a real browser engine (e.g., Playwright/Chromium) to demonstrate actual JavaScript execution on mouseover. The current proof is sufficient to confirm the server-side sink but not a live browser exploit.

## Additional Notes

- **Idempotency:** The reproduction script was executed twice consecutively from a clean state and succeeded both times. It cleans up all Docker containers and networks on exit via a `trap`.
- **Limitations:** The reproduction does not exercise the Elementor editor UI or the `admin-ajax.php` save endpoint. It sets the widget data directly in post meta. This is acceptable for proving the rendered-page vulnerability sink, but it does not reproduce the full author-workflow UI. The root cause is the same regardless of how the malicious value reaches the database.
- **No custom exploit code:** The reproduction uses the real WordPress, Elementor, and JetWidgets plugin code; no reimplementation of the vulnerability was used.
