Wipeout as a Service

Table of Contents

Introduction

Welcome to my first blog article, hopefully first of many!


Today I want to talk about a class of vulnerabilities with disproportionate ability
to damage organizations - mass deletion bugs. These bugs at their core are other
vulnerabilities (IDOR / BAC / CSRFs / etc.) but their danger lies in their impact. They
may appear due to technical flaws in code, or even insecure design decisions.


This article has ideas on

  • why these bugs appear,
  • what factors determine their blast radius,
  • how to safely test them, and
  • how the principle of zero trust can help engineer more robust and secure systems.

Note : One example in this article is from a public report, courtesy z3phyrus (researcher), Mozilla and Hackerone.


Technical Vulnerabilities

Let’s start with our first bug :

1. Cross-Tenant Destructive IDOR

An eCommerce website had a coupon feature, allowing sellers to publish and share coupons for their products. Coupons could be public (visible on product page) or private (for controlled distribution). There was an option to delete the coupons you have published, and its POST request looked like this :

1
2
3
POST /api/seller/<productID>/coupon/<couponID>/delete HTTP/1.1
Host: example.com
Authorization: Bearer <accessToken>

I set the accessToken of my first account, and the productID and couponID of a product and coupon owned by my second account. It failed with a 403 Unauthorized. But the fact that they were asking for two identifiers seemed a little out-of-place to me. So I set the accessToken and productID of my first account, but kept the couponID of a coupon owned by the second account.

1
2
3
4
5
6
HTTP/1.1 200 OK
  
{
  "success": true,
  "message": "Coupon deleted successfully"
}

It worked. As long as you kept the productID to a product you own, you could delete any coupon, or even worse, run a Burp Intruder task to mass-delete all coupons on the platform. It seems the code at the backend verified if the product belonged to the seller, but failed to verify the coupon was actually for the product. This multi-layer check vulnerability may have higher chances of slipping through developers, since at least one access check was performed.

2. Partially Fixed Notification Toggle

I was once contracted by a small org. to test the security patches they had pushed based on a SAST scan, and it was a whitebox assessment. One of the features I tested was the email notification toggle. The original vulnerability was a simple IDOR, with the backend taking userID from the POST body. The backend fix correctly added a line to fetch userID from JWT, and the frontend also stopped including userID in the POST body. However, I noticed that they never removed the code responsible for processing the userID from POST req.

So I intercepted the request (which now did not include userID by default). and manually added the userID parameter :

1
2
3
4
5
6
7
8
POST /profile/notifications/toggle HTTP/1.1
Host: example.com
Authorization: Bearer <accessToken>
  
{
  "userID": 123123123, // Not my userID,
  "channel": "email"
}

The endpoint still accepted it (overwriting the one fetched from JWT), and thus the vulnerability was still live. One user with a simple cURL script could flip email notifications choices across the complete product. What I find funny is that the endpoint provided no way to determine if the toggle switched the notifications on or off, and so, in the wake of such an attack, users who had the notifications turned off could start receiving mails.


This broken fix shows the importance of retesting; and how access to code can help you do a more thorough job. I don’t think I would catch this bug without seeing the patch code. Also interesting is the observation that order of actions in a function matter. Had the userID DB fetch happened after processing user input, the DB data would not be overwritten and the function would be incidentally safe.

💡
Using UUID as ID values would massively decrease the blast radius of the above two bugs, provided it was not trivial to enumerate those ID.

3. Auth Bypass in Delete Account Feature

This is a vulnerability report from Hackerone ↗ for Mozilla, let’s look at it progressively.

When you request to delete your account, this is the POST request sent :

1
2
3
4
5
6
7
8
POST /v1/account/destroy HTTP/2
Host: api.accounts.firefox.com
Authorization: Bearer <accessToken>
  
{
  "email": "attacker@gmail.com",
  "authPW": "42b4c2940....c21900caff"
}

If we simply try to modify the email field in the JSON body, the request is obviously rejected. This happens due to the authPW field, which contains a hashed copy of the user’s password. Since we cannot guess anyone’s password (or its hash), the step seems secure. However, things get interesting when SSO is involved. Suppose a victim creates their account through SSO login. A password is never set. The password column value should be set to NULL. Assuming our victim has logged-in through SSO, we also create an account through SSO. Now let’s test the delete flow again by replacing the attacker’s email with the victim email :

1
2
3
4
5
6
7
8
POST /v1/account/destroy HTTP/2
Host: api.accounts.firefox.com
Authorization: Bearer <accessToken>
  
{
  "email": "victim@gmail.com",
  "authPW": "42b4c2940....c21900caff"
}
1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Server: nginx
Location: http://firefox.com/login
  
{
  "success": true,
  "message": "Account deleted successfully"
}

It worked! Clearly, the authPW method is insufficient for SSO edge-case. But that’s not the actual vulnerability here! Asking for password, or in this case ideally an SSO relogin, is just an additional layer of checks before a destructive action such as deleting the account is executed.


The primary check - reauthenticating the logged-in session - is altogether missing here. Either the endpoint should just determine the userID directly from the session cookie (instead of asking it in the POST body), or if it does accept the user-controlled email field, the server should have verify that the email in POST body actually belongs to the logged-in user making the request.

Due to this vulnerability, even though the attacker was not actually logged in with the victim’s account in the above example, a hash collision on NULL value (or whatever the internal error) allowed the victim’s account to be deleted. This can be targeted into a mass-deletion vulnerability in case we know an entire org. uses eg. Google-based SSO login for a product.


Vulnerable by Design

These mass-destruction bugs might not always exist in a system as purely security vulnerabilities. Sometimes, they present themselves as features. This can happen due to :

  • bandaid fixes (coded temporarily during high traffic and left as-is)
  • functionality designed without security in mind

1. Irreversible Deletion Without Safeguards

Around 6 months ago I was testing the web-app product of a mid-size eCommerce brand. In the feature column, they had a feature called Delete Rows. The page had 3 fields, named table, to and from.

Delete Rows Tool
Table
From
To

Devs told me that the feature was used by inventory staff to delete duplicated product rows in a pinch. I was able to test the feature out, being in the test environment, and for the lack of a better description, it was like cutting a piece of paper using a CNC machine. A single digit error in the ID (to or from) could wreak havoc. As cherry on cake, they had not implemented soft-delete, and had deep cascading built into the database - increasing possible destruction in a mishap.


Such features, although very useful in a pinch, are almost always disasters waiting to happen. While they did not want to remove the feature, on my advice they implemented soft-delete (with a +1 week cron-job for permanent deletion) AND the step of fetching + displaying the data to visually confirm you want to delete it, before the deletion took effect.

Delete Rows Tool
Table
From
To
Following rows will be deleted :
IDProduct NameDate Created
45524Wireless Mouse2026-03-28
45525Mechanical Keyboard2026-03-29
45526USB-C Hub2026-03-30
4552727-inch Monitor2026-03-31
45528Laptop Stand2026-04-01

Takeaways

As a Pentester

  • Test state-changing actions with caution, and destructive endpoints with extreme caution.
  • Avoid testing on production without safeguards; demand controlled environments.
  • Observe patterns: IDs, arrays, and role boundaries. Sometimes, there are more than one factors involved in authN / authZ.
  • Sometimes the root cause may differ from surface vulnerability. AI can help here as a sanity check.

As a Developer

  • Enforce robust authorization (object-level + tenant-level). Authorization must be enforced at the action boundary, not inferred.
  • Never trust client-supplied identifiers for scope.
  • Implement soft deletes, backups, and recovery mechanisms.
  • Design for failure: assume destructive actions will be triggered accidentally or maliciously.
  • Constrain destructive actions using rate-limits to massively decrease blast radius.
That's all for now, folks!

Please let me know about any typos or mistakes that may have crept up in this article.
Thankyou,
- Smit Verma.

Cat