OWASP risks
OWASP Risks
Understand the most consequential categories of web application security risk, why each arises from a design decision rather than a coding slip, and how to address them structurally.
Prerequisites
Recommended Knowledge
- HTTP request handling and API design
- Authentication and session management
- Authorization models and enforcement points
- SQL, query construction, and parameterization
- Encryption, hashing, and key management
- Dependency management and build pipelines
- Audit logging and monitoring
- Threat modeling and trust boundaries
What OWASP Provides
The Open Worldwide Application Security Project publishes community-driven guidance on application security. Its most referenced output is a periodically revised list of the most significant web application risk categories, derived from observed vulnerability data and practitioner survey.
Useful For
- Structuring a design review
- Prioritizing where to invest effort
- Providing shared vocabulary across teams
- Seeding threat modeling discussions
- Training engineers on recurring patterns
Not Sufficient For
- Proving an application is secure
- Covering domain-specific business logic abuse
- Replacing threat modeling
- Addressing infrastructure and supply chain fully
- Substituting for testing and review
Broken Access Control
Consistently the most prevalent category. Access control fails when the server does not verify that the authenticated identity is permitted to perform the requested operation on the specific resource.
| Manifestation | Description | Structural Fix |
|---|---|---|
| Object-level bypass | Changing an identifier reaches another user's record | Verify permission on the specific resource |
| Function-level bypass | Privileged endpoint callable by ordinary users | Enforce server-side, never via hidden controls |
| Tenant boundary crossing | Data from another tenant returned | Scope every query by tenant |
| Mass assignment | Client sets a role or ownership field | Allow-list writable attributes |
| Derived store leakage | Search index omits permission filters | Carry access metadata into the index |
| Forced browsing | Undocumented paths accessible directly | Deny by default on all routes |
async function getDocument(request, response) {
const document = await documentRepository.findById(
request.params.documentId
);
if (!document) {
return response.status(404).json({ error: "not_found" });
}
if (document.tenantId !== request.principal.tenantId) {
return response.status(404).json({ error: "not_found" });
}
const permitted = await authorizationService.canRead(
request.principal,
document
);
if (!permitted) {
return response.status(404).json({ error: "not_found" });
}
return response.json(toDocumentView(document));
}
Cryptographic Failures
This category covers absent, weak, or misapplied cryptography leading to exposure of data that required protection. The failure is frequently in key handling rather than algorithm choice.
| Failure | Consequence | Correct Practice |
|---|---|---|
| Plaintext transmission | Interception on the network path | Enforce transport security everywhere |
| Passwords encrypted not hashed | Key compromise exposes all credentials | Purpose-built hashing with a work factor |
| Obsolete algorithms | Protection is nominal only | Current standard algorithms |
| Predictable randomness | Keys and tokens become guessable | Cryptographically secure generation |
| Keys stored with data | One breach yields both | Separate managed key service |
| Unauthenticated encryption | Tampering goes undetected | Authenticated encryption modes |
| Sensitive data in logs | Protection bypassed entirely | Field-level redaction |
Injection
Injection occurs when untrusted input is interpreted as command or query structure rather than as data. The root cause is constructing an instruction by concatenating strings.
-- Parameterized: the value can never alter query structure
SELECT order_id,
customer_id,
total_amount,
status
FROM orders
WHERE customer_id = :customer_id
AND status = :status
AND tenant_id = :tenant_id
ORDER BY created_at DESC
LIMIT :page_size;
| Injection Type | Interpreter | Primary Defense |
|---|---|---|
| SQL injection | Database query engine | Parameterized statements |
| NoSQL injection | Document query parser | Type validation and operator restriction |
| Command injection | Operating system shell | Avoid shell invocation, pass argument arrays |
| Cross-site scripting | Browser rendering engine | Context-aware output encoding |
| Template injection | Template engine | Never treat input as a template |
| Log injection | Log parser or viewer | Structured logging with encoding |
Insecure Design
A distinct category acknowledging that some applications are implemented flawlessly against a specification that was itself unsafe. No amount of careful coding corrects a missing control.
| Design Gap | Resulting Abuse | Design Response |
|---|---|---|
| No limit on password reset | Account enumeration at scale | Rate limits and uniform responses |
| Unbounded resource creation | Storage and cost exhaustion | Per-tenant quotas |
| Trusting client-supplied price | Manipulated transaction totals | Recompute server-side |
| No approval separation | Self-approved privileged actions | Separation of duties |
| Unlimited retry on codes | Brute-forcing verification | Attempt caps and expiry |
| Refund without state check | Repeated refunds on one order | State machine with valid transitions |
Security Misconfiguration
Recurring Misconfigurations
- Default credentials left unchanged
- Administrative interfaces publicly reachable
- Verbose errors exposing stack traces
- Directory listing enabled
- Unnecessary features and ports active
- Overly permissive cross-origin policies
- Missing security response headers
- Storage buckets open to the public
- Excessive cloud role permissions
- Debug mode enabled in production
Strict-Transport-Security: max-age=63072000; includeSubDomains
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-store
Vulnerable and Outdated Components
Modern applications consist largely of code the team did not write. Transitive dependencies frequently outnumber direct ones by an order of magnitude, and each inherits its own risk.
| Control | Purpose |
|---|---|
| Dependency inventory | Know what is actually deployed, including transitives |
| Automated scanning | Detect known vulnerabilities continuously |
| Version pinning | Ensure builds are reproducible |
| Lockfile integrity | Prevent silent substitution of packages |
| Base image updates | Address vulnerabilities below the application |
| Removing unused packages | Reduce surface with no functional loss |
| Internal registry | Control and vet what enters builds |
Identification and Authentication Failures
| Weakness | Exploitation | Mitigation |
|---|---|---|
| Unlimited login attempts | Credential stuffing | Layered limits and progressive delay |
| Session not regenerated | Session fixation | New identifier on authentication |
| Predictable identifiers | Session guessing | Secure random generation |
| No session expiry | Indefinite credential validity | Idle and absolute timeouts |
| Weak recovery flow | Account takeover via reset | Single-use expiring tokens |
| Token claims unverified | Forged identity accepted | Signature, issuer, and audience checks |
| Distinct error messages | Account enumeration | Uniform responses and timing |
Software and Data Integrity Failures
This category concerns trusting code, updates, or serialized data without verifying its origin and integrity.
Unverified Trust
- Deserializing untrusted input into objects
- Loading scripts without integrity checks
- Deploying unsigned artifacts
- Auto-updating from unverified sources
- Pipeline plugins with unrestricted access
Verified Trust
- Parse structured data without object construction
- Subresource integrity on external scripts
- Signed artifacts verified before deployment
- Pinned and reviewed pipeline dependencies
- Least-privilege deployment credentials
Logging and Monitoring Failures
Insufficient logging does not cause a breach, but it determines whether one is detected in hours or discovered externally months later.
Minimum Viable Detection
- Authentication successes and failures
- Authorization denials with actor and resource
- Privilege and role changes
- Bulk export and mass data access
- Administrative configuration changes
- Alerting on patterns, not merely recording
- Tamper-evident, append-only storage
- Retention exceeding realistic detection delay
Server-Side Request Forgery
SSRF arises when a server fetches a URL supplied or influenced by a user. The server's network position becomes the attacker's, reaching internal services that are otherwise unreachable.
async function fetchExternalResource(rawUrl, policy) {
const parsed = new URL(rawUrl);
if (!policy.allowedProtocols.includes(parsed.protocol)) {
throw new Error("Protocol not permitted");
}
if (!policy.allowedHosts.includes(parsed.hostname)) {
throw new Error("Host not on allow-list");
}
const addresses = await resolveHostname(parsed.hostname);
for (const address of addresses) {
if (isPrivateOrReservedAddress(address)) {
throw new Error("Resolved to a restricted address");
}
}
return await httpClient.get(parsed.toString(), {
timeoutMs: policy.timeoutMs,
maxRedirects: 0,
maxResponseBytes: policy.maxResponseBytes,
pinnedAddress: addresses[0]
});
}
Mapping Risks to Controls
| Risk Category | Primary Architectural Control | Related Chapter Topic |
|---|---|---|
| Broken access control | Centralized server-side authorization | RBAC and ABAC |
| Cryptographic failures | Managed keys and classified data | Encryption |
| Injection | Parameterization and output encoding | Threat modeling |
| Insecure design | Threat modeling and abuse cases | Threat modeling |
| Misconfiguration | Configuration as code with drift detection | Secrets and rotation |
| Outdated components | Inventory and continuous scanning | Secrets and rotation |
| Authentication failures | Standard identity protocols | OAuth2, OIDC, JWT and sessions |
| Integrity failures | Signed artifacts and verified sources | Encryption |
| Logging failures | Tamper-evident audit trail with alerting | Audit logs |
| Server-side request forgery | Allow-lists and network segmentation | Threat modeling |
Cross-Cutting Principles
Never Trust the Client
Any value from a client may be altered. Prices, identifiers, roles, and quantities must be validated or recomputed on the server.
Deny by Default
Absence of an explicit permit is a denial. New endpoints should be inaccessible until authorization is deliberately configured.
Fail Closed
An error during a security check provides no evidence that access is permitted. Errors must not become accidental approvals.
Layer Controls
Assume any single control may fail. Defense in depth ensures one gap does not become a complete compromise.
Centralize Enforcement
Security logic scattered across controllers will be applied inconsistently. One enforcement path is reviewable and testable.
Minimize Exposure
Data not collected cannot leak, and features not enabled cannot be attacked. Reduction is the most durable control.
Verification Approaches
| Method | Detects | Misses |
|---|---|---|
| Static analysis | Injection and unsafe API use | Authorization and business logic flaws |
| Dynamic scanning | Misconfiguration and common patterns | Context-dependent logic abuse |
| Dependency scanning | Known component vulnerabilities | Undisclosed and first-party issues |
| Secret scanning | Committed credentials | Secrets outside scanned locations |
| Threat modeling | Design gaps and abuse cases | Implementation defects |
| Penetration testing | Chained and contextual weaknesses | Issues outside the engagement scope |
| Authorization tests | Regressions in access rules | Rules never written as tests |
describe("tenant isolation", function () {
it("does not expose another tenant's document", async function () {
const foreign = await createDocument({ tenantId: "tenant-a" });
const response = await requestAs(
{ tenantId: "tenant-b", role: "administrator" },
`/api/documents/${foreign.id}`
);
expect(response.statusCode).toBe(404);
});
it("rejects client-supplied role escalation", async function () {
const response = await patchAs(
{ userId: "user-1", role: "viewer" },
"/api/users/user-1",
{ displayName: "Updated", role: "administrator" }
);
const stored = await userRepository.findById("user-1");
expect(stored.role).toBe("viewer");
expect(response.statusCode).not.toBe(500);
});
});
Common Design Mistakes
Weak Practice
- Treating the list as a compliance checklist
- Relying on scanners for authorization coverage
- Checking roles but not specific resources
- Validating only on the client
- Deferring security to a pre-release phase
- Ignoring transitive dependencies
- Differing configuration across environments
- Logging without alerting
- Returning verbose errors in production
Strong Practice
- Uses the list to seed threat modeling
- Encodes authorization rules as tests
- Verifies permission on each resource
- Revalidates everything server-side
- Reviews security at design time
- Inventories the full dependency tree
- Applies one hardened baseline everywhere
- Alerts on defined attack patterns
- Returns generic errors with a trace identifier
System Design Interview Discussion
| Question | What Your Answer Should Cover |
|---|---|
| Which risk concerns you most? | Access control, with reasoning from your design |
| How is injection prevented? | Parameterization and context-aware encoding |
| How are tenants isolated? | Scoping in queries and derived stores |
| How do you find design flaws? | Threat modeling and abuse cases |
| How is the supply chain managed? | Inventory, scanning, pinning, and signing |
| How would a breach be detected? | Audit events with pattern-based alerting |
| Can users make you fetch a URL? | SSRF controls and network segmentation |
| How is this verified continuously? | Layered testing plus authorization regressions |
Review Checklist
Design Review Checklist
- Deny by default on every route
- Verify permission on the specific resource
- Scope every query by tenant
- Allow-list writable attributes on updates
- Parameterize all queries and commands
- Encode output for its rendering context
- Classify data before choosing protection
- Hash passwords with a work factor
- Store keys in a managed service
- Enforce transport security internally and externally
- Apply one hardened configuration baseline
- Disable debug output in production
- Inventory and scan all dependencies
- Verify artifact signatures before deployment
- Avoid deserializing untrusted input
- Allow-list outbound fetch destinations
- Log security events and alert on patterns
- Write regression tests for authorization rules
- Re-run threat modeling on design changes
Knowledge Check
Why is broken access control consistently prevalent?
It must be enforced correctly on every path, and automated tools cannot infer which identity should reach which resource.
What is the root cause of injection?
Untrusted input concatenated into an instruction, allowing it to be interpreted as structure rather than data.
Why is insecure design a separate category?
Flawless implementation of an unsafe specification is still unsafe. Missing controls cannot be added by careful coding.
Why does SSRF matter in internal architectures?
The server's network position lets an attacker reach internal services and metadata endpoints unreachable from outside.
Why is the list insufficient on its own?
It describes general categories and cannot cover domain-specific business logic abuse, which requires threat modeling of your particular system.
Summary
The OWASP risk categories describe recurring classes of web application weakness observed across the industry. They are awareness material for structuring review and prioritizing effort, not a standard that confers security when satisfied.
Broken access control remains the most prevalent, because it must be enforced on every path and cannot be verified automatically without knowledge of intended permissions. Injection persists wherever instructions are built by concatenation, and cryptographic failures usually trace to key handling rather than algorithm selection.
Insecure design acknowledges that some applications implement an unsafe specification correctly. Misconfiguration, outdated components, and integrity failures extend the concern beyond application code into configuration, dependencies, and the build pipeline.
Logging failures determine whether a compromise is detected in hours or months, and server-side request forgery turns the server's network position into an attacker's. Across all of them, the durable principles are the same: distrust the client, deny by default, fail closed, layer controls, centralize enforcement, and minimize what exists to be attacked.
Key Takeaway
Use the OWASP categories to start the conversation, not to end it. Map each risk to an architectural control rather than a code fix, encode your authorization rules as regression tests, and rely on threat modeling for the business logic abuse that no general list can anticipate.