All-in-One Video Gallery <= 4.7.1 - Reflected Cross-Site Scripting via 'vi' Parameter
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:NTechnical Details
<=4.7.1What Changed in the Fix
Changes introduced in v4.7.5
Source Code
WordPress.org SVNThis 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
viquery 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:
- The plugin must be active.
- At least one video (
aiovg_videospost type) should exist to ensure gallery/player components render. - A page must exist that displays a gallery or player.
3. Code Flow (Inferred)
- Request Handling: A user accesses a URL like
http://wp-dist.dev.local/?aiovg_videos=1&vi=PAYLOAD. - Shortcode Processing: The plugin's shortcode handler (likely in
public/files, not provided but inferred fromblocks/blocks.phpreferences to shortcode fields) executes. - Input Acquisition: The code retrieves the value using
$_GET['vi']. - Template Rendering: The plugin generates HTML/JavaScript to handle video navigation. It uses the
vivalue to identify which video to highlight or play. - Vulnerable Sink: The value is echoed without
esc_attr(),esc_js(), oresc_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']; ?>">
- Candidate Sink A (JavaScript):
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.
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 avalue=""attribute, or as raw text).
- Navigate to a gallery page with a unique canary:
Payload Crafting:
- Context: HTML Attribute (e.g.,
value="PAYLOAD")- Payload:
"><script>alert(document.domain)</script>
- Payload:
- Context: JavaScript String (e.g.,
var id = 'PAYLOAD';)- Payload:
';alert(document.domain);//
- Payload:
- Context: HTML Attribute (e.g.,
Execution Phase:
- Use the
http_requesttool to send a GET request to the target page with the craftedviparameter. - Use
browser_navigateto the URL to confirm the script executes in a real browser environment.
- Use the
6. Test Data Setup
Before testing, the environment must be prepared:
- Create a Video Post:
wp post create --post_type=aiovg_videos --post_title="Test Video" --post_status=publish - Create a Gallery Page:
wp post create --post_type=page --post_title="Gallery" --post_content='[aiovg_videos]' --post_status=publish - Identify the URL: The page will likely be at
http://wp-dist.dev.local/gallery/.
7. Expected Results
- Discovery: The string
XSS_CANARY_123is 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
- HTTP Check: Use
http_requestto 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.") - Browser Execution: Use
browser_navigateand check for an alert dialog or a side effect (like writing towindow.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
viis reflected in the search results URL or hidden fields. - Breadcrumbs: Check if the plugin uses
vito build breadcrumb links on single video pages.
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
@@ -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.