Business Logic Vulnerabilities: Attacking the Rules
Business logic testing: sequence abuse, negative values, coupon and discount state machines, multi tenant checks and concurrency as a logic bug.
A scanner can report perfect headers while the application lets the same coupon run fifty times.
Business logic vulnerabilities appear when the system executes the programmed flow correctly but accepts a state that should never exist.
These bugs are hard to automate because they depend on understanding what the application is trying to protect.
Price.
Balance.
Limit.
State.
Sequence.
Role.
Quantity.
A tool sees parameters.
The pentester needs to see rules.
The examples below assume a lab or an authorized penetration test.
First identify the rule that should exist
Before changing a request, I try to explain the feature in one sentence.
“A coupon can be used once per account.”
“An order can only ship after payment.”
“A normal user cannot choose their own role.”
“A transfer cannot exceed the available balance.”
“A promotion allows at most three units.”
If you cannot state the rule, proving that the application violated it becomes difficult.
This also prevents strange behavior from being reported as a vulnerability without real impact.
The interface is not a security rule
Consider a cart request:
1
2
3
4
POST /cart HTTP/1.1
Content-Type: application/x-www-form-urlencoded
productId=31&quantity=1&price=19900
The browser displays the correct price.
That does not mean the backend should trust price=19900.
Change it:
1
price=100
If the server accepts that value, critical logic has been placed on the client side.
This pattern appears in price, shipping and discount. Commission, currency, limits and roles are common too.
Anything affecting a business decision needs server-side validation.
Sequence is an attack surface
Many workflows assume users will follow the screens.
Login.
MFA.
Role selection.
Confirmation.
Checkout.
Payment.
Completion.
Burp Repeater does not follow screens.
You can send step four before step three.
You can repeat step two.
You can return to step one after step four.
You can call the final endpoint directly.
PortSwigger has several labs where the flaw begins with this exact assumption.
The application assumes that reaching the final endpoint means earlier steps were completed.
Server-side state needs to prove that.
Validate state at every transition
Think about an order:
1
CREATED -> PAID -> SHIPPED -> DELIVERED
Now test:
1
CREATED -> SHIPPED
Or:
1
CANCELLED -> SHIPPED
Or:
1
REFUNDED -> PAID
The UI may never provide those buttons.
The API may still accept the transition.
Incomplete state machines are classic logic-flaw territory.
Negative values are still values
Applications often validate a maximum and forget the minimum.
Quantity:
1
2
3
4
{
"productId": 31,
"quantity": -10
}
Balance operation:
1
2
3
{
"amount": -500
}
Percentage:
1
2
3
{
"discount": -20
}
The result may reverse an operation.
Add money.
Increase inventory.
Reduce a total.
The exact effect depends on implementation.
I test zero, negative and maximum. Then I try maximum plus one, unexpected decimals and large values.
Each input tests a specific assumption.
Per-operation limits can fail in aggregate
Consider a wallet with a daily limit of 10,000.
The backend validates:
1
transfer_amount <= 10000
Two transfers of 9,000 both pass.
The actual rule was:
1
sum(transfers_today) + new_transfer <= 10000
The local check looked correct.
The global rule was missing.
This pattern appears in coupons, withdrawals and invitations. Credits, uploads, token issuance and quota consumption behave similarly.
Discounts are state machines too
Consider two coupons.
WELCOME10.
NEWSLETTER20.
The application blocks the same coupon twice in a row.
But it allows:
1
2
3
4
WELCOME10
NEWSLETTER20
WELCOME10
NEWSLETTER20
Validation checked the previous code.
The rule should have checked the full set of benefits already consumed.
This is a state bug, not an input-syntax bug.
Multi-tenant logic needs more than basic authorization
A user can access Project 42.
The project belongs to Organization A.
The same user also belongs to Organization B.
Now the user tries:
1
2
3
4
{
"projectId": 42,
"destinationOrganizationId": "B"
}
The user may be authorized for both objects individually.
The business rule may still prohibit transfers between those organizations.
Object authorization alone does not cover the relationship.
Many business logic flaws live at those intersections.
User roles need server-side treatment
Registration:
1
2
3
4
{
"email": "gabriel@example.test",
"role": "user"
}
Changing role to admin is the obvious test.
The interesting cases often appear later.
The backend may ignore the field during registration but accept it during profile update.
Or a complete-profile endpoint may reuse the same DTO as an administrative feature.
Different workflows touching the same object may enforce different rules.
Compare endpoints.
A trusted user may later change trusted data
An application validates company affiliation during registration.
Later it allows email changes without repeating the rule.
The user starts with:
1
gabriel@gmail.com
The normal checks pass.
Then the email changes to:
1
gabriel@internal-company.example
If privileges depend only on the current domain, the system may grant access that the original registration flow would have blocked.
Any property used for authorization needs protection when it changes.
Concurrency is business logic too
Two requests can observe the same state before either updates the database.
Balance: 100.
Request A checks whether 80 can be withdrawn.
Request B checks whether 80 can be withdrawn.
Both see 100.
Both pass.
The result may be a total withdrawal of 160.
Race conditions deserve their own article, but they belong here because execution order breaks a business rule.
I test hypotheses, not payload collections
For each workflow, I write down what the system assumes.
Then I try to violate one assumption at a time.
Will users always use the UI?
Will steps always arrive in order?
Will values always be positive?
Will objects always stay inside one tenant?
Will a coupon only be used once?
Will approval always happen first?
Can two requests ever arrive together?
That process produces findings scanners rarely discover.
The report should explain the broken rule
“Business logic flaw” is a weak description.
Better:
1
The application allows an order to be confirmed without a recorded payment.
Or:
1
The daily limit is validated per transaction, allowing multiple operations to exceed the configured limit.
Or:
1
The backend allows benefit reuse when different coupon codes are alternated.
The team fixing the bug needs to understand the rule.
Remediation
Document valid states.
Validate transitions on the server.
Derive critical values from trusted sources.
Authorize relationships between objects as well as individual objects.
Enforce invariants in the database where possible.
Use transactions and locking where concurrency affects the rule.
Test paths outside the normal sequence.
Business logic security starts when the team writes down what must never happen and turns those statements into executable validation.
References
PortSwigger Web Security Academy, Business logic vulnerabilities.
PortSwigger Web Security Academy, Examples of business logic vulnerabilities.


