Feb 20, 2026·7 min read·1 visit
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) |