See how monospace compares to other vendors in security performance
Impact
The default value for the CORSENABLED and CORSORIGIN configuration was set to be very permissive by default. This could lead to unauthorized access in uncontrolled environments when the configuration hasn't been changed.
Patches
The default values for CORS have been changed in https://github.com/directus/directus/pull/12022 which is released under 9.7.0
Workarounds
Configure the CORS environment variables to match your project's usage, rather than leaving them at the (permissive) defaults.
For more information If you have any questions or comments about this advisory: Open an issue in directus/directus Email us at security@directus.io
Summary
A vulnerability exists in the file update mechanism which allows an unauthenticated actor to modify existing files with arbitrary contents (without changes being applied to the files' database-resident metadata) and / or upload new files, with arbitrary content and extensions, which won't show up in the Directus UI.
Details
Directus exposes the CRUD operations for uploading or handling files under the /files route.
The endpoint handler is responsible for updating an existing file identified by the provided primary key specified through the pk parameter. Primary keys are UUID values such as /files/927b3abf-fb4b-4c66-bdaa-eb7dc48a51cb. Here the filenamedisk value is never sanitized, it's possible to pass a path containing traversal sequences (../) through it, but a fully arbitrary file write is not possible in case the "local" storage handler is used. (Other storage implementations haven't been checked during the research process). The packages/storage-driver-local/src/index.ts file defines two relevant functions: write and fullpath.
The write method uses the fullPath method to create the absolute path for the to-be-created file. The join method is used to create the final path string. As the fullPath method uses join to create a relative path starting with the separator to be added under the download dir, this call normalizes the path and further upwards traversal is not possible during the write operation. With that being said, it is still possible, to make the system "ignore" the temp prefix given to the file, resulting in an arbitrarily named file being placed in the upload folder.
As a summary for the vulnerability:
- It is possible, to change the contents of an existing file, as an existing UUID can be specified as the file name - The metadata won't change, so the mime type cannot be modified - This also makes the changes happen "silently", without directus knowing about the changes - A new, previously non-existent file can be created with arbitrary contents - The file won't show up in on the Directus UI, it can only be seen through other means (such as shell access) - An extension MUST be defined for the file to be modified - This prevents us from uploading executables or malware with no extensions, but these wouldn't be executable either way
Recommendations for fixing the vulnerability can be found in later chapters.
Requirements
As providing a primary key is required for successful exploitation, at least one asset with a known UUID must be available for an attacker. This can usually be achieved by browsing an application that uses the given Directus instance to provide images.
Naturally, the instance needs to be accessible over the network used by the attacker as well.
Once network access and knowledge of at least one file UUID is available for the attacker, exploitation can be done by sending a single request.
Potential impacts
The impact of successful exploitation is highly dependent on how Directus is set up to be used by a different application. Many different configurations can be created, but the following are likely the most noteworthy:
1. Setting up a phishing site
SVGs can be used to set up very sophisticated looking pages, as it allows the embedding of HTML, CSS and scripts. The issue is once again with the default-src: none CSP settings. This setting prevents the use of CSS in the SVG file, so the created page will look strange.
While the page obviously looks strange, it's important to notice that since the domain checks out, the browser could fill out the login forms, making for a much more convincing page as shown below:
An error message can be used to make it look like an error in the system!
2. Server serves files directly from the upload directory:
In this setup, a server such ash nginx serves files in a static manner. The served files are loaded from a "public" folder made accessible through Directus as it's recommended in the files API docs. Quoting: "make a public folder and allow access to this", except the upload folder is directly served by the server:
Since the files loaded by the server are sourced directly from the file storage, the arbitrary file write might allow an attacker to upload a webshell into the folder, giving it an arbitrary file extension. As the extension checks out as a valid PHP file for instance, and the contents are correct code, an attacker can achieve unauthenticated code execution on the server.
3. Poisoning hosted files
The previous examples focused on active exploitation, but it's important to mention that the vulnerability allows for arbitrary changes in files. This can be used for many different attack primitives. Let's consider the following scenario: Directus is used not only to serve contents on a company's web page, but internally as well. Onboarding documents for new entries are hosted on the instance. Manuals with links to internal services are provided through PDF files. If the file can be accessed and modified by an attacker, it would be trivial to set up a spoofed instance which receives credentials for internal services but redirects to the original, internal service right after.
Credits
The bug was discovered by Zombor Máté, a security researcher at PCA Cyber Security (https://pcacybersecurity.com/)
Summary
Directus's Single Sign-On (SSO) login pages lacked a Cross-Origin-Opener-Policy (COOP) HTTP response header. Without this header, a malicious cross-origin window that opens the Directus login page retains the ability to access and manipulate the window object of that page. An attacker can exploit this to intercept and redirect the OAuth authorization flow to an attacker-controlled OAuth client, causing the victim to unknowingly grant access to their authentication provider account (e.g. Google, Discord).
Impact
A successful attack allows the attacker to obtain an OAuth access token for the victim's third-party identity provider account. Depending on the scopes authorized, this can lead to: - Unauthorized access to the victim's linked identity provider account - Account takeover of the Directus instance if the attacker can authenticate using the stolen credentials or provider session
Patches
This issue has been addressed by adding the Cross-Origin-Opener-Policy: same-origin HTTP response header to SSO-related endpoints. This header instructs the browser to place the page in its own browsing context group, severing any reference the opener window may hold.
Workarounds
Users who are unable to upgrade immediately can mitigate this vulnerability by configuring their reverse proxy or web server to add the following HTTP response header to all Directus responses: Cross-Origin-Opener-Policy: same-origin
Directus is a real-time API and App dashboard for managing SQL database content. Prior to 11.17.0, the PATCH /files/{id} endpoint accepts a user-controlled filenamedisk parameter. By setting this value to match the storage path of another user's file, an attacker can overwrite that file's content while manipulating metadata fields such as uploadedby to obscure the tampering. This vulnerability is fixed in 11.17.0.
Describe the Bug
In Directus, when a Flow with the "Webhook" trigger and the "Data of Last Operation" response body encounters a ValidationError thrown by a failed condition operation, the API response includes sensitive data. This includes environmental variables, sensitive API keys, user accountability information, and operational data.
This issue poses a significant security risk, as any unintended exposure of this data could lead to potential misuse.
!Image !Image !Image
To Reproduce
Steps to Reproduce: 1. Create a Flow in Directus with: - Trigger: Webhook - Response Body: Data of Last Operation 2. Add a condition that is likely to fail. 3. Trigger the Flow with any input data that will fail the condition. 4. Observe the API response, which includes sensitive information like: - Environmental variables ($env) - Authorization headers - User details under $accountability - Previous operational data.
Expected Behavior: In the event of a ValidationError, the API response should only contain relevant error messages and details, avoiding the exposure of sensitive data.
Actual Behavior: The API response includes sensitive information such as: - Environment keys (FLOWSENVALLOWLIST) - User accountability (role, user, etc.) - Operational logs (currentpayments, $last), which might contain private details.
Summary
When response caching is enabled (CACHEENABLED=true), the cache-key derivation in api/src/utils/get-cache-key.ts includes only version, path, query, and accountability.user (plus a conditional ip). Authorization context beyond user (share, role, roles, admin, app, policies) is not part of the key.
For share tokens this is load-bearing. Directus's share-authentication flow (api/src/services/shares.ts:100-105) issues a JWT without an id claim, so api/src/utils/get-accountability-for-token.ts never assigns accountability.user, leaving it null (the default from create-default-accountability.ts). Every share token, and every anonymous request, therefore reduces to user: null in the cache-key input. Two different shares (or an anonymous request and a share token) requesting the same URL with the same query produce identical cache keys. The first request populates the bucket with a permission-filtered response; subsequent hits from unrelated shares or anonymous clients receive that payload without any permission re-evaluation.
This is the web-cache pattern "authorization-dependent response cached under an unsegmented key" (cache key collision / missing authorization context in cache key, CWE-524 and CWE-639). Two adjacent read populations collide:
- Share to share: Share A populates the cache, Share B reads Share A's scoped response. - Share to anonymous (and the reverse): any unauthenticated client hitting the same URL retrieves cached share-scoped data without presenting any token.
Affected
- Config required: CACHEENABLED=true (any store: memory, redis, memcached) plus at least one active directusshares row. This is not a default-on bug: CACHEENABLED ships as false. The cache is documented as a production performance setting, so operators who enable it are the ones affected.
Vulnerability class
- CWE-524: Use of Cache Containing Sensitive Information - CWE-639: Authorization Bypass Through User-Controlled Key (the key here is the derived cache key, not the URL) - OWASP API3:2023: Broken Object Property Level Authorization
Impact
- Cross-share confidentiality breach. Any share's filtered response can be served to a holder of a different share token, or to an anonymous request, that hits the same URL and query. Shares are advertised as a mechanism to distribute scoped, read-only access to specific items; this bug makes every cached share response readable by any other share-token holder who can reach the URL (the item-detail admin UI uses a predictable pattern). - Anonymous request can read share data. Anonymous requests also compute user=null. An anonymous client hitting /items/articles?fields= after a share request has populated the cache receives the share's scoped payload with zero authentication. - Password-protected share, derivative effect. Password protection lives only at shares.login (JWT issuance). Once any share has populated the cache for a URL, an anonymous or alternate-share request to that URL retrieves the cached payload without exchanging the share password. This is the same cache-key collision surfacing as a password-protection bypass symptom, not a distinct mechanism. - Persistence. The leak persists for the CACHETTL window (commonly 5 to 30 minutes). CACHEAUTOPURGE clears on mutating writes to cached collections but does not purge per-user or per-share. With CACHESTORE=redis (common in production) the poisoned bucket survives server restarts. - No write impact. The bypass is read-only.
Scope of the leak depends on the share's permission surface. A share with no backing role (directusshares.role = null, the default) collapses visibility to the primary key, so the cache leaks only PKs. A share backed by a role with broader field access (the intended production setup for distributing useful content) leaks the full content that role can see on the scoped item.
Password reset vulnerable to accent confusion
The password reset mechanism of the Directus backend is implemented in a way where combined with (specific, need to double check if i can work around) configuration in MySQL or MariaDB. As such, it allows attackers to receive a password reset email of a victim user, specifically having it arrive at a similar email address as the victim with a one or more characters changed to use accents.
This is due to the fact that by default MySQL/MariaDB are configured for accent-insenstive and case-insensitve comparisons.
MySQL weak comparison: sql select 1 from directususers where 'julian@cure53.de' = 'julian@cüre53.de';
This is exploitable due to an error in the API using the supplied email address for sending the reset password mail instead of using the email from the database.
Steps to reproduce:
1. If the attacker knows the email address of the victim user, i.e., julian@cure53.de. (possibly just the domain could be enough for an educated guess) 2. A off-by-one accented domain cüre53.de can be registered to be able to receive emails. 3. With this email the attacker can request a password reset for julian@cüre53.de. http POST /auth/password/request HTTP/1.1 Host: example.com [...] {"email":"julian@cüre53.de"} 4. The supplied email (julian@cüre53.de) gets checked against the database and will match the non-accented email julian@cure53.de and will continue to email the password reset link to the provided email address instead of the saved email address. 5. With this email the attacker can log into the target account and use it for nefarious things
Workarounds Should be possible with collations but haven't been able to confirm this.
References - https://www.monolune.com/articles/what-is-the-utf8mb40900aici-collation/ - https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-sets.html
Summary
Directus' TUS resumable upload endpoint (/files/tus) allows any authenticated user with basic file upload permissions to overwrite arbitrary existing files by UUID. The TUS controller performs only collection-level authorization checks, verifying the user has some permission on directusfiles, but never validates item-level access to the specific file being replaced. As a result, row-level permission rules (e.g., "users can only update their own files") are completely bypassed via the TUS path while being correctly enforced on the standard REST upload path.
Impact
- Arbitrary file overwrite: Any authenticated user with basic TUS upload permissions can overwrite any file in directusfiles by UUID, regardless of row-level permission rules. - Permanent data loss: The victim file's original stored bytes are deleted from storage and replaced with attacker-controlled content. - Metadata corruption: The victim file's database record is updated with the attacker's filename, type, and size metadata. Privilege escalation potential: If admin-owned files (e.g., application assets, templates) are stored in directusfiles, a low-privilege user could replace them with malicious content.
Workaround
Disable TUS uploads by setting TUSENABLED=false if resumable uploads are not required.
Credit
This vulnerability was discovered and reported by bugbunny.ai.
Summary
Aggregate functions (min, max) applied to fields with the conceal special type incorrectly return raw database values instead of the masked placeholder. When combined with groupBy, any authenticated user with read access to the affected collection can extract concealed field values, including static API tokens and two-factor authentication secrets from directususers.
Details
Fields marked with conceal are protected by payload processing logic that replaces real values with a masked placeholder on read. This protection works correctly for standard item queries, but aggregate query results are structured differently, operations are nested under their function name rather than appearing as flat field keys. The masking logic does not account for this nested structure, causing it to silently skip concealed fields in aggregate responses and return their raw values to the client.
Impact
- Account Takeover An authenticated attacker can harvest static API tokens for all users, including administrators, enabling immediate authentication as any account without credentials.
- 2FA Bypass TOTP seeds stored in directususers can similarly be extracted, allowing an attacker to bypass two-factor authentication for any account.
Summary Directus >=9.23.0, <=v10.5.3 improperly handles in, nin operators. It evaluates empty arrays as valid so expressions like {"role": {"in": $CURRENTUSER.somefield}} would evaluate to true allowing the request to pass.
Details This results in Broken Access Control because the rule fails to do what it was intended to do: Pass rule if field matches any of the values. ref: https://docs.directus.io/reference/filter-rules.html#filter-operators In my example this would translate to "Pass rule if <collection>.role matches any of []". Which should fail. This instead passes in Directus <= v10.5.3, >=v9.23.0
PoC {"role": {"in": $CURRENTUSER.somefield}} field validation would pass if $CURRENTUSER.somefield is null.
Real scenario: Using https://github.com/u12206050/directus-extension-role-chooser with the specified versions of Directus (I tested on 10.0.0) allows users with access to this feature set their role to whatever role if they don't have any roles assigned (userroles.role is left with the default value, null) despite the validation rule being yaml validation: role: in: $CURRENTUSER.userroles.role Latest version of Directus (v10.8.3 and above) handles the above validation rule correctly.
Impact Permissions fail to open for setups relying on this filter and can lead to users getting access to things they're not supposed to.
Summary A Server-Side Request Forgery (SSRF) protection bypass has been identified and fixed in Directus. The IP address validation mechanism used to block requests to local and private networks could be circumvented using IPv4-Mapped IPv6 address notation.
Details Directus implements an IP deny-list to prevent server-side requests to internal/private network ranges. The validation logic failed to normalize IPv4-Mapped IPv6 addresses (e.g., the IPv6 representation of 127.0.0.1) before checking them against the deny-list. Because the deny-list check did not recognize these mapped addresses as equivalent to their IPv4 counterparts, an attacker could bypass the restriction while the underlying HTTP client and operating system still resolved and connected to the intended private target.
This has been fixed by adding a normalization step that converts IPv4-Mapped IPv6 addresses to their canonical IPv4 form prior to validation.
Impact An authenticated user (or an unauthenticated user if public file-import permissions are enabled) could exploit this bypass to perform SSRF attacks against internal services on the same host (databases, caches, internal APIs) or cloud instance metadata endpoints (e.g., AWS/GCP/Azure IMDS).
Summary
The SSRF protection on Directus's file-import-from-URL feature can be bypassed using the address 0.0.0.0. While 127.0.0.1 and other internal addresses are denied, 0.0.0.0 is not added to the blocklist. On Linux and macOS, connecting to 0.0.0.0 reaches localhost, so an authenticated user with file-upload rights can make the server fetch internal services and retrieve the response as a downloadable file (full-read SSRF).
Affected Versions
- Affected: Directus <= 12.0.0 (confirmed on directus/directus:latest, v11.17.3, with default configuration) - Patched: Directus >= 12.0.0
Details
Directus uses a deny-list config, IMPORTIPDENYLIST, whose default value is ['0.0.0.0', '169.254.169.254'].
The issue is in how api/src/request/is-denied-ip.ts processes this list. When it encounters the entry 0.0.0.0, it treats it as a special keyword meaning "block all local network interfaces," but it never blocks the literal address 0.0.0.0 itself. The handler sets the network-interface flag and skips to the next entry without adding 0.0.0.0 to the blocklist.
What actually gets blocked is the loopback subnet 127.0.0.0/8 (from the lo interface) plus whatever addresses are assigned to the machine's network interfaces. The address 0.0.0.0 is not inside 127.0.0.0/8; it belongs to the separate 0.0.0.0/8 range. So a request to http://0.0.0.0:8055/ passes the blocklist check as "allowed."
At the OS level, however, connecting to 0.0.0.0 reaches localhost, functionally equivalent to 127.0.0.1. The same gap applies to the IPv6 unspecified address ::. As a result, the SSRF protection is bypassed.
Impact
An authenticated user with create permission on directusfiles (file-upload rights) can make the server issue requests to its own localhost via the /files/import endpoint. The response body is stored as a downloadable file, making this a full-read SSRF. On bare-metal or single-host deployments, this can reach databases, caches, and internal APIs bound to localhost. This bypass defeats the protections tracked as CVE-2026-35409 and CVE-2024-46990.
Describe the Bug
Providing a non-numeric length value to the random string generation utility will create a memory issue breaking the capability to generate random strings platform wide. This creates a denial of service situation where logged in sessions can no longer be refreshed as sessions depend on the capability to generate a random session ID.
To Reproduce
1. Test if the endpoint is working and accessible, GET http://localhost:8055/utils/random/string 2. Do a bad request GET http://localhost:8055/utils/random/string?length=foo 3. After this all calls to GET http://localhost:8055/utils/random/string will return an empty string instead of a random string 4. In this error situation you'll see authentication refreshes fail for the app and api.
Impact
This counts as an unauthenticated denial of service attack vector so this impacts all unpatched instances reachable over the internet.
Impact When relying on SSO providers in combination with local authentication it can be possible to enumerate existing SSO users in the instance. This is possible because if an email address exists in Directus and belongs to a known SSO provider then it will throw a "helpful" error that the user belongs to another provider.
Reproduction
1. Create a user using a SSO provider test@directus.io. 2. Try to log-in using the regular login form (or the API) 3. When using a valid email address
| APP | API | | --- | --- | | !image | !image |
4. When using an invalid email address
| APP | API | | --- | --- | | !image | !image |
5. Using this differing error it is possible to determine whether a specific email address is present in the Directus instance as an SSO user.
Workarounds When only using SSO for authentication then you can work around this issue by disabling local login using the following environment variable AUTHDISABLEDEFAULT="true"
References Implemented as feature in https://github.com/directus/directus/pull/13184 https://owasp.org/www-project-web-security-testing-guide/v42/4-WebApplicationSecurityTesting/03-IdentityManagementTesting/04-TestingforAccountEnumerationandGuessableUserAccount
Summary When setting WEBSOCKETSGRAPHQLAUTH or WEBSOCKETSRESTAUTH to "public", an unauthenticated user is able to do any of the supported operations (CRUD, subscriptions) with full admin privileges.
Details Accountability for unauthenticated WebSocket requests is set to null, which used to be "public permissions" until the Permissions Policy update which now defaults that to system/admin level access. So instead of null we need to make use of createDefaultAccountability() to ensure public permissions are used for unauthenticated users.
PoC 1. Start directus with bash WEBSOCKETSENABLED=true WEBSOCKETSGRAPHQLAUTH=public WEBSOCKETSRESTAUTH=public
2. Subscribe using GQL or REST or do any CRUD operation on a user created collection (system tables are not reachable with crud) gql subscription { directususersmutated { key event data { id email firstname lastname password } } } or json { "type": "items", "action": "read", "collection": "yourcollectionname" } 3a. Open up the data studio as any user. Observe how the subscriber gets notified on each page navigation (because the users lastpage gets updated, the password fields is properly redacted here)
3b. Observe receiving all available items from the yourcollectionname collection.
Impact
This impacts any Directus instance that has either WEBSOCKETSGRAPHQLAUTH or WEBSOCKETSRESTAUTH set to public allowing unauthenticated users to subscribe for changes on any collection or do REST CRUD operations on user defined collections ignoring permissions.
Summary It seems that any Directus installation that has websockets enabled can be crashed if the websocket server receives an invalid frame. This could probably be posted as an issue and I might even be able to put together a pull request for a fix (if only I had some extra time...), but I decided to instead post as a vulnerability just for the maintainers, since this seemingly can be used to crash any live Directus server if websockets are enabled, so public disclosure is not a good idea until the issue is fixed.
Details The fix for this seems quite simple; the websocket server just needs to properly catch the error instead of crashing the server. See for example: https://github.com/websockets/ws/issues/2098
PoC - Start a fresh Directus server (using for example the compose file here: https://docs.directus.io/self-hosted/docker-guide.html). Enable websockets by setting WEBSOCKETSENABLED: 'true' environment variable. - run a separate node app somewhere else to send an invalid frame to the server:
const WebSocket = require("ws"); const websocket = new WebSocket("ws://0.0.0.0:8055/websocket"); websocket.on("open", function () { const chunk = Buffer.from("a180", "hex"); websocket.socket.write(chunk); });
Impact The server crashes with an error: RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear. Server needs to be manually restarted to get back online (if there's no recovery mechanism in place, as there often isn't with simple node servers). This was confirmed on a local server, and additionally I was able to crash our staging server with the same code, just pointing to our staging Directus server running at fly.io. It seems to also crash servers running in the directus.cloud service. I created https://websocket-test.directus.app/, pointed the above script to the websocket url of that instance and the server does crash for a while. It seems that in there there's a mechanism for bringing the server back up quite fast, but it would be quite trivial for anyone to DoS any server running in directus.cloud by just spamming these invalid frames to the server.
Directus is a real-time API and App dashboard for managing SQL database content. Directus is vulnerable to Server-Side Request Forgery (SSRF) when importing a file from a remote web server (POST to /files/import). An attacker can bypass the security controls by performing a DNS rebinding attack and view sensitive data from internal servers or perform a local port scan. An attacker can exploit this vulnerability to access highly sensitive internal server(s) and steal sensitive information. This issue was fixed in version 9.23.0.
Summary Unauthenticated user can access credentials of last authenticated user via OpenID or OAuth2 where the authentication URL did not include redirect query string.
For example: - Project is configured with OpenID or OAuth2 - Project is configured with cache enabled - User tries to login via SSO link, but without redirect query string - After successful login, credentials are cached - If an unauthenticated user tries to login via SSO link, it will return the credentials of the other last user
The SSO link is something like https://directus.example.com/auth/login/openid/callback, where openid is the name of the OpenID provider configured in Directus
Details This happens because on that endpoint for both OpenId and Oauth2 Directus is using the respond middleware, which by default will try to cache GET requests that met some conditions. Although, those conditions do not include this scenario, when an unauthenticated request returns user credentials. For OpenID, this can be seen here: https://github.com/directus/directus/blob/main/api/src/auth/drivers/openid.ts#L453-L459 And for OAuth2 can be seen here https://github.com/directus/directus/blob/main/api/src/auth/drivers/oauth2.ts#L422-L428
PoC - Create a new Directus project - Set CACHEENABLED to true - Set CACHESTORE to redis for reliable results (if using memory with multiple nodes, it may only happen sometimes, due to cache being different for different nodes) - Configure REDIS with redis string or redis host, port, user, etc. - Set AUTHPROVIDERS to openid - Set PUBLICURL to the the main URL of your project . For example, PUBLICURL: http://localhost:8055 - Configure AUTHOPENIDCLIENTID, AUTHOPENIDCLIENTSECRET, AUTHOPENIDISSUERURL with proper OpenID configurations - Be sure that on OpenID external app you have configured Redirect URI to http://localhost:8055/auth/login/openid/callback - Run Directus - Open the SSO link like http://localhost:8055/auth/login/openid/callback - Do the authentication on the OpenID external webpage - Verify that it you got redirected to a page with a JSON including accesstoken property - Be sure all anonymous mode windows are closed - Open an anonymous window and go to the SSO Link http://localhost:8055/auth/login/openid/callback and see you have the same credentials, even though you don't have any session because you are in anonymous mode
Impact All projects using OpenID or OAuth 2, that does not include redirect query string on loggin in users.
Summary A denial of service (DoS) attack by field duplication in GraphQL is a type of attack where an attacker exploits the flexibility of GraphQL to overwhelm a server by requesting the same field multiple times in a single query. This can cause the server to perform redundant computations and consume excessive resources, leading to a denial of service for legitimate users.
Details Request to the endpoint /graphql are sent when visualizing graphs generated at a dashboard: !image
!DoS5
By modifying the data sent and duplicating many times the fields a DoS attack is possible.
PoC The goal is to create a payload that generates a body like this, where the 'max' field is duplicated many times, each with the 'id' field duplicated many times inside it. {'query': 'query { query4f4722ea: testtableaggregated { max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } max {id id id id id id id id id id } } }'}
Although that payload seems harmless, a bigger one leaves the service unresponsive.
The following code might serve as a PoC written in Python3: # Field Duplication DoS GitHub @asantof
import requests
CHANGE THIS VALUES: url, authtoken, queryname, collectionname url = 'http://0.0.0.0:8055/graphql' authtoken = '' queryname = 'queryXXXXX' collectionname = ''
headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {authtoken}', }
idpayload = 'id ' 200 maxpayload = 'max {' + idpayload + ' } ' fullpayload = maxpayload 200
data = { 'query': 'query { ' + queryname + ': ' + collectionname + 'aggregated { ' + fullpayload + ' } }' }
print(data)
response = requests.post(url, headers=headers, json=data)
print(response.json())
!DoS4
After running it the service will be unresponsive for a while: !DoS
Impact The vulnerability impacts the service's availability by causing it to become unresponsive for a few minutes. An attacker could continuously send this request to the server, rendering the service unavailable indefinitely.
Summary
Directus Flows with a manual trigger are not validating whether the user triggering the Flow has permissions to the items provided as payload to the Flow. Depending on what the Flow is set up to do this can lead to the Flow executing potential tasks on the attacker's behalf without authenticating.
Impact
Bad actors could execute the manual trigger Flows without authentication, or access rights to the said collection(s) or item(s).
Users with manual trigger Flows configured are impacted as these endpoints do not currently validate if the user has read access to directusflows or to the relevant collection/items. The manual trigger Flows should have tighter security requirements as compared to webhook Flows where users are expected to perform do their own checks.
Workarounds Users have to implement permission checks for read access to Flows and read access to relevant collection/items.
Summary
A vulnerability allows authenticated users to search concealed/sensitive fields when they have read permissions. While actual values remain masked (), successful matches can be detected through returned records, enabling enumeration attacks on sensitive data.
Details
The system permits search operations on concealed fields in the directususers collection, including token, tfasecret, password. Matching records are returned with masked values, but their presence confirms the searched value exists.
The "Recommended Defaults" for "App Access" grant users full read permissions to their role/user records, inadvertently enabling them to search for any user's tokens, TFA secrets, and password hashes. Attackers can leverage known password hashes from breach databases to identify accounts with compromised passwords.
Impact
This vulnerability enables: - Token enumeration - Verification of valid authentication tokens - Password hash matching - Identification of accounts using known compromised passwords - Information disclosure - Confirmation of sensitive value existence without viewing actual data - Increased attack surface - Default permissions automatically expose all deployments using recommended settings
The risk is particularly high for password fields, where attackers can cross-reference publicly available hash databases to identify vulnerable accounts.
Summary
Directus' GraphQL endpoints (/graphql and /graphql/system) did not deduplicate resolver invocations within a single request. An authenticated user could exploit GraphQL aliasing to repeat an expensive relational query many times in a single request, forcing the server to execute a large number of independent complex database queries concurrently, multiplying database load linearly with the number of aliases. The existing token limit on GraphQL queries still permitted enough aliases for significant resource exhaustion, while the relational depth limit applied per alias without reducing the total number executed. Rate limiting is disabled by default, meaning no built-in throttle prevented this from causing CPU, memory, and I/O exhaustion that could degrade or crash the service. Any authenticated user, including those with minimal read-only permissions, could trigger this condition.
Fix
A request-scoped resolver deduplication mechanism was introduced and applied broadly across all GraphQL read resolvers, both system and items endpoints. When multiple aliases in a single request invoke the same resolver with identical arguments, only the first call executes; all subsequent aliases share its result. This eliminates the amplification factor regardless of how many aliases a query contains.
Impact
- Service degradation or outage: Concurrent complex database queries exhaust the connection pool and server resources, affecting all users - Low privilege required: Any authenticated user, including those with read-only access to a single collection, can trigger this condition - Linear scaling: Impact scales with the number of aliases and depth of relational queries - Compounded by concurrency: Multiple simultaneous requests multiply the effect further
Directus is a real-time API and App dashboard for managing SQL database content. Prior to 11.17.0, Directus stores revision records (in directusrevisions) whenever items are created or updated. Due to the revision snapshot code not consistently calling the prepareDelta sanitization pipeline, sensitive fields (including user tokens, two-factor authentication secrets, external auth identifiers, auth data, stored credentials, and AI provider API keys) could be stored in plaintext within revision records. This vulnerability is fixed in 11.17.0.
Summary
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor Access to information you should not have access to when the permissions rely on $CURRENTUSER for filtering.
Details
The permission filters (i.e. usercreated IS $CURRENTUSER) are not properly checked when using GraphQL subscription resulting in unauthorized users getting event on their subscription which they should not be receiving according to the permissions. This can be any collection but out-of-the box the directususers collection is configured with such a permissions filter allowing you to get updates for other users when changes happen.
An example: graphql subscription { directususersmutated { event data { id lastaccess lastpage } } }
Patches https://github.com/directus/directus/pull/19155
Workarounds Disable GraphQL Subscriptions
References
Directus is a free and open-source data platform for headless content management. The Directus process can be aborted by having an authorized user update the filenamedisk value to a folder and accessing that file through the /assets endpoint. This vulnerability has been patched and release v9.15.0 contains the fix. Users are advised to upgrade. Users unable to upgrade may prevent this problem by making sure no (untrusted) non-admin users have permissions to update the filenamedisk field on directusfiles.
An issue found in Directus API v.2.2.0 allows a remote attacker to cause a denial of service via a great amount of HTTP requests.
Security Advisory: Open Redirect in Directus SAML Authentication
Summary
An open redirect vulnerability exists in the Directus SAML authentication callback endpoint. The RelayState parameter is used in redirects without proper validation against an allowlist of permitted domains.
Vulnerability Description
During SAML authentication, the RelayState parameter is intended to preserve the user's original destination. However, while the login initiation flow validates redirect targets against allowed domains, this validation is not applied to the callback endpoint. This allows an attacker to craft a malicious authentication request that redirects users to an arbitrary external URL upon completion.
The vulnerability is present in both the success and error handling paths of the callback.
Impact
- Phishing: Users can be redirected to attacker-controlled sites that mimic legitimate login pages - Credential theft: Chained attacks may leverage the redirect to capture OAuth tokens or authorization codes - Trust erosion: Users may lose confidence in the application's security posture
This vulnerability can be exploited without authentication.
Summary
An open redirect vulnerability exists in the login redirection logic. The isLoginRedirectAllowed function fails to correctly identify certain malformed URLs as external, allowing attackers to bypass redirect allow-list validation and redirect users to arbitrary external domains upon successful authentication.
Details
A parser differential exists between the server-side URL validation logic and how modern browsers interpret URL path segments containing backslashes. Specifically, certain URL patterns are incorrectly classified as safe relative paths by the server, but are normalized by browsers into external domain references.
This is particularly impactful in SSO authentication flows (e.g., OAuth2 providers), where an attacker can craft a login URL that redirects the victim to an attacker-controlled site immediately after successful authentication, without any visible indication during the login process.
Impact
- Phishing: Users may be silently redirected to attacker-controlled sites impersonating legitimate services after authenticating. - Credential/token theft: The redirect can be chained to capture OAuth tokens or authorization codes. - Trust erosion: Users lose confidence in the application after being redirected to unexpected domains post-login.
Summary The Comment feature has implemented a filter to prevent users from adding restricted characters, such as HTML tags. However, this filter operates on the client-side, which can be bypassed, making the application vulnerable to HTML Injection.
Details The Comment feature implements a character filter on the client-side, this can be bypassed by directly sending a request to the endpoint.
Example Request:
PATCH /activity/comment/3 HTTP/2 Host: directus.local
{ "comment": "<h1>TEST <p style=\"color:red\">HTML INJECTION</p> <a href=\"//evil.com\">Test Link</a></h1>" }
Example Response:
json { "data": { "id": 3, "action": "comment", "user": "288fdccc-399a-40a1-ac63-811bf62e6a18", "timestamp": "2023-09-06T02:23:40.740Z", "ip": "10.42.0.1", "useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", "collection": "directusfiles", "item": "7247dda1-c386-4e7a-8121-7e9c1a42c15a", "comment": "<h1>TEST <p style=\"color:red\">HTML INJECTION</p> <a href=\"//evil.com\">Test Link</a></h1>", "origin": "https://directus.local", "revisions": [] } }
Example Result:
!Screenshot 2023-09-06 094536
Impact
With the introduction of session cookies this issue has become exploitable as a malicious script is now able to do authenticated actions on the current users behalf.
Summary
A stored cross-site scripting (XSS) vulnerability exists that allows users with upload files and edit item permissions to inject malicious JavaScript through the Block Editor interface. Attackers can bypass Content Security Policy (CSP) restrictions by combining file uploads with iframe srcdoc attributes, resulting in persistent XSS execution.
Details
The vulnerability arises from insufficient sanitization in the Block Editor interface when processing JSON content containing HTML elements. The attack requires two permissions: - upload files - To upload malicious JavaScript files - edit item - To create or modify content with the Block Editor
Attack Vector:
1. JavaScript File Upload: Attackers upload a malicious JavaScript file via the files endpoint, obtaining a file ID accessible through the assets directory
2. Block Editor Exploitation: Using a JSON field with Block Editor interface, attackers inject raw HTML containing an iframe with srcdoc attribute that references the uploaded file
3. CSP Bypass: The iframe srcdoc technique circumvents existing CSP protections by creating a new document context that loads the uploaded script
The payload is injected through direct API manipulation (PATCH request) to bypass client-side validation, targeting the Block Editor's paragraph data structure within the JSON content field.
Impact
This vulnerability enables: - Persistent XSS - Malicious scripts execute whenever affected content is viewed - Session hijacking - Access to authentication tokens and cookies of users viewing the content - Administrative compromise - If administrators view infected content, their elevated privileges can be exploited - CSP bypass - Demonstrates ineffective security controls, potentially affecting other protections - Data exfiltration - Ability to steal sensitive information displayed in the application - Phishing attacks - Injection of convincing fake login forms or malicious redirects