The Sonar writeup of WordPress's file-delete-to-RCE is one of the cleanest demonstrations of why "low impact" file-deletion bugs deserve full attention. The original disclosure showed how an authenticated user, with no special privileges beyond uploading media, could escalate to arbitrary code execution through a sequence of mundane-looking primitives.
The chain, simplified
The bug gave an attacker the ability to delete files inside the WordPress installation. That, on paper, is a defacement / availability issue. In practice, here's how it became RCE:
- Delete
.htaccess. WordPress installs ship an.htaccessinwp-content/uploads/that prevents Apache from executing PHP files in that directory. Delete that file and the directory is back to default — anything inside it is now executable. - Upload a crafted PHP file via the media uploader. Image uploads run through a sanitisation pipeline, but you can stash PHP into the EXIF or comment metadata of an image and let the file land on disk with a
.phpextension if you have control over the filename — which the bug allowed. - Visit the uploaded file's URL. PHP runs.
No part of that chain is novel. Each primitive is "small" on its own. The composition is the whole game.
Why this pattern keeps coming back
- Sanitisation pipelines focus on content, not on what the file is allowed to do. A pipeline that strips active content from an image is doing useful work — but it doesn't decide whether the destination directory should execute scripts.
- The defense-in-depth on uploads is fragile. A single
.htaccess, a singlephp_admin_flag engine offin the vhost, a single nginxlocationblock — knock any one of them out and execution comes back. - "Authenticated low-privilege" is a wide door. Sites with open registration, comment-author roles, or WooCommerce customer accounts give that role away to anyone.
How to harden against it
- Block PHP execution in uploads at the server level, not just at the application level. Apache
<FilesMatch>directive in the vhost, nginxlocation ~ \.php$ { deny all; }inside/wp-content/uploads/. Don't rely on.htaccessalone — if an attacker can delete it, your only defense is gone. - Read-only uploads where possible. For many marketing sites, nothing legitimate needs to land in uploads after launch. Mount the directory read-only and use a separate write path for the admin.
- Cap mime types at the web server level. Even if WordPress thinks the file is an image, the server can decide to refuse to serve anything that isn't.
- Watch for
.htaccessmodifications. A monitoring rule that fires whenever an.htaccessinside the WordPress tree changes will catch this and a dozen other variants.
If you'd like the upload-hardening review done as a standalone, that's part of our hardening service. If you suspect a site already went through this kind of chain, open an engagement and we'll diff the file tree.