August 13, 2026
Real-World Business Logic Flaws: From Tiny Mistakes to Major Security Vulnerabilities
In today’s bug bounty landscape, especially with the rise of AI traditional vulnerability classes such as SQL Injection, Cross-Site…
By Viodex
20 min read
In today's bug bounty landscape, especially with the rise of AI traditional vulnerability classes such as SQL Injection, Cross-Site Scripting (XSS), and Server-Side Template Injection (SSTI) have become increasingly difficult to find. Modern applications are routinely scanned using powerful automated tools and AI-driven systems, such as:
- NoScope (well known for discovering an RCE in alf.io)
- Nuclei (widely used for its extensive vulnerability templates)
As a result, many common implementation flaws are identified and fixed long before a researcher ever looks at the application.
However, some of the most impactful vulnerabilities are not caused by insecure code they are caused by flawed business logic.
A business logic vulnerability doesn't necessarily come from a programming mistake. Instead, it originates from an incorrect assumption, an incomplete workflow, or a missing security rule in the application's design. The code may behave exactly as intended, yet the overall process still allows an attacker to perform actions that should never be possible.
Unlike classic vulnerabilities, business logic flaws are extremely difficult for automated scanners and AI to detect because they require understanding how the application is supposed to work, not just how the code behaves.
A simple analogy is building an extremely secure house with reinforced doors, bulletproof windows, and advanced alarm systems — only to accidentally leave the key inside the house. Every security mechanism works perfectly, but the overall design still fails because of a logical oversight.
This is why business logic vulnerabilities continue to be among the most valuable findings in real-world bug bounty programs. They exploit the gap between secure implementation and secure design a gap that automated tools often cannot understand.
Business logic vulnerabilities are different from traditional vulnerabilities because they are not defined by a specific coding mistake. Instead, they are discovered by understanding how an application is designed to work and identifying the assumptions the developers made during that design.
When analyzing an application, the goal isn't to ask, "Is this input vulnerable to SQL Injection or XSS?" Instead, you ask questions like:
- What assumptions does the application make about its users?
- What actions does it expect users not to perform?
- Can I complete a workflow in an unexpected order?
- Can I abuse a legitimate feature in a way the developers never intended?
Unlike technical vulnerabilities, business logic flaws cannot be explained with a simple definition or a checklist. They are highly dependent on the application's workflows, business rules, and the developers' assumptions. The best way to understand them is by seeing them in action.
So, rather than talking about them in theory, let's dive into a few examples and see how seemingly harmless pieces of logic can turn into serious security vulnerabilities.
1. Business Logic / Trust Boundary Violation
A common misconception is that every vulnerability is caused by insecure code. In reality, many critical vulnerabilities exist because the application trusts data that should never be trusted.
Consider an e-commerce website. The developer made a dangerous design decision: instead of storing product prices on the server and looking them up during checkout, the application stores the price on the client and sends it along with the product ID.
When a user clicks Quick Buy, the browser sends the following request:
POST /checkout HTTP/1.1
Host: target.com
Content-Type: application/json
{
"productId": "A1F34S1",
"price": "12.50"
}POST /checkout HTTP/1.1
Host: target.com
Content-Type: application/json
{
"productId": "A1F34S1",
"price": "12.50"
}Notice the JSON body:
{
"productId": "A1F34S1",
"price": "12.50"
}{
"productId": "A1F34S1",
"price": "12.50"
}The price field is completely controlled by the client.
The server processes the request and responds:
HTTP/1.1 200 OK
Content-Type: application/json
{
"product": "Broom",
"price": "12.50",
"total_cost": "12.50"
}HTTP/1.1 200 OK
Content-Type: application/json
{
"product": "Broom",
"price": "12.50",
"total_cost": "12.50"
}At first glance, everything looks normal. However, whenever you see a security-sensitive value being supplied by the client, you should immediately ask yourself:
What happens if I change it?
Using a proxy such as Burp Suite, intercept the request and modify the price:
POST /checkout HTTP/1.1
Host: target.com
Content-Type: application/json
{
"productId": "A1F34S1",
"price": "0"
}POST /checkout HTTP/1.1
Host: target.com
Content-Type: application/json
{
"productId": "A1F34S1",
"price": "0"
}There are now two possible outcomes.
Secure Implementation
A properly designed application ignores the client-provided price and retrieves the actual price from its database.
If the values do not match, the request is rejected:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Invalid price."
}HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Invalid price."
}The important point is that the server never trusts the client to decide how much an item costs.
Vulnerable Implementation
Suppose the developer assumed that users would never modify the request and directly trusted the price parameter.
In that case, the server might respond with:
HTTP/1.1 200 OK
Content-Type: application/json
{
"product": "Broom",
"price": "0",
"total_cost": "0"
}HTTP/1.1 200 OK
Content-Type: application/json
{
"product": "Broom",
"price": "0",
"total_cost": "0"
}The customer has now purchased a $12.50 product for free.
In even worse implementations, the application may fail to validate negative values. Changing the price to -10 could result in a negative invoice or store credit, effectively allowing an attacker to receive money instead of paying for the product.
Notice that there is no SQL Injection, Cross-Site Scripting, or buffer overflow involved. Every line of code behaves exactly as the developer intended. The flaw lies entirely in a broken assumption:
"The client would never modify the price."
This is the essence of a business logic vulnerability. The application is technically functioning as designed — but the design itself is insecure.
Actually this is one of the most cases used in this type of articles so i will use another example to give you a new ideas :) :
2. Bad Business Logic Causing a 2FA Bypass
I first read about a case similar to this in an article a few years ago. It described a real penetration testing engagement where a tiny mistake in the authentication workflow completely bypassed two-factor authentication (2FA).
The developer made an incorrect assumption about how the login flow would be followed.
The intended authentication process looked like this:
Enter credentials → MFA code is generated → Redirect to the OTP verification page → Enter the OTP → Access the dashboard
The developer believed that once a user was redirected to the OTP page, they would be unable to access any protected resources until the OTP had been verified.
Internally, the logic worked something like this:
- Correct username and password → Create a valid session and redirect the user to
/login/otp. - Correct OTP → Mark the session as fully authenticated and allow access to the dashboard.
However, there was a subtle design flaw.
Immediately after verifying the username and password, the server created a valid session cookie:
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Hackers-agent
username=alice&password=CorrectPasswordPOST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Hackers-agent
username=alice&password=CorrectPasswordThe server responded with:
HTTP/1.1 302 Found
Set-Cookie: session=abc123
Location: /login/otpHTTP/1.1 302 Found
Set-Cookie: session=abc123
Location: /login/otpThe application assumed that because the browser had been redirected to /login/otp, the user would complete the OTP challenge before accessing anything else.
But attackers don't have to follow the intended workflow.
Instead of requesting /login/otp, the attacker simply ignored the redirect and manually requested a protected endpoint while including the newly issued session cookie:
GET /dashboard HTTP/1.1
Cookie: session=abc123GET /dashboard HTTP/1.1
Cookie: session=abc123If the application only checked whether the session was valid — and never verified whether the second authentication factor had been completed — the request succeeded and the dashboard was displayed.
The OTP page became nothing more than a suggested step in the login process rather than an enforced security control.
The vulnerability wasn't caused by weak cryptography, predictable OTPs, or brute force. The OTP itself worked perfectly.
The flaw was that the session created after the password check was already powerful enough to access protected resources. In other words, the application treated "password verified" and "fully authenticated" as the same state.
The correct design is to keep the session in a partially authenticated state until the OTP has been successfully verified. Every protected endpoint should explicitly verify that the session has completed the second authentication factor before granting access.
Once again, there is no traditional coding vulnerability here. The application behaved exactly as it was programmed to — it simply enforced the authentication workflow incorrectly.
To cover at least the most common types of vulnerabilities caused by flawed business logic, I like to group them into the following categories:
Workflow and Process Bypasses
- Skipping Steps: Users jump directly to a final action (such as an order confirmation) without completing required intermediate steps like payment verification or multi-factor authentication.
- State Manipulation: Modifying hidden parameters, workflow tokens, or state identifiers to trick the application into believing a different stage of a process has already been completed.
Financial and Pricing Exploits
- Price Manipulation: Changing product prices, fees, or order totals supplied by the client when the server fails to independently calculate or validate them.
- Quantity Manipulation: Supplying zero, negative, or extremely large quantities to produce unexpected financial outcomes or abuse inventory logic.
- Discount Abuse: Reusing one-time coupons, stacking promotions that should be mutually exclusive, or exploiting flaws in discount calculation.
Trust and Validation Failures
- Excessive Trust in Client Input: Relying on client-side JavaScript, hidden form fields, or user-controlled parameters for security decisions instead of enforcing them on the server.
- Flawed Business Assumptions: Assuming users will always follow the intended workflow in a predictable order. For example, qualifying for a discount by adding an item to the cart and then removing it before checkout while still keeping the discount.
Concurrency and Timing Issues
- Race Conditions: Sending multiple requests simultaneously to exploit the tiny window before the application's state is updated, such as performing the same withdrawal twice before the account balance is locked or refreshed.
The first three categories — Workflow and Process Bypasses, Financial and Pricing Exploits, and Trust and Validation Failures — represent only a small subset of business logic vulnerabilities. In reality, business logic flaws are almost impossible to enumerate completely because every application implements its own unique workflows and business rules. A single incorrect assumption by a developer can introduce an entirely new class of vulnerability.
In this article, we've already explored two practical examples:
- Skipping Steps, demonstrated through the 2FA bypass.
- Price Manipulation, where the server trusted a client-supplied price instead of calculating it itself.
Finally, I'd like to briefly introduce Concurrency and Timing Issues, particularly Race Conditions. Although race conditions are often treated as a standalone vulnerability class, they are fundamentally rooted in business logic and state management. Covering them properly deserves an article of its own, so here I'll only introduce the core concept. A dedicated deep dive on race conditions will follow in a future article.
Race Conditions
Race conditions are often considered one of the most difficult classes of vulnerabilities to understand and discover. Unlike many traditional vulnerabilities, they don't usually stem from a single insecure line of code. Instead, they arise from the timing of multiple operations happening at nearly the same moment.
To identify a race condition, you need to understand how the application's workflow works internally, how requests are processed, and when shared resources — such as balances, inventory, or account states — are updated.
The easiest way to understand the concept is through an analogy.
Imagine you've just started a full-time job as an airport security officer. Your responsibility is to inspect every passenger's luggage before allowing them to board the plane.
One day, two passengers arrive at your checkpoint at almost exactly the same time.
The first passenger hands you his luggage. You begin inspecting it, but because the process takes a while, your records haven't been updated yet to indicate that the checkpoint is occupied.
At that exact moment, the second passenger reaches the checkpoint with an identical boarding pass. Since the system still appears to be in its previous state, he is also allowed to proceed.
Only after both passengers have passed do you finish your inspection and update the records.
The problem wasn't that you failed to inspect the luggage. The problem was that both passengers were able to act before the system finished updating its state.
Now let's map this analogy to a web application.
- The security officer is the server.
- The passengers are HTTP requests.
- The luggage inspection represents the server performing validation.
- The security records represent the application's shared state, such as a database record, account balance, or inventory count.
If two requests arrive at nearly the same time, they may both read the same state before either request has updated it. As a result, both requests believe they are allowed to continue, even though only one should have succeeded.
This tiny timing window is known as a race condition.
Unlike business logic flaws that rely on incorrect assumptions, race conditions rely on concurrency. The vulnerability exists because multiple requests "race" to complete an operation before the application's state changes.
In the next section, we'll see how this tiny timing window can be exploited to create serious vulnerabilities, such as double-spending, coupon reuse, inventory abuse, and balance manipulation.
Reservation Override via Race Condition
We'll cover real-world race condition vulnerabilities later in this article, but let's start with a simple example to understand the concept.
Imagine you're booking a flight through an airline's website. There is only one seat left — Seat 12.
At almost the same moment, both you and another customer attempt to reserve that seat.
You send the following request:
POST /reserve HTTP/1.1
Host: airline.example
Cookie: session=ABC123
Content-Type: application/json
{
"seatId": 12
}POST /reserve HTTP/1.1
Host: airline.example
Cookie: session=ABC123
Content-Type: application/json
{
"seatId": 12
}Internally, the server performs the following workflow:
Check whether Seat 12 is available → Reserve the seat → Save the reservation to the database
The problem is that these operations are not atomic.
Suppose your request and another customer's request arrive only a few milliseconds apart.
The sequence might look like this:
Request A Request B
Check: Seat available ✔
Check: Seat available ✔
Reserve Seat 12
Reserve Seat 12
Save reservation
Save reservationRequest A Request B
Check: Seat available ✔
Check: Seat available ✔
Reserve Seat 12
Reserve Seat 12
Save reservation
Save reservationBoth requests checked the seat before either one updated the database.
As a result, both requests believed the seat was available and both attempted to reserve it.
Depending on how the application is implemented, this can lead to several problems:
- Two users receive confirmation for the same seat.
- One reservation silently overwrites the other.
- The database enters an inconsistent state.
- The airline must manually resolve the conflict at check-in.
Notice that there is nothing wrong with the reservation logic itself. The flaw exists because the server allows multiple requests to observe the same state before that state has been updated.
This tiny timing window is what makes race conditions so dangerous. A difference of only a few milliseconds can completely change the outcome of an operation.
This vulnerability exploits what is known as a Timing Window.
To better understand the concept, consider the following simplified pseudo-code:
seat_status = get_status(seatId)
if seat_status != "reserved":
reserve_seat(seatId, userId)
sleep(1.0) # -------- Timing Window --------
update_reservation_db()seat_status = get_status(seatId)
if seat_status != "reserved":
reserve_seat(seatId, userId)
sleep(1.0) # -------- Timing Window --------
update_reservation_db()The problem is that while the first request is being processed, the application opens a timing window before the new reservation is committed to the database.
During this short period, the application's state has not yet been updated. If a second request reaches the server within this tiny window, it may perform the same availability check and receive the same result — the seat still appears to be available.
As a result, both requests believe they are entitled to reserve the seat, even though only one reservation should be allowed.
Depending on how the application handles conflicting writes, the outcome may vary. One request may overwrite the other, both users may receive a successful reservation, or the application may end up in an inconsistent state.
This small interval between checking the seat's availability and saving the updated reservation is called the Timing Window, and it is the fundamental reason why many race conditions occur.
Final Words
Business logic vulnerabilities are unlike most traditional vulnerability classes. They are rarely discovered by simply fuzzing parameters or running automated scanners. Instead, they require a deep understanding of how an application is designed to work and, more importantly, how the developers expect users to interact with it.
Whenever you're hunting for business logic flaws, ask yourself one simple question:
"What assumption did the developer make that I can break?"
Don't approach an application the same way you would when searching for SQL Injection, Cross-Site Scripting (XSS), or Cross-Site Request Forgery (CSRF). Those vulnerabilities often revolve around insecure code. Business logic flaws revolve around insecure assumptions.
Try to understand the application's workflows. Think about what would happen if you performed actions out of order, repeated a request, modified a seemingly harmless parameter, or combined two legitimate features in a way the developers never anticipated.
Always try to stay one step ahead of the developer — not by looking for broken code, but by looking for broken assumptions.
A Final Note for Bug Bounty Hunters
One thing you'll quickly discover is that finding a business logic flaw is only half the battle.
You may identify a workflow that clearly violates the application's intended behavior, demonstrate a meaningful security impact, and still receive a response such as "Intended behavior", "Won't Fix", or another resolution that you disagree with.
Business logic vulnerabilities are often subjective because they depend on the application's business rules rather than on universally accepted security issues. As a result, different organizations may evaluate the exact same behavior differently.
I remember reporting a business logic vulnerability that resulted in Broken Access Control. Even the application's own documentation stated that the action I performed should not have been possible. Despite that, the report was resolved as a duplicate because another researcher had already reported the same issue in 2024, confirming that it was indeed a legitimate vulnerability.
So don't get discouraged if one of your reports is rejected or classified differently than you expected. Business logic testing is one of the most creative areas of offensive security, and not every company evaluates these findings consistently.
Keep learning how applications work, keep questioning assumptions, and remember: sometimes the biggest vulnerabilities aren't hidden in the code — they're hidden in the logic behind it.
Real-World Examples about business logic Flows
now we will introduce some business logic flaw examples for better understanding
1. lovable — Business Logic Bypass Allows Setting "Read Access" Role Without Pro Plan Subscription
Hunter: ziadmomen
Platform: lovable
Source: https://hackerone.com/reports/3591764
Severity: Medium (4 ~ 6.9)
Weakness: Business-Logic errors
Bounty: NoneHunter: ziadmomen
Platform: lovable
Source: https://hackerone.com/reports/3591764
Severity: Medium (4 ~ 6.9)
Weakness: Business-Logic errors
Bounty: NoneOverview
Lovable allows project owners to generate invitation links that grant specific roles within a project.
One of these roles, Viewer (read-only), is intended to be available only to Lovable Pro subscribers. While exploring the feature, I discovered that this restriction existed only in the frontend. The backend API accepted requests to create Viewer invitation links without verifying whether the requester had an active Pro subscription.
As a result, any project owner could generate Viewer invitation links even without access to the premium plan.
Root Cause
The API responsible for creating invitation links accepted the requested access level directly from the client.
Example request:
POST /projects/{project-id}/magic-codes HTTP/2
Content-Type: application/json
{
"access_level": "ROLE-HERE"
}POST /projects/{project-id}/magic-codes HTTP/2
Content-Type: application/json
{
"access_level": "ROLE-HERE"
}The frontend prevented free users from selecting the Viewer role, but the backend failed to enforce the same subscription check.
Exploitation
To verify whether the restriction was enforced server-side, I:
- Selected a role that was available to free users (for example, Editor).
- Intercepted the outgoing request.
- Modified the
access_levelparameter before forwarding it to the server.
Original request:
{
"access_level": "edit"
}{
"access_level": "edit"
}Modified request:
{
"access_level": "read"
}{
"access_level": "read"
}If the backend had properly enforced the subscription requirement, it should have rejected the request with an authorization or subscription-related error.
Instead, the API responded with HTTP 200 OK and successfully generated a Viewer invitation link.
Why This Happened
The application's business rule requires that only Lovable Pro subscribers can generate Viewer invitation links. However, the implementation appears to have been based on the assumption that free users would never attempt to submit a Viewer role, since the frontend did not expose that option.
Because of this assumption, the subscription check was enforced only in the client, while the backend API accepted the user-supplied access_level without verifying whether the authenticated user was actually entitled to assign that role.
This incorrect trust assumption allowed a free user to modify a client-controlled parameter and bypass the intended subscription restriction, ultimately accessing a premium feature without a Pro subscription.
2. stripe business logic vulnerability caused fee-discounts to be reedemed more than one time via race condition
Hunter: ian
Platform: stripe
Source: https://hackerone.com/reports/3591764
Severity: Medium (6.5)
Weakness: Business-Logic errors
Bounty: 5000Hunter: ian
Platform: stripe
Source: https://hackerone.com/reports/3591764
Severity: Medium (6.5)
Weakness: Business-Logic errors
Bounty: 5000Overview
While Ian was chilling after recently receiving a $20,000 fee discount offer on Stripe transactions, he came up with an idea that he was almost certain would not work.
Before clicking the button to accept and redeem the $20,000 discount, he opened Burp Suite, intercepted the request, and sent it to Burp Repeater:
POST /ajax/accept_fee_discount_offer?include_only%5B%5D=token HTTP/1.1
Host: dashboard.stripe.com
Cookie: [Redacted]
Content-Length: 31
Sec-Ch-Ua: "Chromium";v="109", "Not_A Brand";v="99"
X-Stripe-Manage-Client-Revision: [Redacted]
Stripe-Version: 2022-08-01
Authorization: [Redacted]
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
X-Stripe-Csrf-Token: [Redacted]
Stripe-Account: [Redacted]
Stripe-Livemode: true
Origin: https://dashboard.stripe.com
Referer: https://dashboard.stripe.com/dashboard
id=fdo_1Mb8GtDk3bp1ZWoGQKhSaN5EPOST /ajax/accept_fee_discount_offer?include_only%5B%5D=token HTTP/1.1
Host: dashboard.stripe.com
Cookie: [Redacted]
Content-Length: 31
Sec-Ch-Ua: "Chromium";v="109", "Not_A Brand";v="99"
X-Stripe-Manage-Client-Revision: [Redacted]
Stripe-Version: 2022-08-01
Authorization: [Redacted]
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
X-Stripe-Csrf-Token: [Redacted]
Stripe-Account: [Redacted]
Stripe-Livemode: true
Origin: https://dashboard.stripe.com
Referer: https://dashboard.stripe.com/dashboard
id=fdo_1Mb8GtDk3bp1ZWoGQKhSaN5EIan expected the server to enforce the redemption limit. In other words, the first request should redeem the offer, while any additional attempts should be rejected.
He decided to test that assumption.
The Unexpected Result
He created a new tab group, duplicated the request around 30 times, and sent all of them in parallel.
Ian was expecting something like this:
Request 1 → 200 OK
Request 2 → 403 / 400
Request 3 → 403 / 400
...
Request 30 → 403 / 400Request 1 → 200 OK
Request 2 → 403 / 400
Request 3 → 403 / 400
...
Request 30 → 403 / 400Instead, something completely unexpected happened.
All 30 requests were accepted.
The result was a total of:
$20,000 × 30 = $600,000
in fee discounts applied to the account.
At this point, Ian realized that this was not simply a harmless test. The application had allowed the same discount offer to be redeemed multiple times.
Of course, Ian freaked out for a moment — but he did not panic.
He immediately reported the issue to Stripe.
He explained that he had tested the behavior using his own personal account because he was convinced the additional requests would fail. He also provided Stripe with his account information so they could identify the affected account and investigate the unintended discounts.
The report was eventually triaged, and Stripe deployed a fix.
Retesting
Stripe later offered Ian an opportunity to retest the fix.
Interestingly, the issue was no longer reproducible using Burp Repeater.
However, Ian had a suspicion that the fix might have only reduced the timing window rather than completely eliminating the underlying issue.
He therefore used Turbo Intruder, a Burp extension designed for sending highly concurrent requests, to perform another controlled test.
The issue appeared to still be reproducible when the requests were sent with sufficiently high concurrency.
Ian reported this to Stripe, and the issue was eventually fixed completely.
What Actually Happened?
There are two possible stages to consider here.
1. The Original Business Logic Flaw
The original behavior suggested that the application did not properly enforce the intended rule that the fee discount could only be redeemed once.
The business rule should have been:
One account
↓
One offer
↓
One redemptionOne account
↓
One offer
↓
One redemptionInstead, the application allowed the same offer to be accepted multiple times.
At this stage, the issue could potentially be explained by the redemption limit simply not being properly enforced.
2. The Race Condition After the Fix
After Stripe's initial fix, the behavior changed.
The issue was no longer reproducible through normal repeated requests in Burp Repeater, but could still appear when requests were sent with much higher concurrency.
This strongly suggested that there was a very small timing window in the redemption process.
Conceptually, the vulnerable flow could look like:
Request A → Check: "Already redeemed?" → No
Request B → Check: "Already redeemed?" → No
Request C → Check: "Already redeemed?" → No
↓
Redemption occurs
↓
Mark as redeemedRequest A → Check: "Already redeemed?" → No
Request B → Check: "Already redeemed?" → No
Request C → Check: "Already redeemed?" → No
↓
Redemption occurs
↓
Mark as redeemedIf multiple requests reach the check before the redemption state is updated, they may all pass the validation.
This type of issue is commonly known as a TOCTOU (Time-of-Check to Time-of-Use) race condition.
The important observation here was that after the initial fix, the issue required significantly higher request concurrency to reproduce. This is why Ian suspected that the initial fix had reduced the timing window but had not completely eliminated the underlying race.
Note: The following is only an assumption about what may have happened internally and should not be considered the confirmed root cause of the vulnerability.
One possible explanation is that a race condition, specifically a TOCTOU (Time-of-Check to Time-of-Use) issue, was involved. This could happen if multiple requests were able to pass the redemption check before the discount was recorded in the database as already redeemed.
However, this was not confirmed to be the actual root cause. Another possibility is that the original implementation simply failed to properly enforce the one-redemption-per-account business rule.
After the initial fix, the behavior could no longer be reproduced with normal requests and required significantly higher concurrency. This is what led the researcher to suspect that a race condition might still exist within a much smaller timing window.
Therefore, the race-condition explanation should be treated as an observation-based assumption rather than a confirmed finding about Stripe's internal implementation.
Final Result
Ian reported the behavior again to Stripe, explaining that the issue could still be triggered under a much narrower timing window.
Stripe subsequently implemented a final fix that prevented the same fee discount from being redeemed multiple times, even when redemption requests were sent concurrently.
1. [REDECATED] — business logic flow leads to a free plan user to invite more than one member to his workspace
Hunter: ayman
Platform: Not Disclosed
Source: https://medium.com/%40ayman_amer_1/business-logic-flaw-lets-free-plan-add-extra-team-members-600581cd3205
Severity: meduim (4.3 ~ 5.6) prediction
Weakness: Business-Logic errors
Bounty: N/AHunter: ayman
Platform: Not Disclosed
Source: https://medium.com/%40ayman_amer_1/business-logic-flaw-lets-free-plan-add-extra-team-members-600581cd3205
Severity: meduim (4.3 ~ 5.6) prediction
Weakness: Business-Logic errors
Bounty: N/AOverview
While testing a private bug bounty program, Ayman came across a feature that allowed workspace owners to invite new members.
The Free Plan had a simple restriction: a workspace could only have one member invitation.
At first, Ayman tried the obvious things. He replayed the invitation request after the invitation button disappeared, but the server rejected it. He also tried testing for a race condition, but that didn't lead anywhere.
Then he noticed something that seemed a little strange.
When he invited his own email address, the invitation was successfully sent — but, for some reason, it didn't count toward the workspace's invitation limit:
That small inconsistency turned out to be the key.
Under normal circumstances, the workflow was supposed to look something like this:
Free Plan
↓
Invite 1 member
↓
Invitation limit reached
↓
Further invitations are blockedFree Plan
↓
Invite 1 member
↓
Invitation limit reached
↓
Further invitations are blockedBut when Ayman invited his own email, the behavior was different:
Invite my own email
↓
Invitation is sent
↓
Invitation does not consume the limitInvite my own email
↓
Invitation is sent
↓
Invitation does not consume the limitThis raised an important question:
Why was the invitation being sent if it wasn't being counted?
It suggested that the application was treating invitations to the current user's email differently when calculating the limit.
Ayman then realized that he didn't necessarily need to bypass the invitation limit itself.
Instead, he could try manipulating the condition that caused an invitation to be excluded from the limit.
Step 1 — Change the Account Email
He changed the email address associated with his account to the email address of a target user.
For example:
Original email:
ayman@example.com
Changed to:
target1@example.comOriginal email:
ayman@example.com
Changed to:
target1@example.comFrom the application's perspective, the current user's email was now:
target1@example.comtarget1@example.comStep 2 — Invite the Same Email
He then invited:
target1@example.comtarget1@example.comBecause the invited email matched the current user's email, the application treated the invitation as an invitation to the current user.
But there was an important difference:
The invitation was still actually sent.
So the result was effectively:
Invitation sent → Yes
Invitation counted → NoInvitation sent → Yes
Invitation counted → NoStep 3 — Repeat
Ayman then changed his account email again:
target2@example.comtarget2@example.comand invited:
target2@example.comtarget2@example.comThe same behavior occurred.
The invitation was sent, but it didn't consume the workspace's invitation allowance.
By repeating the process with additional email addresses, he could continue sending invitations without properly consuming the Free Plan's invitation limit.
General Impact
The impact of a business logic flaw is not fixed. Unlike many traditional vulnerabilities, its severity depends entirely on how the flawed logic affects the application's business processes.
A seemingly minor logical mistake can have little to no security impact, while another can lead to account takeover, financial loss, unauthorized access, or complete compromise of critical business functionality.
In other words, the severity of a business logic vulnerability is determined not by the flaw itself, but by the consequences of exploiting it and the extent to which it affects the application and its business operations.
Conclusion
Finally, Business Logic vulnerabilities arise not from a vulnerable source code, but from a vulnerable mindset or a flawed assumption made by the developer.
Most of them don't require replaying requests, using Burp Suite, or performing complicated technical attacks. Instead, they require a curious mindset that thinks about the developer's assumptions and looks for the weak points in the application's business rules.
The examples covered in this article are intentionally Medium severity to keep the focus on understanding the methodology rather than diving into more advanced Business Logic vulnerabilities. There are far more complex cases, such as email parsing issues and other critical Business Logic exploitations, which I plan to cover in an advanced article soon.
Whenever you're hunting for these types of vulnerabilities, always think about the worst assumption a developer could make then challenge that assumption and see whether the application's business logic still holds.
Recommended Resources
I'd love to recommend a lot of resources, because Business Logic vulnerabilities are incredibly diverse. Every application has its own business rules, which means every scenario can be unique.
Here are some of the best resources I recommend:
- for practical solving:
All labs Mystery lab challenge Try solving a random lab with the title and description hidden. As you'll have no prior knowledge…
- readable resources:
Business logic vulnerabilities In this section, we'll introduce the concept of business logic vulnerabilities and explain how they can arise due to…
Business logic vulnerability | OWASP Foundation Business logic vulnerability on the main website for The OWASP Foundation. OWASP is a nonprofit foundation that works…
- another real-world reports:
Subscription Bypass Leading to Full Access to Paid Features Hi, my name is Hossam Hamada, I'm a Bug Bounty Hunter, and today I'll be sharing a vulnerability I discovered in a Bug…
How a GraphQL Bug Resulted in Authentication Bypass | HackerOne Experienced security researchers explain how a GraphQL bug resulted in authentication bypass — and how to avoid it.
Business logic error occurs by altering the letters from lowercase to uppercase only !! Business logic error occurs by altering the letters from lowercase to uppercase only !! السلام عليكم , بسم الله والصلاه…
you can find more by searching on medium , hackerone hackactivity , bugcrowd crowdstream , books like real world bug bounty hunting.
written by : viodex (founder and leader for perdo team)
version : v1.0
published by : viodex
re-published by : perdo team