Attack Patterns · 2 of 5
Enumeration with No Rate Limit: When a Single Guessable ID Lets You Scan the Whole Database
This is the second post in a series on the most common attack patterns. After broken object-level authorization (where changing one number in the URL is enough) comes the pattern that almost always sits next to it: when nothing stops a script from trying thousands of numbers in a row.
What Is "Enumeration with No Rate Limit", Really?
Most records in an app have an ID: a user number, a card number, a national ID, sometimes even a phone number. When those IDs are sequential or guessable (1001, 1002, 1003, or national ID numbers in a known range), an attacker only needs to know what one valid request looks like. From there they write a short loop that tries the next number, and the one after that, and the one after that.
If the server returns data for every ID that exists, and nothing limits how many requests you can send per minute, that small flaw becomes a full walk of the database. Security people call this enumeration (or ID enumeration): listing every existing ID and pulling whatever is attached to it. Rate limiting is exactly the barrier that keeps that loop from running freely.
Note the difference from pattern 1: there, one foreign ID is enough to read one other person's record. Here the story is scale: the same weakness, thousands of times per second, until the whole list is in the attacker's hands.
How It Actually Happens
Suppose you have a "verify card" or "password reset by national ID" screen. The client sends a number, and the server returns a name and phone number, "to confirm it's really you." Now imagine a script that does exactly the same thing, only with a sequential list of numbers:
The misleading part: sometimes there is a login check, and sometimes even a partial ownership check, but if you can discover which IDs exist and what is attached to them at high speed, you can still build a full list. And often you do not even need to log in: a public "reset" or "verify" endpoint that returns too much information is enough.
An Example from the Press
The same pattern showed up outside Israel too: in November 2025 TechCrunch reported on U.S. state jury-management systems with sequential IDs and no limit on login attempts, which let someone scan sensitive personal records. And in December 2025, a "secure" messaging app with no limit on guessing phone numbers.
Read the article on TheMarker →
How to Fix It
There is no single magic button: there are layers that need to work together:
- Server-side rate limiting on every endpoint that returns data by a guessable ID (password reset, card verification, user lookup, login). After X attempts: slowdown, temporary lockout, or CAPTCHA. Without this, every other defense erodes under a script.
- Hard-to-guess IDs (a random UUID instead of 1001, 1002) reduce risk, but do not replace rate limiting, especially when the ID is a real-world national ID or phone number.
- Less information in the response: "If the account exists we sent an SMS" is better than "here is the name and phone of the person who owns this number." Every field that comes back in a 200 is another field you can scan for.
- And still: ownership checks (pattern 1), so an ID that leaks does not open someone else's record.
Note: hiding the ID on the client, or "not publishing the API," is not a fix. A script does not need a pretty screen. It needs a URL that works. The defense has to be on the server, on every attempt, even if it comes from a rotating IP or many accounts.
How to Check If Your App Is Exposed to This Pattern
This check is short, and worth doing on every screen that accepts an ID or phone number:
- Find an endpoint that accepts an ID (password reset, card lookup, profile by number).
- Manually send a few requests with nearby IDs (or phones in sequence) and see if useful data comes back.
- If it does, try at a higher rate (even with a simple tool or short script). If the server keeps answering with 200s with no slowdown and no 429, you have found the pattern.
- Also check what comes back when the ID does not exist: a different response ("not found" vs "SMS sent") can itself leak who exists in the system.
If within a minute or two you already have dozens of records in hand, do not wait for the next headline. Fix the rate first, then how much information each response returns.
How secureFlows Solves This From the Start
In secureFlows, access to data is based on the user's session token, not on a sequential ID your app puts in the URL. There is no "loop over user numbers" that opens other people's records, because the record is bound to the token holder from the start.
In addition, sensitive auth paths are protected with server-side rate limiting, so a guessing script cannot run freely against entry points. When you build on that infrastructure, you do not have to remember to add this barrier on every reset screen separately: it is part of the layer every request passes through.
Want user isolation and rate limiting built into the infrastructure, instead of depending on a developer's memory? Hit the button below and start building with secureFlows.
Start Building for Free