July 4, 2026
How I Discovered a Critical Data Leak Starting with a Small Clue
INTRODUCTION

By Uday
15 min read
Sometimes, the most valuable vulnerabilities are not found in complex functionalities. They are found in the silence between features, in the quiet corners of a website where developers assumed no one would look too closely. This is the story of one such bug. It began on a website that, at first glance, had nothing at all to attack.
I had chosen a target from a bug bounty program. The program belonged to a major internet service provider, a company with millions of active users. Naturally, I was excited. But when I loaded their main domain, my excitement cooled. The website was essentially a static brochure. There was a banner advertising their services, a list of their Wi-Fi plans, some pricing details, and very little else. No login form. No user dashboard. No dynamic functionality that accepted user input in any meaningful way. For a hunter, a website without functionality is like a locked room with no doors. You circle it, you look for cracks, but nothing invites you in.
But I did not close the tab. I stayed on the page and simply looked at it. And then I noticed something small, floating quietly in the bottom corner of the screen. It was an AI chatbot. A little button that said something friendly like, "Need help? Ask me anything." I clicked it.
The chatbot opened and presented me with a menu of options. There were choices like "Check your Wi-Fi bill," "New connection plans," and other customer support queries. Since I happened to be a customer of this very same internet provider, I was familiar with how their system worked. When you install their Wi-Fi, they assign you a unique twelve-digit account ID. This ID is your identity within their system. You use it to log into their mobile app, to check your bills, and to manage your account.
I clicked on the "Check your Wi-Fi bill" option inside the chatbot. The chatbot responded immediately and asked me to enter my twelve-digit ID. I entered my own ID, the one assigned to my home connection. For the sake of this article, imagine that ID was 123456789101. The chatbot processed my request and then said something interesting. It told me that it had sent a One Time Password to the mobile number registered to that account. I checked my phone, and sure enough, an OTP had arrived. The chatbot then asked me to enter that OTP. I did. After verification, the chatbot informed me that it had sent the billing details to the registered email address on file.
My first reaction was positive. This was a good security practice. The system was not displaying sensitive billing information directly inside the chatbot window. Instead, it was sending the bill to the email address that only the legitimate account owner should have access to. An attacker who somehow obtained my account ID would still need to bypass the OTP sent to my phone. And even if they did, the actual sensitive data would be pushed to my email, not shown on the screen. I noted this as a well-designed flow and almost moved on.
But I paused.
THE SMALL OBSERVATION THAT CHANGED EVERYTHING
I started thinking about the twelve-digit ID. Twelve digits is a large number space, but account IDs like these are often not random. They are frequently generated sequentially or predictably. I wondered what would happen if I took my own ID, which ended with the digits 12, and simply incremented it to 13. I imagined the ID 123456789113. If the IDs were indeed predictable, this number might belong to another genuine customer.
I went back to the chatbot, clicked the bill check option again, and this time entered the modified ID. I expected one of two things to happen. Either the chatbot would tell me that no such account existed, or it would initiate the OTP process for another user. What happened next was subtle but extremely revealing.
The chatbot did not reject my input. It did not say the account did not exist. Instead, it told me that it had sent an OTP to the registered mobile number. I did not have access to that phone number, so I could not proceed further. But the damage, from an information disclosure perspective, had already been done. The chatbot had just confirmed to me that the account ID 123456789113 belonged to a real, valid user of this internet service provider.
This is a classic case of what I call behavioral information leakage. The system did not give me the user's bill. It did not give me their email or their phone number. It simply told me that the account existed. And that alone was enough.
THE WATCHMAN ANALOGY
To explain why this seemingly harmless behavior is dangerous, let me offer a real-world analogy.
Imagine a thief who wants to rob a particular house. The thief knows the neighborhood but is not sure which house belongs to the target, a man named Alex Bob. The house has a watchman standing outside. The thief approaches the watchman and deliberately asks a wrong question. He asks, "Is this the house of Alex Max?" The watchman has a job to do, but he is not trained to guard his words carefully. He replies, "No, this is not Alex Max's house. This house belongs to Alex Bob."
The watchman did not let the thief inside. He did not hand over the keys. He simply confirmed the owner's name. But that confirmation is exactly what the thief needed. Now the thief knows for certain which house to target. He can go away, prepare more thoroughly, and return with a plan specifically tailored to Alex Bob's house.
The chatbot in my story behaved exactly like that untrained watchman. It did not leak the bill. It did not leak the phone number or the email. It simply confirmed the existence of an account. And in the world of cybersecurity, enumeration vulnerabilities like this are often the first quiet domino that leads to a much larger breach.
THE DANGER OF INFORMATION LEAKAGE THROUGH ERRORS
This kind of user enumeration is more common than many people realize. It often hides in plain sight, inside error messages that developers considered harmless. One of the most frequent examples is found in login and registration forms.
Imagine you visit a website and try to create a new account. You enter your email address.
The website responds with a red error message: "This email is already taken".
Please use a different email." That single sentence has just told you that the email address belongs to a registered user of that platform. An attacker can use this behavior to test thousands of email addresses and compile a list of valid ones. This is called email enumeration, and it can lead to mass exposure of user identities. With a list of confirmed emails, attackers can launch targeted phishing campaigns, credential stuffing attacks, or simply sell the verified email list to other malicious parties.
Errors can reveal far more than just email existence. Depending on the application, error messages can sometimes leak software versions, internal file paths, database details, and even hints about the underlying server infrastructure. A single verbose error message during a failed login might reveal whether the username exists or whether the password is wrong. A misconfigured API might return stack traces that expose the inner workings of the backend. As a hunter, you must train yourself to read every error message not as a dead end, but as a potential whisper from the system, telling you something it should not.
In my case with the chatbot, there was no explicit error message. The information leakage was behavioral. The system behaved differently for a valid ID and an invalid ID. For a valid ID, it initiated the OTP process. For an invalid ID, presumably, it would have said the account did not exist. I confirmed this by testing a few more random twelve-digit numbers. When the ID was fake, the chatbot refused to proceed. When the ID was real, the chatbot sent an OTP. The difference in response was my enumeration vector.
PATIENCE: GATHERING DATA WITHOUT REPORTING TOO SOON
By simply cycling through a handful of sequential IDs, I had confirmed that I could map out valid account identifiers on this platform. I now had a method to collect legitimate twelve-digit IDs of active customers. But I also had a problem. I had no impact.
If I reported this enumeration behavior to the company right then, what would I have told them? That their chatbot confirms whether an account ID exists? That I could gather a list of valid IDs? A security team receiving such a report would likely mark it as Informative, or Low Severity at best. The IDs themselves are not considered highly sensitive secrets. There was no direct access to bills, personal data, or account settings. A valid ID alone could not be used to log in, because the OTP was still sent to the registered phone. I knew that if I submitted my finding at this stage, the report would probably be closed without a bounty, and more importantly, without fixing the deeper architectural flaw that I suspected existed.
So I did not report it. I wrote down my enumeration method. I saved my notes. I kept the list of valid IDs I had discovered. And I continued hunting the same program, patiently, for another week. This decision was critical. Many hunters make the mistake of firing off a report the moment they see any anomalous behavior, without asking themselves the most important question: What is the real-world impact of this?
THE SECOND DISCOVERY: A DIFFERENT SUBDOMAIN
A week later, my patience paid off. I discovered another subdomain belonging to the same internet service provider. This website had a different purpose. It was a dedicated portal for customers to fetch and check their monthly Wi-Fi bills. The URL suggested it was a utility page, perhaps designed for quick access without needing to log into the full account dashboard.
When I loaded this new website, I immediately noticed something interesting. There was no login page. There was no username and password field. There was simply a form asking for the twelve-digit account ID. I entered my own ID and clicked submit.
The page loaded my billing details. I could see how much I needed to pay, the billing period, and other charges. But the personal details on the page caught my attention. My name was displayed partially masked. If my name was Alex, the page showed it as AXX. My email address and phone number were also masked, showing only the first and last characters, with the middle sections replaced by asterisks. From a front-end perspective, this looked secure. The developers had thought about sensitive data exposure and had implemented masking.
But I have learned never to trust front-end masking alone. User interfaces are designed for human eyes. The real data exchange happens behind the scenes, between the client and the server. I opened my Burp Suite proxy, captured the request, and examined the server's response.
I fully expected to find the unmasked data in the raw JSON or HTML response, waiting to be extracted. I was prepared to document a classic sensitive data exposure where the server sends the full information and relies on the front-end JavaScript to mask it. But to my genuine surprise, the backend response was also masked. The server was sending the same redacted data that the front-end displayed. My name was AXX in the API response. My email was truncated. This was a rare example of proper data handling, and I noted it with appreciation. The developers had done this part correctly.
FOLLOWING THE PAYMENT FLOW
I could have stopped there. But something told me to go deeper. I wanted to understand the full user journey on this bill-checking portal. What happened after I viewed my bill? There was a button that said "Pay Now." I clicked it.
When I clicked Pay Now, my browser fired a new request. This request was not going back to the same server. It was being redirected toward a payment gateway. Let me explain the setup. The company I was testing, which I will call Dark for the purpose of this story, was integrating with a well-known payment processor. I will refer to this payment processor as PayU. When a customer clicks Pay Now, Dark's server prepares a request and sends it to PayU. This request contains the customer's billing details and the amount to be paid. PayU then processes this and presents a payment interface, often including a QR code or a push notification to the customer's mobile device, so the payment can be completed seamlessly.
I captured the request that was sent from Dark's server to PayU. What I saw made me lean forward in my chair. The data being sent was in a URL-encoded format, which is normal for web form submissions. But the content of that encoded string was far from normal.
I copied the encoded string and pasted it into Burp Suite's decoder. I also used a couple of online URL decoding tools and even an AI assistant to double-check my work. When the string was decoded, I saw plain text. And in that plain text, clearly visible and completely unmasked, were the full name, full email address, and full phone number of the customer.
This was not encoded in Base64. It was not hashed. It was not encrypted. It was not even masked with asterisks. The payment gateway request contained the user's entire personal profile in plain, readable text. This was the exact opposite of what I had seen in the bill-checking page. The front-end masked the data. The bill API masked the data. But when it came time to send the data to a third-party payment processor, the developers had bundled up the complete, unmasked user information and sent it over the wire in a format that anyone intercepting the request could decode effortlessly.
(An intercepted POST request captured during the payment flow. The request, sent from the Dark portal to the third-party payment gateway, contains URL-encoded customer data. The parameters firstname, email, and phone reveal the full, unmasked personally identifiable information of the account holder in plain text. This example represents a typical request structure observed during testing.)
POST /payment HTTP/1.1 Host: payment.example-gateway.com Cookie: SESSIONID= Content-Length: 420 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Origin: https://portal.example-isp.com Referer: https://portal.example-isp.com/ User-Agent: Mozilla/5.0 (…) Accept: text/html,application/xhtml+xml Connection: keep-alive
firstname=John+Doe& email=john.doe%40example.com& phone=9876543210& productinfo=Monthly+Internet+Bill& merchant_key=& hash=& transaction_id=TXN-123456789& amount=999.00& success_url=https%3A%2F%2Fportal.example-isp.com%2Fpayment%2Fsuccess& failure_url=https%3A%2F%2Fportal.example-isp.com%2Fpayment%2Ffailure& customer_id=123456789012& payment_method=upi
I sat back and processed what I had found. I now had two pieces of the puzzle.
Piece one was the enumeration method from the chatbot. I could generate or predict valid twelve-digit account IDs on demand. Piece two was the payment gateway request. For any valid account ID, I could trigger the Pay Now flow and intercept the outbound request to PayU. That request contained the user's unmasked personal details.
This was no longer an Informative enumeration bug. This was a chain. An attacker could write a script that iterates through a range of twelve-digit IDs, checks their validity using the chatbot's behavioral response, and then for each valid ID, hits the bill-checking portal, triggers the payment request, and captures the decoded personal information from the PayU redirect.
This vulnerability falls under A04:2021 — Insecure Design in the OWASP Top 10. Insecure Design refers to flaws that exist because security was not considered during the planning and architecture phase of the application. The developers built a chatbot to help customers but never questioned whether its behavioral responses could leak valid account IDs. The payment integration was designed with masking on the front-end and API, yet the same unmasked data was sent to the third-party gateway in plain text. The predictable twelve-digit account IDs themselves reflect a design choice made without considering enumeration risks. These are not implementation bugs. They are gaps in threat modeling, gaps that existed long before the first line of code was written.
But the impact needed to be measured. I did some research on the company Dark, the internet service provider, was not a small operation. According to publicly available information, they had between 3.5 and 4 million active Wi-Fi subscribers. Four million users. If my attack chain could be automated, and if the account IDs were indeed as predictable as they seemed, then the potential exposure was not dozens of records, not hundreds, but millions.
This was a critical data exposure vulnerability. A mass enumeration leading to Personally Identifiable Information leakage on a scale that could affect a significant portion of the company's entire customer base. The financial implications for the company, the privacy implications for the users, and the reputational damage would all be enormous. This was no longer a quiet little observation in a forgotten chatbot. This was a full-blown security incident waiting to happen.
REPORTING AND THE FRUSTRATION OF SELF-HOSTED PROGRAMS
I prepared a detailed report. I documented the enumeration method step by step. I explained the predictable nature of the twelve-digit IDs. I described the bill-checking portal and how the payment flow worked. I provided the decoded PayU request showing the plain text personal data. I outlined the attack chain and made it clear how an automated script could harvest millions of records. I calculated the potential number of affected users based on the company's publicly stated subscriber count. I submitted the report through their self-hosted bug bounty platform and waited.
The platform's guidelines stated that response times would be between seven and ten days. Ten days passed, and I heard nothing. I sent a polite follow-up email on day fifteen. No response. I sent another follow-up on day eighteen. Silence. The days stretched on, and my frustration grew. When a researcher finds a vulnerability of this magnitude, responsible disclosure is a race against time. You do not know if someone else has also found the same bug. You do not know if it is already being exploited in the wild. The waiting is the hardest part, and with self-hosted programs, the waiting can be interminable.
On day twenty, I finally received a reply. The security team had reviewed my report. Their verdict: Duplicate. Someone else had already reported the same vulnerability. My twenty days of waiting, my three follow-up emails, my carefully crafted proof of concept, all of it had been for a finding that had already been submitted by another researcher.
This is the painful reality of bug bounty hunting. You can do everything right, find a critical bug, chain it beautifully, document it meticulously, and still end up with nothing because you were not the first. Self-hosted programs, in particular, often suffer from slow triage times and poor communication. Unlike platforms like HackerOne or Bugcrowd, which have mediation and structured processes, self-hosted programs are managed directly by the company's internal security team, which may be understaffed, overwhelmed, or simply not prioritizing bounty reports. My humble recommendation to fellow hunters is this: be very selective with self-hosted programs. They can consume your time, your energy, and your motivation without any reward. The lesson stung, but it did not erase the valuable learning that the entire journey had given me.
THE CRITICAL LEARNINGS
Even though the outcome was a duplicate, the experience sharpened my skills and reinforced several principles that every aspiring hunter should internalize.
First, observation is everything. The entire chain began because I noticed a small floating chatbot button on a website that otherwise had no attack surface. Most people would have ignored it. I clicked it, and I paid attention to how it responded. The difference between a hacker and a casual user is the depth of observation. We do not just use features. We study behaviors. We ask, what happens if I change this? What does this response tell me?
Second, do not rush to report without impact. When I first discovered the ID enumeration, I had no impact. It was a finding, but it was a toothless one. If I had reported it immediately, it would have been dismissed. By being patient and keeping it in my back pocket, I was able to chain it later with a much more severe vulnerability. A bug without impact is a curiosity. A bug with impact is a vulnerability. Wait until you have impact, or keep hunting until you find the link that transforms your small observation into something significant.
Third, never trust front-end masking alone. Always check the backend responses, and always follow the entire data flow. The bill-checking portal masked everything on the screen. The API responses were also masked, which was impressive. But the developers forgot about the third-party payment integration. When data leaves your application and travels to an external service, it often does so in its raw, unmasked form. Intercept those outbound requests. Decode them. You might find that the developers considered security within their own application but became careless at the perimeter.
Fourth, understand the power of enumeration, even when it seems harmless. The ability to confirm a valid user ID, a valid email, or a valid phone number is the starting point for many devastating attacks. User enumeration is the reconnaissance phase of application hacking. It is the foundation upon which targeted phishing, credential brute-forcing, and data aggregation are built. Developers often dismiss enumeration as a low-severity issue. As hunters, we must demonstrate how enumeration chains into critical impact.
Fifth, know the scale of the target. When I researched the company and discovered they had millions of users, the severity of my finding multiplied instantly. A bug that leaks data for ten users is High severity. The same bug leaking data for four million users is Critical. Always quantify the blast radius of your finding. Numbers matter in security reports. They turn abstract vulnerabilities into business risks that executives understand.
FINAL THOUGHTS
This experience taught me that hacking is not always about breaking encryption, injecting SQL, or crafting elaborate cross-site scripting payloads. Sometimes, it is about sitting quietly with a seemingly boring website and noticing the one small thing that the developers overlooked. It is about chaining low-severity quirks into high-impact exposures. It is about patience, persistence, and an almost obsessive attention to the flow of data.
The watchman outside the house did not think he was giving away sensitive information when he corrected the thief about the owner's name. The chatbot did not think it was leaking anything when it confirmed an account ID. The developers of the payment integration did not think anyone would look at the outbound URL-encoded request. But in security, every interaction is a potential leak, and every leak can be chained.
To anyone starting in bug bounty hunting, I offer this guidance. Look at the websites that seem to have nothing. Click the buttons that seem insignificant. Read the error messages like they are clues in a detective novel. Do not report the first anomaly you find. Wait. Dig deeper. Follow the data wherever it goes, even when it leaves the application and heads toward a third party. And when you finally find the thread that ties your small observation to a massive impact, document it clearly, quantify the damage, and submit it with confidence.
The duplicate response hurt, but the knowledge I gained is mine forever. The next time I see a silent chatbot in the corner of a page, I will click it. I will change the ID. I will watch the response. And I will be patient. Because somewhere, in the quiet spaces between features, the next critical vulnerability is waiting to be chained.
Thank you for reading. If you enjoyed this breakdown, follow me for more real-world bug bounty stories and security deep dives.