Feb 20, 2026·7 min read·34 visits
Authenticated low-privileged users can trick the TYPO3 Recycler into permanently deleting any database record (hard delete). By sending crafted AJAX requests, attackers bypass table-level permission checks, effectively allowing a disgruntled editor to wipe out the entire site or admin accounts.
A critical Broken Access Control vulnerability in TYPO3 CMS allows authenticated backend users to weaponize the Recycler module. By exploiting flaws in how the DataHandler processes deletion requests for 'invisible' (soft-deleted) records, attackers can bypass permission checks and permanently purge data from any table—including critical system tables and user accounts—regardless of their actual privileges.
In the world of Content Management Systems, the "Recycler" (or Trash) is supposed to be a safety net. It's the place where deleted pages go to reflect on their mistakes before being restored or banished to the void. Theoretically, a user should only be able to interact with their own garbage. If I can't edit the sys_user table, I definitely shouldn't be able to empty the bin containing the root administrator account.
But TYPO3, in its infinite complexity, introduced a fascinating paradox. To manage the trash, the system uses a "soft delete" mechanism—flipping a database flag (deleted=1) rather than actually removing the row. This keeps the data safe but hides it from the frontend.
CVE-2025-59022 is what happens when that safety mechanism backfires. It turns out that the code responsible for permanently purging items from the Recycler got a little too enthusiastic. It assumed that if you were asking to take out the trash, you must have permission to do so, failing to verify if the trash in question was actually yours to touch. It’s like a janitor key that accidentally opens the CEO’s safe, just because the safe was marked for cleaning.
The core of this vulnerability lies in a logic gap within the DataHandler and how TYPO3 handles permission checks for "invisible" records. When a record is soft-deleted, it effectively vanishes from the standard permission scope. Most helper functions in TYPO3 are designed to fetch active records to determine if a user has edit or delete rights.
When a record has deleted=1, functions like calcPerms or isInWebMount often return false negatives or simply fail to locate the record context entirely because the database queries driving them include a default WHERE deleted=0 clause. The system effectively says, "I can't see this record, so I can't strictly enforce permissions on it."
The vulnerability is a classic "Fail Open" scenario. The DataHandler::deleteAction method—responsible for the final execution of the purge—neglected to explicitly verify tables_modify permissions for the specific table being targeted. It relied on upstream checks that were easily confused by the soft-deleted state of the target records.
Compounding this was the RecyclerAjaxController. This endpoint exposed the deleteRecords action to authenticated users. It accepted a list of identifiers (e.g., pages:42) and passed them straight to the DataHandler for hard deletion. Because the permissions check struggled to "see" the soft-deleted target, and the DataHandler didn't double-check the user's rights to modify that specific table, the operation proceeded. The result? Arbitrary file deletion.
Let's look at the patch to understand the severity of the omission. The fix was applied in TYPO3\CMS\Core\DataHandling\DataHandler. In the vulnerable versions, the deleteAction method would happily process a command to delete a record without verifying if the user had write access to the table housing that record.
Here is the critical diff from the patch (simplified for clarity):
// EXT:core/Classes/DataHandling/DataHandler.php
public function deleteAction($table, $id, $recordToDelete)
{
+ // THE FIX: Explicitly check if the user is allowed to modify this table
+ if (!$noRecordCheck && !$this->checkModifyAccessList($table)) {
+ $this->log($table, $id, 2, 0, 1, 'Attempt to delete record without delete permissions');
+ return;
+ }
+
// ... existing logic ...
}Before this check was added, the code essentially assumed that if the request reached this point, the user was authorized.
Furthermore, the RecyclerAjaxController was patched to perform a permission check before even calling the DataHandler. In EXT:recycler/Classes/Controller/RecyclerAjaxController.php, the developers had to explicitly allow the permission system to see deleted records:
// The controller now checks permissions explicitly
if (!$this->isDeleteAllowed($tableName, $uid)) {
// Throw 403 Forbidden
}The fix involves a new parameter $useDeleteClause in permission methods, allowing the system to toggle off the "invisibility cloak" of soft-deleted records so it can properly calculate permissions.
Exploiting this does not require a complex binary payload; it just requires a browser and a valid backend session. Imagine you are a low-level editor with access to the Recycler module (often standard for content editors).
The Setup:
be_users table (Backend Users). We want to delete the main Administrator account.The Attack Chain: First, the attacker logs into the TYPO3 backend. They open the browser's Developer Tools (Network Tab) and navigate to the Recycler module to capture a legitimate request structure.
Next, they construct a malicious POST request to the AJAX endpoint. The goal is to feed the deleteRecords action a record ID from a table they shouldn't touch.
POST /typo3/index.php?M=recycler&tx_recycler_recycler[action]=deleteRecords HTTP/1.1
Host: target-typo3.com
Cookie: be_typo_user=...
Content-Type: application/x-www-form-urlencoded
msg=output&records[]=be_users:1The Result:
The RecyclerAjaxController receives the request. It parses be_users:1. It passes this to DataHandler. The vulnerable DataHandler (pre-patch) sees the command to hard-delete User ID 1 (usually the super-admin). It doesn't check if the current user has tables_modify rights for be_users. It executes the SQL DELETE FROM be_users WHERE uid=1.
The admin is gone. Irreversibly. The attacker could just as easily target pages:1 to delete the root of the website, effectively taking the entire site offline.
While this is not Remote Code Execution (RCE), the business impact is arguably just as catastrophic. RCE lets you run code; this vulnerability lets you erase the business.
pages table), the entire frontend of the website serves 404s. Restoration requires database backups.sys_log), historic data, or user accounts, covering tracks for other malicious activities.The EPSS score is currently low (~0.00015) because this requires authentication, but for an insider threat or a scenario where a lower-privileged account is phished, this is a "Game Over" button for the site's data.
The patch is straightforward: Update. The TYPO3 team has released versions 10.4.55 LTS, 11.5.49 LTS, 12.4.41 LTS, 13.4.23 LTS, and 14.0.2 which include the necessary access control checks.
If you cannot update immediately (why?), you can mitigate this via configuration, though it limits functionality:
recycler system extension for all non-admin users via Backend User Groups.mod.recycler.allowDelete = 0Note for Developers: This serves as a grim reminder: relying on "soft delete" flags to hide data does not secure it. Always enforce strict permission checks at the lowest possible level (the DataHandler/Model), not just in the Controller or UI.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
TYPO3 CMS TYPO3 | >= 10.0.0, <= 10.4.54 | 10.4.55 |
TYPO3 CMS TYPO3 | >= 11.0.0, <= 11.5.48 | 11.5.49 |
TYPO3 CMS TYPO3 | >= 12.0.0, <= 12.4.40 | 12.4.41 |
TYPO3 CMS TYPO3 | >= 13.0.0, <= 13.4.22 | 13.4.23 |
TYPO3 CMS TYPO3 | >= 14.0.0, <= 14.0.1 | 14.0.2 |
| Attribute | Detail |
|---|---|
| CWE | CWE-862 (Missing Authorization) |
| CVSS v3.1 | 8.1 (High) |
| Attack Vector | Network (AJAX) |
| Privileges Required | Low (Authenticated Backend User) |
| Impact | High (Data Destruction / DoS) |
| EPSS Score | 0.00015 (Low Probability) |
A high-severity access control vulnerability in ToolHive CLI before v0.30.1 and ToolHive Studio before v0.38.0 allows local containerized MCP servers to bypass network isolation. This enables malicious workloads to establish TCP/IP connections to administrative and control plane endpoints exposed on the host loopback interface.
AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.
A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.
CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.