Business Logic Vulnerabilities
My notes about Logic Flaws
Category: [Server-Side]
Severity: [High to Critical]
Impact: [Price manipulation, Promo abuse, Payment bypass, Unauthorized data access, Privilege escalation, Resource exhaution, etc...]
1. Concept
Business logic vulnerabilities also known as Application logic vulnerabilities or Logic flaws are flaws in the design and implementation of an application that allow an attacker to get unintended behavior from application. This enables an attacker to manipulate website’s functionality to reach a malicious goal. These flaws are generally the result of failing to anticipate unexpected application’s behavior.
The attacker may interact with application in a way that developer never intended One of the main purposes of business logic is to enforce that the rules that were defined when designing and developing the application. These rules dictate how application should react to a request or a scenario when it occurs. This includes preventing users from doing malicious activities in the application. Flaws in these rules can lead to bypassing the rules, by passing the unexpected values into server-side logic, an attacker can tricks the application to do something which is not supposed to.
Logic-based vulnerabilities are unique based on the web application and how it works, so finding a logic flaw may be difficult as there’s no special predefined way to find it.
2. How do business logic vulnerabilities arise?
Logic flaws arise because developer and design team didn’t consider some unexpected values that may be used to attack the application.
For example: E-Commerce price manipulation An online store uses a multi-step checkout process:
- Item selection: user adds a product to their shopping cart
- Payment processing: The server calculates the total cost and passes the request to the payment gateway
[User Browser] → ( Sends: item_id=402, quantity=1, price=100 ) → [Web Server]
The application relies on hidden input fields which is contain price and sent from user’s browser to server, rather than verifying the price against a trusted backend database:
POST /checkout HTTP/1.1
Host: shop.com
Content-Type: application/x-www-form-urlencoded
itemID=402&quantity=3&price=100.0
The attacker intercept this POST request and change the price to 0.01:
POST /checkout HTTP/1.1
Host: shop.com
Content-Type: application/x-www-form-urlencoded
itemID=402&quantity=3&price=0.01
As the result the payment gateway successfully processes a valid $0.01 transaction because no code execution or error occurred, The application process the payment and give the product to attacker just because the price relies on Client-side rather that determining in the Server-side.
3. Real-world cases
Excessive trust in client-side controls
Some developers think users will interact with the application only using the provided web interface, so an attacker can easily use a proxy tool like Burp Suite to intercept the request and modify parameters as we see in the previous section’s example.
Failing to handle unconventional input
A numeric data type may accept negative value, this may not make sense for business logic to allow this, attacker may use this to craft an unexpected response from server
Consider a funds transfer between two banks, this will certainly check whether sender has sufficient funds or not:
$transferAmount = $_POST['amount'];
$currentBalance = $user->getBalance();
if ($transferAmount <= $currentBalance) {
transfer();
}
else {
error();
}
But if the logic doesn’t prevent the users from entering negative values in the amount parameter, the attacker may use this to bypass balance check and and transfer funds in the wrong direction. If the attacker send -$1000 to the victim’s account, this might result in receiving $1000 from victim’s account. the logic say “Ok, -$1000 is always less than the current balance, so let me approve the transfer”.
Flawed assumptions about user behavior
One of the most root causes of business logic vulnerabilities is making flawed assumption about what user will do in website. This will lead to a wide range of scenarios that can happen under the name of Flawed Logic.
Trusted users won’t always remain trustworthy
Some web applications apply logic filters initially, so when a user signed-in successfully, they may trust the user. This can lead to a logic flaw, attacker simply sign-in to abuse the bugs.
Users won’t always supply default/mandatory inputs
Browser may prevent submitting some values, but browser is not everything, attacker may intercept the request and change the parameter (Parameter Tampering) to test the web application.
For example: this is the input code of a form:
<form action="/api/submit" method="POST">
<input type="number" name="value" min="0" max="100" step="1" required>
<button type="submit">Submit</button>
</form>
This code gets a number in range 1-100 and POST it to /api/submit, so ordinary users can’t submit any other data types, but attacker can! look the request:
POST /api/submit HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
value=42
that is easy for the attacker to change the value to any other type of data or test out-of-range values. If server doesn’t apply any server-side filter, it might lead to some issues due to flawed logic of code.
Consequently, web application needs a server-side validation:
app.post('/api/submit', (req, res) => {
const value = req.body.value;
// Type check
if (typeof value !== 'number' && !/^\d+$/.test(value)) {
return res.status(400).json({ error: 'Must be an integer' });
}
const num = Number(value);
// Range check
if (num < 1 || num > 100) {
return res.status(400).json({ error: 'Must be between 1 and 100' });
}
// Safe to use `num` now
res.json({ received: num });
});
Sometimes it’s also possible to reach a logic flaw by removing parameters. When probing for logic flaws, you should try removing each parameter and check how does it affected. you should try:
- Only remove one parameter at a time to ensure all relevant code paths are reached.
- Try deleting the name of the parameter as well as the value. The server will typically handle both cases differently.
- Follow multi-stage processes through to completion. Sometimes tampering with a parameter in one step will have an effect on another step further along in the workflow.
Do not forget to check URL queries, POST requests and also cookies as they may change the server behavior.
Users won’t always follow the intended sequence
Many transactions rely on predefined workflows. User do the first step, then the second step until the end, each step shows to the user in a separate page. but the attacker won’t adhere to the intended sequence.
For example: many websites require user to enter Username & Password in the first webpage and then enter the 2FA in another webpage, so attacker may simply go to the intended endpoint for account like /my-account after signing-in without entering the 2FA code. this is a logic flaw in the website which leads to Authentication Bypass.
Once an attacker has seen a request, they might use it when server is not in the state of processing that specific request, this might enable them to check web application’s behavior to find and exploit a vulnerability.
It’s also possible to look for a logic flaw using Parameter Tampering. You often submit a request using POST or GET, it’s worthy to try other methods as they may give us some valuable data.
A stack-trace, syntax error or any other error can be valuable, so don’t forget to change data type like this, for example:
POST /api/submit HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
value[]=42
value=[42]
value={42}
this may lead to some errors, even if they didn’t, you can figure out how back-end server works.
Domain-specific flaws
Almost all business logic flaws are specific to the business domain as the purpose of different web applications is different.
One of the classic logic flaw point is discounting functionality in shopping sites.
For example: Consider a website apply 10% discount on orders over 1000$, an attacker may easily add items to the cart until it reaches 1000$, apply the discount code and then remove what he doesn’t need, so what will happen? the discount was supposed to being used only for orders that are over 1000$, attacker used the logic flaw here and apply the discount for a cheaper order.
(Item1, Item2, Item3) → 1000$ → Apply 10% discount → Remove Item2 & Item3 → discount is still there → Item1 10% discount
Providing an encryption oracle
In some cases, web application convert the user input to some encrypted data. This kind of input called Encryption Oracle. The attacker can use this input to encrypt arbitrary input with correct algorithm and key that is expected by application. This becomes dangerous when there are another kinds of user-controllable inputs that uses the encrypted data to do something or run a function by decrypting the data. Once attacker find the encryption algorithm, they can use it to craft a malicious payload and serve it to server and get what they want. This can become even more dangerous and critical in case of another user-controllable reverse function is there to decrypt the data, so attacker can find out how data is being structured and encrypted.
Note: The reverse function will make it easier for the attacker to exploit the vulnerability, not a required option to exploit.
Email address parser discrepancies
Some websites parse email addresses and extract domain to determine which organization the email owner belongs to. Discrepancies in how different parts of application handle the application will lead to a logic flaw which can be abused by attacker.
Example:
Application grants admin access to @company.com emails. In this case the RFC validator rejects invalid email syntaxes, and Auth system uses split(@)[1] to determine email’s domain:
Email = "email@company.com"
Splitted = Email.split('@') # ['email', 'company.com']
Domain = Splitted[1] # company.com
Attacker simply use such an email: email@company.com@attacker.com, the result:
['email', 'company.com', 'attacker.com']
Index [1] → company.com
Real Domain → attacker.com
Validator(attacker.com) → Pass
Auth(company.com) → Pass
Access Granted!
It checks index 1 of the array which is company.com and regard it as an admin, but the real domain is attacker.com, so the attacker gets admin access.
Real-World:
# Naive parser (vulnerable)
domain = email.split('@')[-1] # "company.com" → WRONG
# Slightly better but still flawed
domain = email.split('@')[-1] if email.count('@') == 1 else reject()
# RFC-compliant (safe)
from email.utils import parseaddr
_, addr = parseaddr(email)
domain = addr.split('@')[-1]
Other tricks:
| Input | Strict parser | Naive parser | # |
|---|---|---|---|
user@company.com%40attacker.com | attacker.com | company.com | %40 = @ |
user@company.com@[127.0.0.1] | [127.0.0.1] | company.com | |
"user@company.com"@attacker.com | attacker.com | company.com |
4. Preventing
- Developers should understand the domain that application serves
- Avoid making implicit assumptions about user behavior or the behavior of other parts of the application
- Maintain clear design documents and data flows for all transactions and workflows, noting any assumptions that are made at each stage.
- Write code as clearly as possible. If it’s difficult to understand what is supposed to happen, it will be difficult to spot any logic flaws. Ideally, well-written code shouldn’t need documentation to understand it. In unavoidably complex cases, producing clear documentation is crucial to ensure that other developers and testers know what assumptions are being made and exactly what the expected behavior is.
- Note any references to other code that uses each component. Think about any side-effects of these dependencies if a malicious party were to manipulate them in an unusual way.