ACF Galerie 4 <= 1.4.2 - Missing Authorization
Description
The ACF Galerie 4 plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.4.2. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=1.4.2What Changed in the Fix
Changes introduced in v1.4.3
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2025-62104 (ACF Galerie 4 - Missing Authorization) ## 1. Vulnerability Summary The **ACF Galerie 4** plugin (versions <= 1.4.2) contains a missing authorization vulnerability in its migration functionality. Specifically, the AJAX handler `acfg4_start_migration` fa…
Show full research plan
Vulnerability Research Plan: CVE-2025-62104 (ACF Galerie 4 - Missing Authorization)
1. Vulnerability Summary
The ACF Galerie 4 plugin (versions <= 1.4.2) contains a missing authorization vulnerability in its migration functionality. Specifically, the AJAX handler acfg4_start_migration fails to perform a capability check (e.g., current_user_can('manage_options')). While it implements a nonce check, the nonce is exposed to any authenticated user who can access the WordPress admin dashboard (including the Subscriber role). An attacker with Subscriber-level access can trigger this migration process, which modifies existing Advanced Custom Fields (ACF) field definitions in the database, potentially disrupting the site's gallery functionality or altering data format in wp_postmeta.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
acfg4_start_migration - Method: POST
- Parameters:
action:acfg4_start_migration(Required)nonce: A valid nonce for theacfg4_start_migration_nonceaction (Required)migrate_from: Either1(ACF Photo Gallery Field) or2(ACF Gallery Pro) (Required)
- Required Authentication: Subscriber or higher (Authenticated).
- Preconditions: The plugin must be active. For the migration to have a visible effect, existing ACF fields of type
photo_galleryorgalleryshould exist.
3. Code Flow
- Registration: In
acf-galerie-4.php, theinitialize_migration()function instantiates theacfg4_migrationclass. - Hooking: In
providers/class.migration.php, the constructor registers the AJAX action:add_action('wp_ajax_acfg4_start_migration', array($this, 'acfg4_start_migration')); - Nonce Exposure: The function
acfg4_start_migration_nonce()is hooked toadmin_head. It checksif ( !is_admin() ) return;. Since the WordPress dashboard (/wp-admin/) is accessible to Subscribers, this function executes for them, printing:<script type="text/javascript">const acfg4_start_migration_nonce = "...";</script> - Vulnerable Function: The
acfg4_start_migration()function:- Verifies the nonce via
wp_verify_nonce( $_POST['nonce'], 'acfg4_start_migration_nonce'). - Checks if
migrate_fromis1or2. - Crucially, it skips any
current_user_can()check. - It queries
wp_postsforpost_type = 'acf-field'. - Iterates through fields and updates
post_content(serialized metadata) if the field type isphoto_galleryorgallery, changing it togalerie-4. - If
migrate_from == 1, it also performs a regex-like migration onwp_postmetaassociated with those fields.
- Verifies the nonce via
4. Nonce Acquisition Strategy
The nonce is printed directly into the HTML of any admin page for a logged-in user.
- Login: Log in as a Subscriber.
- Navigate: Navigate to
/wp-admin/profile.php(standard landing page for Subscribers). - Extract: Use
browser_evalto extract the global JavaScript variable. - Target Variable:
window.acfg4_start_migration_nonce
5. Exploitation Strategy
- Data Setup: Create a test ACF field with type
gallery(representing ACF Pro) orphoto_gallery(representing the ACF Photo Gallery plugin). - Authentication: Authenticate as a Subscriber-level user.
- Nonce Capture: Navigate to
/wp-admin/profile.phpand runbrowser_eval("window.acfg4_start_migration_nonce"). - Trigger Migration: Perform a POST request to
admin-ajax.phpusing thehttp_requesttool.
Request Details:
- URL:
{{BASE_URL}}/wp-admin/admin-ajax.php - Method: POST
- Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Usingaction=acfg4_start_migration&nonce={{NONCE}}&migrate_from=2migrate_from=2to trigger the basic field type update).
6. Test Data Setup
- User: A user with the role
subscriber. - ACF Field: Since the plugin migrates existing fields, we must create an ACF field manually via WP-CLI to act as the "victim" data.
# Create an ACF Field Group FIELD_GROUP_ID=$(wp post create --post_type=acf-field-group --post_title="Test Gallery Group" --post_status=publish --porcelain) # Create an ACF Field of type 'gallery' (simulating ACF Pro Gallery) # The 'post_content' must be a serialized array containing 'type' => 'gallery' FIELD_DATA='a:1:{s:4:"type";s:7:"gallery";}' wp post create --post_type=acf-field --post_excerpt="my_gallery_field" --post_content="$FIELD_DATA" --post_parent=$FIELD_GROUP_ID --post_status=publish
7. Expected Results
- The AJAX response should be:
{"success":true,"data":{"message":"Migration has successfully completed."}} - The database record for the ACF field created in the setup phase should have its
post_contentupdated.
8. Verification Steps
- Check Post Content: Use WP-CLI to inspect the
post_contentof the ACF field.wp db query "SELECT post_content FROM wp_posts WHERE post_type = 'acf-field' AND post_excerpt = 'my_gallery_field'" - Verify Transformation: Confirm that the serialized string now contains
s:9:"galerie-4"instead ofs:7:"gallery".- Before:
a:1:{s:4:"type";s:7:"gallery";} - After:
a:1:{s:4:"type";s:9:"galerie-4";}
- Before:
9. Alternative Approaches
If the Subscriber cannot access /wp-admin/profile.php due to other security plugins:
- The nonce is also enqueued via
acfg4_start_migration_nonceinadmin_head. If any other plugin page is accessible to Subscribers (e.g., a custom dashboard page), the nonce will be there. - Check if
migrate_from=1produces observable changes inwp_postmeta. If a post has metadata associated with the field namemy_gallery_field, the migration will attempt to serialize comma-separated values into a PHP array.- Setup meta:
wp post meta add [POST_ID] my_gallery_field "123,456,789" - After Exploit:
wp post meta get [POST_ID] my_gallery_fieldshould return a serialized arraya:3:{i:0;s:3:"123";i:1;s:3:"456";i:2;s:3:"789";}.
- Setup meta:
Summary
The ACF Galerie 4 plugin for WordPress (versions <= 1.4.2) fails to perform a capability check in its AJAX migration handler. This allow authenticated users, including those with Subscriber-level access, to trigger a migration process that alters ACF field definitions and transforms post metadata in the database.
Vulnerable Code
// providers/class.migration.php function acfg4_start_migration_nonce() { if ( !is_admin() ) return; $nonce = wp_create_nonce('acfg4_start_migration_nonce'); ?> <script type="text/javascript">const acfg4_start_migration_nonce = "<?php echo $nonce; ?>";</script> <?php } --- // providers/class.migration.php, around line 37 public function acfg4_start_migration() { global $wpdb; $wpdb->query('START TRANSACTION'); try { $migrate_from = $_POST['migrate_from']; if ( isset( $_POST['nonce'] ) && !wp_verify_nonce( $_POST['nonce'], 'acfg4_start_migration_nonce') ) { wp_send_json_error(['message' => "Nonce verification failed. Please try again."], 400); } if( !in_array( $migrate_from, [1, 2] ) ){ wp_send_json_error(['message' => "Choose which plugin you want to migrate from."], 400); } $fields = $wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_type = 'acf-field'"); foreach( $fields as $field ){ $field_name = $field->post_excerpt; $field_metadata = unserialize( $field->post_content ); $field_type = $field_metadata['type']; if( in_array( $field_type, ['photo_gallery', 'gallery'])){ $field_metadata['type'] = 'galerie-4'; $updated_content = serialize($field_metadata); $wpdb->update( $wpdb->posts, array( 'post_content' => $updated_content ), array( 'ID' => $field->ID ) ); ...
Security Fix
@@ -18,7 +18,7 @@ } function acfg4_start_migration_nonce() { - if ( !is_admin() ) return; + if ( ! current_user_can('manage_options') ) return; $nonce = wp_create_nonce('acfg4_start_migration_nonce'); ?> <script type="text/javascript">const acfg4_start_migration_nonce = "<?php echo $nonce; ?>";</script> @@ -26,27 +26,41 @@ } public function enqueue_plugin_admin_scripts() { - wp_enqueue_script('acfg4-admin-script', ACFG4_PLUGIN_URL . 'assets/js/admin-script.js', ['jquery'], '1.0.0', true); + if ( current_user_can('manage_options') ) { + wp_enqueue_script('acfg4-admin-script', ACFG4_PLUGIN_URL . 'assets/js/admin-script.js', ['jquery'], '1.0.0', true); + } } public function enqueue_plugin_admin_styles() { - wp_enqueue_style('acfg4-admin-css', ACFG4_PLUGIN_URL . 'assets/css/admin-style.css', [], '1.0.0'); + if ( current_user_can('manage_options') ) { + wp_enqueue_style('acfg4-admin-css', ACFG4_PLUGIN_URL . 'assets/css/admin-style.css', [], '1.0.0'); + } } public function acfg4_start_migration() { + if ( ! current_user_can('manage_options') ) { + wp_send_json_error( + ['message' => 'Unauthorized'], + 403 + ); + } + + if ( + ! isset($_POST['nonce']) || + ! wp_verify_nonce($_POST['nonce'], 'acfg4_start_migration_nonce') + ) { + wp_send_json_error( + ['message' => 'Invalid or missing nonce'], + 400 + ); + } + global $wpdb; $wpdb->query('START TRANSACTION'); try { $migrate_from = $_POST['migrate_from']; - if ( - isset( $_POST['nonce'] ) && - !wp_verify_nonce( $_POST['nonce'], 'acfg4_start_migration_nonce') ) - { - wp_send_json_error(['message' => "Nonce verification failed. Please try again."], 400); - } - if( !in_array( $migrate_from, [1, 2] ) ){ wp_send_json_error(['message' => "Choose which plugin you want to migrate from."], 400); }
Exploit Outline
To exploit this vulnerability, an attacker first logs into the WordPress site as a Subscriber. They then access any administrative page (such as /wp-admin/profile.php) to obtain the 'acfg4_start_migration_nonce', which is automatically printed into the page's HTML for any user in the admin area. Once the nonce is acquired, the attacker makes a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'acfg4_start_migration', the 'nonce' parameter populated with the captured nonce, and 'migrate_from' set to 1 or 2. This triggers the acfg4_start_migration function, which proceeds to update ACF field definitions in the wp_posts table and potentially re-serializes gallery data in the wp_postmeta table without verifying that the user has the 'manage_options' capability.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.