CVE-2026-1706

All-in-One Video Gallery <= 4.7.1 - Reflected Cross-Site Scripting via 'vi' Parameter

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

Description

The All-in-One Video Gallery plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'vi' parameter in all versions up to, and including, 4.7.1 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.7.1
PublishedMarch 3, 2026
Last updatedMarch 4, 2026

What Changed in the Fix

Changes introduced in v4.7.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the steps to research and exploit a Reflected Cross-Site Scripting (XSS) vulnerability in the All-in-One Video Gallery WordPress plugin (version 4.7.1). ### 1. Vulnerability Summary * **Vulnerability:** Reflected Cross-Site Scripting (XSS) * **Parameter:** `vi` (GET parameter…

Show full research plan

This plan outlines the steps to research and exploit a Reflected Cross-Site Scripting (XSS) vulnerability in the All-in-One Video Gallery WordPress plugin (version 4.7.1).

1. Vulnerability Summary

  • Vulnerability: Reflected Cross-Site Scripting (XSS)
  • Parameter: vi (GET parameter)
  • Affected Versions: <= 4.7.1
  • Cause: The plugin fails to sufficiently sanitize or escape the vi query parameter when rendering it back onto the page, typically within a gallery or video player context.
  • Sink: The value is likely reflected into a JavaScript variable or a hidden input field used to track the currently active video in a gallery.

2. Attack Vector Analysis

  • Endpoint: Any public-facing page containing an All-in-One Video Gallery shortcode (e.g., [aiovg_video], [aiovg_videos], or [aiovg_gallery]) or the default video archive/single pages.
  • Payload Carrier: URL Query Parameter vi.
  • Authentication: Unauthenticated (PR:N).
  • Preconditions:
    1. The plugin must be active.
    2. At least one video (aiovg_videos post type) should exist to ensure gallery/player components render.
    3. A page must exist that displays a gallery or player.

3. Code Flow (Inferred)

  1. Request Handling: A user accesses a URL like http://wp-dist.dev.local/?aiovg_videos=1&vi=PAYLOAD.
  2. Shortcode Processing: The plugin's shortcode handler (likely in public/ files, not provided but inferred from blocks/blocks.php references to shortcode fields) executes.
  3. Input Acquisition: The code retrieves the value using $_GET['vi'].
  4. Template Rendering: The plugin generates HTML/JavaScript to handle video navigation. It uses the vi value to identify which video to highlight or play.
  5. Vulnerable Sink: The value is echoed without esc_attr(), esc_js(), or esc_html().
    • Candidate Sink A (JavaScript): var current_video = '<?php echo $_GET['vi']; ?>';
    • Candidate Sink B (HTML Attribute): <div class="aiovg" data-vi="<?php echo $_GET['vi']; ?>">

4. Nonce Acquisition Strategy

Reflected XSS via a GET parameter in the public frontend typically does not require a nonce. Nonces in WordPress are primarily used for state-changing operations (CSRF protection). Since this is a reflection during page generation, the vi parameter is treated as a navigation state and is usually processed before any nonce check would occur.

Verification Step: If the agent encounters a 403 Forbidden or a "nonce check failed" message, it should search the page source for localized variables (e.g., aiovg_ajax or aiovg_vars) using browser_eval("window.aiovg_ajax").

5. Exploitation Strategy

The goal is to demonstrate script execution by breaking out of the context where vi is reflected.

  1. Discovery Phase:

    • Navigate to a gallery page with a unique canary: ?vi=XSS_CANARY_123.
    • View page source and locate XSS_CANARY_123.
    • Identify the surrounding context (e.g., inside <script> tags, inside a value="" attribute, or as raw text).
  2. Payload Crafting:

    • Context: HTML Attribute (e.g., value="PAYLOAD")
      • Payload: "><script>alert(document.domain)</script>
    • Context: JavaScript String (e.g., var id = 'PAYLOAD';)
      • Payload: ';alert(document.domain);//
  3. Execution Phase:

    • Use the http_request tool to send a GET request to the target page with the crafted vi parameter.
    • Use browser_navigate to the URL to confirm the script executes in a real browser environment.

6. Test Data Setup

Before testing, the environment must be prepared:

  1. Create a Video Post:
    wp post create --post_type=aiovg_videos --post_title="Test Video" --post_status=publish
    
  2. Create a Gallery Page:
    wp post create --post_type=page --post_title="Gallery" --post_content='[aiovg_videos]' --post_status=publish
    
  3. Identify the URL: The page will likely be at http://wp-dist.dev.local/gallery/.

7. Expected Results

  • Discovery: The string XSS_CANARY_123 is found in the response body.
  • Exploitation: When visiting the URL with the payload, the browser should execute the JavaScript.
  • Response Body Snippet (Example):
    <div class="aiovg-player" data-params='{"vi":"><script>alert(document.domain)</script>", ...}'>
    

8. Verification Steps

  1. HTTP Check: Use http_request to fetch the page with the payload and verify that the payload characters (like < and >) are NOT HTML-encoded in the sink.
    # Example logic for agent
    resp = http_request("http://wp-dist.dev.local/gallery/?vi=\"><script>alert(1)</script>")
    if "\"><script>alert(1)</script>" in resp['body']:
        print("Vulnerability Confirmed: Unescaped reflection found.")
    
  2. Browser Execution: Use browser_navigate and check for an alert dialog or a side effect (like writing to window.hacked = 1).

9. Alternative Approaches

If [aiovg_videos] does not reflect the parameter, try other shortcodes or default archive pages:

  • Archive Page: http://wp-dist.dev.local/aiovg_videos/?vi=PAYLOAD
  • Search Page: The plugin provides a search widget/block. Use the search form and check if vi is reflected in the search results URL or hidden fields.
  • Breadcrumbs: Check if the plugin uses vi to build breadcrumb links on single video pages.
Research Findings
Static analysis — not yet PoC-verified

Summary

The All-in-One Video Gallery plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'vi' query parameter in versions up to 4.7.1. This is caused by insufficient input sanitization and output escaping when reflecting the parameter value into the page, allowing unauthenticated attackers to execute arbitrary JavaScript in the user's browser context.

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/all-in-one-video-gallery/4.7.1/admin/admin.php /home/deploy/wp-safety.org/data/plugin-versions/all-in-one-video-gallery/4.7.5/admin/admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/all-in-one-video-gallery/4.7.1/admin/admin.php	2026-01-17 11:23:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/all-in-one-video-gallery/4.7.5/admin/admin.php	2026-02-25 12:57:34.000000000 +0000
@@ -76,6 +76,10 @@
 				$new_player_settings['hide_youtube_logo'] = $defaults['aiovg_player_settings']['hide_youtube_logo'];
 			}
 
+			if ( ! array_key_exists( 'statistics', $player_settings ) ) {
+				$new_player_settings['statistics'] = $defaults['aiovg_player_settings']['statistics'];
+			}
+
 			if ( count( $new_player_settings ) ) {
 				update_option( 'aiovg_player_settings', array_merge( $player_settings, $new_player_settings ) );
 			}						
@@ -360,6 +364,9 @@
 
 			// Remove the unfiltered_html capability from editors
 			aiovg_remove_unfiltered_html_capability_from_editors();
+
+			// Force rewrite rules flush on next load
+        	delete_option( 'aiovg_rewrite_rules_flushed' );
 		}
 	}		
 
@@ -1063,6 +1070,100 @@
 	}
 
 	/**
+     * Flags rewrite rules for flushing on the next request when relevant options change.
+     *
+     * @since 4.7.3
+     * @param string $option Option name being updated.
+     * @param mixed  $old    Previous option value.
+     * @param mixed  $value  Updated option value.
+     */
+    public function force_rewrite_rules_on_settings_update( $option, $old, $value ) {
+        if ( in_array( $option, array( 'aiovg_page_settings', 'aiovg_permalink_settings' ), true ) ) {
+            // Force rewrite rules flush on next load
+            delete_option( 'aiovg_rewrite_rules_flushed' );
+        }
+    }
+
+	/**
+     * Flags rewrite rules for flushing when an assigned AIOVG page
+     * or its translation is updated.
+     *
+     * @since 4.7.3
+     * @param int     $post_id Post ID.
+     * @param WP_Post $post    Post object.
+     * @param bool    $update  Whether this is an existing post update.
+     */
+    public function force_rewrite_rules_on_page_update( $post_id, $post, $update ) {
+        // Avoid autosaves & revisions
+        if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
+            return;
+        }
+
+        // Only act on updates (not new pages)
+        if ( ! $update ) {
+            return;
+        }
+
+        // Defensive capability check
+        if ( ! current_user_can( 'edit_page', $post_id ) ) {
+            return;
+        }
+
+        $page_settings      = aiovg_get_option( 'aiovg_page_settings' );
+		$permalink_settings = aiovg_get_option( 'aiovg_permalink_settings' );
+
+		if ( ! empty( $permalink_settings['video_archive_page'] ) ) {
+			$page_settings['video_archive_page'] = (int) $permalink_settings['video_archive_page'];
+		}
+
+        if ( empty( $page_settings ) ) {
+            return;
+        }
+
+        // Normalize assigned page IDs
+        $assigned_pages = array_map( 'intval', $page_settings );
+
+        // Direct Match (Fast Path)
+        if ( in_array( (int) $post_id, $assigned_pages, true ) ) {
+            delete_option( 'aiovg_rewrite_rules_flushed' );
+            return;
+        }
+
+        // Check for Polylang translations
+        if ( function_exists( 'pll_get_post_translations' ) ) {
+            foreach ( $assigned_pages as $assigned_id ) {
+                $translations = pll_get_post_translations( $assigned_id );
+
+                if ( empty( $translations ) ) {
+                    continue;
+                }
+
+                if ( in_array( (int) $post_id, array_map( 'intval', $translations ), true ) ) {
+                    delete_option( 'aiovg_rewrite_rules_flushed' );
+                    return;
+                }
+            }
+        }
+
+        // Check for WPML translations (TRID-based)
+        if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
+            $element_type = apply_filters( 'wpml_element_type', 'page' );
+            $current_trid = apply_filters( 'wpml_element_trid', null, $post_id, $element_type );
+
+            if ( ! empty( $current_trid ) ) {
+                foreach ( $assigned_pages as $assigned_id ) {
+                    $assigned_trid = apply_filters( 'wpml_element_trid', null, $assigned_id, $element_type );
+
+                    if ( (int) $current_trid === (int) $assigned_trid ) {
+                        delete_option( 'aiovg_rewrite_rules_flushed' );
+                        return;
+                    }
+                }
+            }
+        }
+    }

Exploit Outline

The exploit targets public-facing pages containing an All-in-One Video Gallery component, such as a gallery shortcode or the video archive page. An attacker crafts a URL with a malicious payload in the 'vi' GET parameter (e.g., `?vi="><script>alert(document.domain)</script>`). When a user visits this link, the plugin retrieves the 'vi' value using `$_GET['vi']` and echoes it into the HTML (likely within a data-params attribute or a JavaScript initialization block) without proper escaping via `esc_attr()` or `esc_js()`. This causes the injected script to execute in the user's browser context. The attack requires no authentication.

Check if your site is affected.

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