OWASP: Insecure Design
By Admin
•
November 9, 2025
Bad Example — Insecure Design
Scenario
A banking app lets users transfer money by trusting the "fromAccountId" in the request body.No one thought: "What if the attacker changes that field?"
// BAD: flawed business logic design
@WebServlet("/transfer")
public class MoneyTransferServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String fromAccount = req.getParameter("fromAccountId");
String toAccount = req.getParameter("toAccountId");
double amount = Double.parseDouble(req.getParameter("amount"));
// ❌ Design flaw: assumes client input = truth
// ❌ No check if the logged-in user actually owns fromAccount
BankService.transfer(fromAccount, toAccount, amount);
resp.getWriter().println("Transfer successful!");
}
}
⚠️ What's wrong
- Trusts user-supplied data for authorization context.
- No ownership check for fromAccount.
- An attacker can simply POST:
fromAccountId=123456 (victim) toAccountId=999999 (attacker) amount=1000 - Violates OWASP A04: Insecure Design, CWE-602 (Client-Side Enforcement of Server-Side Security), and CWE-285 (Improper Authorization).
This is not a "code bug" — it's a logic flaw caused by insecure assumptions in the design.
✅ Good Example — Secure Design
Fix: Enforce authorization and business rules server-side
@WebServlet("/transfer")
public class SecureMoneyTransferServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
HttpSession session = req.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Login required");
return;
}
String loggedInUserId = (String) session.getAttribute("userId");
String fromAccount = req.getParameter("fromAccountId");
String toAccount = req.getParameter("toAccountId");
double amount = Double.parseDouble(req.getParameter("amount"));
// ✅ Check ownership of the source account
if (!BankService.userOwnsAccount(loggedInUserId, fromAccount)) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You do not own this account");
return;
}
// ✅ Enforce business constraints
if (amount <= 0 || amount > BankService.getAccountBalance(fromAccount)) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid transfer amount");
return;
}
BankService.transfer(fromAccount, toAccount, amount);
resp.getWriter().println("Transfer completed securely.");
}
}
✅ Why it's secure
- Server-side ownership check prevents horizontal privilege escalation.
- Business rule validation (no negative, zero, or overdraft transfers).
- Session-based identity ensures design separation of authentication and authorization.
- Fail-secure responses — returns 403/400 properly instead of "silent success."
Design-Level Secure Practices
Principle | Secure Design Action |
Threat modeling early | Use STRIDE or misuse cases before writing code. |
Least privilege | Users can only act within their authorization context. |
Secure defaults | Deny by default; explicitly grant access. |
Defense in depth | AuthN + AuthZ + Input Validation + Logging. |
Business rule enforcement | Validate workflows (not just input). |
Separation of duties | Keep authorization and business logic independent. |
TL;DR
Bad design: "If the front end sends fromAccountId, it must be valid."Good design: "The backend enforces ownership, authorization, and business rules — never trusts the client."
