Lesson 1.c / Module 1
The vulnerability classes that matter
The OWASP Top 10, the OWASP API Security Top 10, and the CWE Top 25 are maps of the same territory. Underneath them sit four root causes, and fixing a root cause removes a whole class.
On this page
The idea in one minute#
Application security has three widely used lists of what goes wrong. The OWASP Top 10 ranks risk categories for web applications. The OWASP API Security Top 10 does the same for APIs. The CWE Top 25 ranks specific weakness types by how often and how severely they appear in published vulnerabilities. Each is useful, and each is easy to misuse as a syllabus or a compliance target.
Read them instead as maps. Most entries on all three lists come from four root causes: the code never asked whether the caller may do this (missing authorization), the code let data be read as code or as a command, path, or address (interpreting input), the code believed something the client said (trusting the client), or the system shipped in an open state (unsafe defaults). The consequence to act on: when you find a bug, name the root cause, then fix it where every instance goes through, so the next instance cannot be written.
A picture#
flowchart LR L1["<b>A01, API1, API3, API5</b><br/><small>CWE-862, CWE-639</small>"] --> R1["<b>Missing authorization</b>"] --> F1["<b>One policy check</b><br/><small>every object, deny by default</small>"] L2["<b>A03, A10, API7</b><br/><small>CWE-89, 79, 22, 918, 502</small>"] --> R2["<b>Interpreting input</b>"] --> F2["<b>Data stays data</b><br/><small>parameterize, encode, allow-list</small>"] L3["<b>A07, API2, API6</b><br/><small>CWE-287, CWE-352</small>"] --> R3["<b>Trusting the client</b>"] --> F3["<b>Server derives state</b><br/><small>identity, price, intent</small>"] L4["<b>A05, API8</b><br/><small>CWE-611, CWE-306</small>"] --> R4["<b>Unsafe defaults</b>"] --> F4["<b>Secure by default</b><br/><small>config as code, checked in CI</small>"] class L1,L2,L3,L4 neutral class R1,R2,R3,R4 warn class F1,F2,F3,F4 good classDef neutral fill:#faf9f5,stroke:#5f5e55,color:#141413,stroke-width:1.5px classDef compute fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef memory fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef queue fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef io fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef default fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef warn fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:2px,stroke-dasharray:5 3 classDef good fill:#e2efd8,stroke:#2b6a24,color:#141413,stroke-width:2px
The mapping is not one-to-one, and the lesson says where it leaks. It is close enough to organize a review, a backlog, and the rest of this course.
How it really works#
Three lists, three purposes#
| List | Maintained by | What an entry is | Built from | Best used for |
|---|---|---|---|---|
| OWASP Top 10 | OWASP Foundation | A broad risk category, mapped to many CWEs | Contributed testing data plus a community survey | Awareness, shared vocabulary, a first review checklist |
| OWASP API Security Top 10 | OWASP API Security Project | A risk category specific to APIs | Public data on API security incidents and practitioner input | Reviewing an API design and its authorization model |
| CWE Top 25 | MITRE, with CISA | One weakness type, a single CWE ID | CVE records mapped to CWEs, scored by frequency and severity | Prioritizing weakness classes; tagging findings precisely |
CWE itself (Common Weakness Enumeration) is not a ranking. It is a catalog
of weakness types, organized from abstract classes down to specific variants,
each with a stable numeric ID. CWE-89 means SQL injection to every scanner,
bug tracker, and advisory that uses it. That precision is why you should tag
findings with CWE IDs even if your team thinks in OWASP categories.
OWASP describes its Top 10 as an awareness document. For a checkable standard of what an application should do, OWASP points to its Application Security Verification Standard (ASVS). Treating the Top 10 as the full scope of a security review leaves out everything that did not make the cut.
The editions this lesson uses, in order. Check which one a document cites before comparing category numbers.
timeline title Editions referenced in this lesson 2019 : OWASP API Security Top 10, first edition : injection is a category 2021 : OWASP Top 10 : XSS joins injection, XXE joins misconfiguration : three new categories 2023 : OWASP API Security Top 10 : injection dropped, three authorization entries 2024 : CWE Top 25 : CWE-79 ranks first 2025 : OWASP Top 10 update in progress
The OWASP Top 10 (2021)#
This lesson uses the 2021 edition, with a 2025 update in progress as of late 2025. Check the project page for the current status before citing category numbers in your own work.
| ID | Category | Mostly root cause |
|---|---|---|
| A01 | Broken Access Control | Missing authorization |
| A02 | Cryptographic Failures | Outside the four: weak or missing cryptography |
| A03 | Injection (cross-site scripting included) | Interpreting input |
| A04 | Insecure Design | Any of them, decided before code was written |
| A05 | Security Misconfiguration (XML external entities included) | Unsafe defaults |
| A06 | Vulnerable and Outdated Components | Outside the four: what you depend on |
| A07 | Identification and Authentication Failures | Trusting the client, weak credential handling |
| A08 | Software and Data Integrity Failures | Interpreting input (deserialization), unverified updates |
| A09 | Security Logging and Monitoring Failures | Outside the four: what you can see |
| A10 | Server-Side Request Forgery | Interpreting input |
Three changes in 2021 are worth knowing because they show how the categories are built. Cross-site scripting stopped being its own entry and joined injection: both are data interpreted as code, in the browser or in the database. XML external entities joined security misconfiguration, because the fix is usually a parser setting. And three categories were new: Insecure Design, Software and Data Integrity Failures, and Server-Side Request Forgery, the last added on the strength of the community survey rather than the testing data.
Broken access control at A01 is the headline. It is also the category that automated scanners find least well, because a scanner does not know that invoice 1002 belongs to Bob.
The OWASP API Security Top 10 (2023)#
| ID | Category | Mostly root cause |
|---|---|---|
| API1 | Broken Object Level Authorization | Missing authorization |
| API2 | Broken Authentication | Trusting the client, weak credential handling |
| API3 | Broken Object Property Level Authorization | Missing authorization (which fields), trusting the client (mass assignment) |
| API4 | Unrestricted Resource Consumption | Unsafe defaults: no limits |
| API5 | Broken Function Level Authorization | Missing authorization |
| API6 | Unrestricted Access to Sensitive Business Flows | Trusting the client to behave like a human customer |
| API7 | Server Side Request Forgery | Interpreting input |
| API8 | Security Misconfiguration | Unsafe defaults |
| API9 | Improper Inventory Management | Outside the four: routes and versions nobody tracks |
| API10 | Unsafe Consumption of APIs | Trusting the client, where the “client” is a third-party API you call |
Two things stand out. Three of the ten are authorization (API1, API3, API5). APIs expose object IDs and field names directly, and every one is a key the caller can change. And injection is not a category in the 2023 list, although the 2019 edition had one. Injection did not go away; the project chose to focus on risks that are specific to APIs. API10 is the subtle one: data your service receives from a partner API is as untrusted as data from a browser, even though your code initiated the call.
One API call carries all three keys. Each is checked by a different entry, and each check can be the one that is missing.
flowchart LR
C(["Caller"])
F["<b>PATCH /api/orders/{id}</b><br/><small>the function</small>"]
O["<b>id = 1002</b><br/><small>the object</small>"]
P["<b>body field status</b><br/><small>the property</small>"]
A5["<b>API5</b><br/><small>may this caller call it?</small>"]
A1["<b>API1</b><br/><small>may this caller touch 1002?</small>"]
A3["<b>API3</b><br/><small>may this caller set status?</small>"]
C --> F
C --> O
C --> P
F --> A5
O --> A1
P --> A3
class F,O,P warn
class A5,A1,A3 good
classDef neutral fill:#faf9f5,stroke:#5f5e55,color:#141413,stroke-width:1.5px
classDef compute fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef memory fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef queue fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef io fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef default fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef warn fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:2px,stroke-dasharray:5 3
classDef good fill:#e2efd8,stroke:#2b6a24,color:#141413,stroke-width:2pxCWE and the Top 25#
The CWE Top 25 is computed from published CVE records that carry a CWE mapping, ranked by a score that combines how often a weakness appears and how severe the resulting vulnerabilities are. MITRE publishes it annually. In the 2024 edition, cross-site scripting (CWE-79) ranked first, and out-of-bounds write (CWE-787), SQL injection (CWE-89), cross-site request forgery (CWE-352), and path traversal (CWE-22) filled the rest of the top five.
Two properties of the list matter when you use it:
- It covers all software. Memory-safety weaknesses such as out-of-bounds write and use after free rank high because of code in C and C++. A service in Python, Java, or Go mostly inherits them only through native dependencies.
- It reflects what gets reported. A weakness that is common but hard to detect, or rarely assigned a CVE (business-logic flaws in a private SaaS application, for example), is underrepresented.
The CWE IDs that recur through this course:
| CWE | Name | Root cause | Course lesson |
|---|---|---|---|
| CWE-862 | Missing Authorization | Missing authorization | Authorization |
| CWE-639 | Authorization Bypass Through User-Controlled Key | Missing authorization | Broken object-level authorization |
| CWE-89 | SQL Injection | Interpreting input | Injection and parameterization |
| CWE-79 | Cross-site Scripting | Interpreting input | XSS and output encoding |
| CWE-918 | Server-Side Request Forgery | Interpreting input | Server-side request forgery |
| CWE-22 | Path Traversal | Interpreting input | Files and paths |
| CWE-502 | Deserialization of Untrusted Data | Interpreting input | Parsers and deserialization |
| CWE-287 | Improper Authentication | Trusting the client | Authentication |
| CWE-352 | Cross-Site Request Forgery | Trusting the client | Sessions and cookies |
Learn the distinction between CWE-862, Missing Authorization (there is no check) and CWE-863, Incorrect Authorization (there is a check, and it is wrong). They fail differently and are found differently: the first by listing routes, the second by reading the policy.
The two weaknesses split at one question, and the answer tells you how to look for them.
flowchart TB
R["<b>Route reaches an object or function</b>"]
Q1@{ shape: diam, label: "Is there an authorization check?" }
Q2@{ shape: diam, label: "Does it test server-owned facts?" }
M["<b>CWE-862 Missing Authorization</b><br/><small>found by listing routes</small>"]
I["<b>CWE-863 Incorrect Authorization</b><br/><small>found by reading the policy</small>"]
OK["<b>Check holds</b>"]
R --> Q1
Q1 -->|no| M
Q1 -->|yes| Q2
Q2 -->|"no, e.g. tenant from the body"| I
Q2 -->|yes| OK
class M,I warn
class OK good
classDef neutral fill:#faf9f5,stroke:#5f5e55,color:#141413,stroke-width:1.5px
classDef compute fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef memory fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef queue fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef io fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef default fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef warn fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:2px,stroke-dasharray:5 3
classDef good fill:#e2efd8,stroke:#2b6a24,color:#141413,stroke-width:2pxThe four root causes#
| Root cause | The belief the code should not have held | Systemic fix | Where in this course |
|---|---|---|---|
| Missing authorization | “If they can name it, they may use it.” | One policy decision point called on every function and every object, deny by default | Authorization and centralizing policy |
| Interpreting input | “This string is just data.” | Keep data out of code: parameterized queries, contextual output encoding, allow-listed destinations, parsers that cannot execute | Untrusted input |
| Trusting the client | “The client says so.” | Derive identity, prices, state, and intent on the server | The request is a trust boundary and authentication |
| Unsafe defaults | “The defaults are probably fine.” | Secure configuration as code, reviewed and tested, with insecure options requiring an explicit opt-in | Secure defaults |
Interpreting input deserves a sharper statement because it covers so much. A vulnerability of this class exists whenever attacker data reaches an interpreter in a position where it can change the structure of what is interpreted rather than just fill a slot: SQL, HTML and JavaScript, shell commands, file paths, URLs, XML entities, template languages, serialized object graphs. The fix is always the same in shape: an API that keeps the structure fixed and passes data separately (bound parameters, auto-escaping templates, argument arrays instead of shell strings, an allow-list of destinations, a data format that cannot express code).
The same attacker value can take two routes into an interpreter. Only one of them lets it change what the interpreter runs.
flowchart LR
AT(["Attacker value"])
CAT["<b>Concatenated into the code</b><br/><small>SQL string, HTML, shell string, path</small>"]
SEP["<b>Passed separately</b><br/><small>bound parameter, escaped output, argv, allow-list</small>"]
DB[("Database")]
WEB("Browser")
SH("Shell")
FS("File system")
AT --> CAT
AT --> SEP
CAT -->|"structure changed"| DB
CAT --> WEB
CAT --> SH
CAT --> FS
SEP -.->|"data stays data"| DB
class CAT warn
class SEP good
classDef neutral fill:#faf9f5,stroke:#5f5e55,color:#141413,stroke-width:1.5px
classDef compute fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef memory fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef queue fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef io fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef default fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px
classDef warn fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:2px,stroke-dasharray:5 3
classDef good fill:#e2efd8,stroke:#2b6a24,color:#141413,stroke-width:2pxThe entries that do not fit the four are not less important; they are about what surrounds the code. Cryptographic failures belong to the cryptography course. Vulnerable components are a supply-chain problem, covered in dependencies and supply chain. Logging failures are about detection. Unrestricted resource consumption is largely a capacity design question, worked through in a rate limiter. For systems built around language models, a parallel set of lists exists, and is mapped in frameworks and maps.
The whole map in one view: the four root causes with the CWEs this course uses, and the entries that sit outside them.
mindmap
root((Vulnerability classes))
Missing authorization
CWE-862
CWE-639
Interpreting input
CWE-89 SQL
CWE-79 XSS
CWE-918 SSRF
CWE-22 paths
CWE-502 deserialization
Trusting the client
CWE-287
CWE-352
Unsafe defaults
CWE-611 XXE
CWE-306
Outside the four
Cryptographic failures
Vulnerable components
Logging and monitoring
Inventory of routesCode#
A triage helper. Each simulated finding records which request field carried the attacker’s value and what the server did with it. A rule table maps that to a CWE ID and a root cause, then groups findings by the fix that removes the whole class. The last line shows what happens when a finding fits no rule.
"""root_causes.py: eleven findings, eleven CWEs, four root causes.
Each simulated finding records where attacker data entered and what the server
did with it. A small rule table turns that into a CWE ID and a root cause, then
groups the findings by the fix that removes the whole class. Standard library
only; offline.
"""
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
# What the server did with the value -> (CWE ID, short CWE name, root cause)
RULES: dict[str, tuple[int, str, str]] = {
"object key, no owner check": (639, "Authorization Bypass Through User-Controlled Key", "missing authorization"),
"privileged action, no check": (862, "Missing Authorization", "missing authorization"),
"concatenated into SQL": (89, "SQL Injection", "interpreting input"),
"written into HTML unescaped": (79, "Cross-site Scripting", "interpreting input"),
"fetched as a URL": (918, "Server-Side Request Forgery", "interpreting input"),
"joined onto a file path": (22, "Path Traversal", "interpreting input"),
"deserialized as objects": (502, "Deserialization of Untrusted Data", "interpreting input"),
"believed as identity": (287, "Improper Authentication", "trusting the client"),
"cookie taken as intent": (352, "Cross-Site Request Forgery", "trusting the client"),
"parser default resolves XXE": (611, "XML External Entity Reference", "unsafe defaults"),
"endpoint open by default": (306, "Missing Authentication for Critical Function", "unsafe defaults"),
}
FIXES = {
"missing authorization": "one policy check on every function and every object, deny by default",
"interpreting input": "keep data out of code: parameterize, encode, allow-list, safe parsers",
"trusting the client": "derive identity, intent and state on the server",
"unsafe defaults": "secure configuration as code, verified in CI",
}
@dataclass(frozen=True)
class Finding:
fid: str
where: str # endpoint
source: str # which request field carried the attacker's value
sink: str # what the server did with it; a key of RULES
FINDINGS = [
Finding("F01", "GET /api/orders/{id}", "path id", "object key, no owner check"),
Finding("F02", "DELETE /api/users/{id}", "any session", "privileged action, no check"),
Finding("F03", "GET /api/search?q=", "query string", "concatenated into SQL"),
Finding("F04", "PUT /api/profile", "display_name", "written into HTML unescaped"),
Finding("F05", "POST /api/avatar", "avatar_url", "fetched as a URL"),
Finding("F06", "GET /download?file=", "query string", "joined onto a file path"),
Finding("F07", "any request", "session cookie", "deserialized as objects"),
Finding("F08", "any request", "X-User-Id header", "believed as identity"),
Finding("F09", "POST /api/email", "ambient cookie", "cookie taken as intent"),
Finding("F10", "POST /api/import", "XML body", "parser default resolves XXE"),
Finding("F11", "GET /debug/vars", "nothing", "endpoint open by default"),
]
def classify(f: Finding) -> tuple[int, str, str]:
try:
return RULES[f.sink]
except KeyError:
raise ValueError(f"{f.fid}: no rule for {f.sink!r}; triage by hand") from None
def main() -> None:
groups: dict[str, list[str]] = defaultdict(list)
cwes: set[int] = set()
print(f"{'id':<4} {'endpoint':<23} {'entered via':<17} {'CWE':<8} root cause")
for f in FINDINGS:
cwe, _name, cause = classify(f)
cwes.add(cwe)
groups[cause].append(f"{f.fid}/CWE-{cwe}")
print(f"{f.fid:<4} {f.where:<23} {f.source:<17} {'CWE-' + str(cwe):<8} {cause}")
print(f"\n{len(FINDINGS)} findings, {len(cwes)} distinct CWEs, {len(groups)} root causes\n")
for cause, items in sorted(groups.items(), key=lambda kv: (-len(kv[1]), kv[0])):
print(f"{cause} ({len(items)}): {', '.join(items)}")
print(f" fix once: {FIXES[cause]}")
try:
classify(Finding("F12", "POST /api/coupons", "coupon code", "applied twice in a race"))
except ValueError as e:
print(f"\n{e}")
if __name__ == "__main__":
main()Run it with python3 root_causes.py:
id endpoint entered via CWE root cause
F01 GET /api/orders/{id} path id CWE-639 missing authorization
F02 DELETE /api/users/{id} any session CWE-862 missing authorization
F03 GET /api/search?q= query string CWE-89 interpreting input
F04 PUT /api/profile display_name CWE-79 interpreting input
F05 POST /api/avatar avatar_url CWE-918 interpreting input
F06 GET /download?file= query string CWE-22 interpreting input
F07 any request session cookie CWE-502 interpreting input
F08 any request X-User-Id header CWE-287 trusting the client
F09 POST /api/email ambient cookie CWE-352 trusting the client
F10 POST /api/import XML body CWE-611 unsafe defaults
F11 GET /debug/vars nothing CWE-306 unsafe defaults
11 findings, 11 distinct CWEs, 4 root causes
interpreting input (5): F03/CWE-89, F04/CWE-79, F05/CWE-918, F06/CWE-22, F07/CWE-502
fix once: keep data out of code: parameterize, encode, allow-list, safe parsers
missing authorization (2): F01/CWE-639, F02/CWE-862
fix once: one policy check on every function and every object, deny by default
trusting the client (2): F08/CWE-287, F09/CWE-352
fix once: derive identity, intent and state on the server
unsafe defaults (2): F10/CWE-611, F11/CWE-306
fix once: secure configuration as code, verified in CI
F12: no rule for 'applied twice in a race'; triage by handEleven findings, eleven distinct CWEs, and four fixes. Five of the eleven are one bug in five interpreters. Written up as eleven tickets, they would be fixed one at a time by different people; grouped by root cause, they become a handful of changes to shared code, such as a query layer that only accepts bound parameters or a URL fetcher that only reaches allow-listed hosts. The coupon race (F12) has no rule on purpose: business-logic flaws do not fit the lists well and need a human to name them.
The program’s grouping as a chart: eleven tickets on the left of the output collapse into four bars, and one bar holds almost half the findings.
xychart-beta title "Findings per root cause in root_causes.py" x-axis ["interpreting input", "missing authorization", "trusting the client", "unsafe defaults"] y-axis "findings" 0 --> 6 bar [5, 2, 2, 2]
Where it breaks#
One finding can have several root causes. SSRF is interpreting input (a URL decides where the server connects) and also missing authorization (the server never asked whether this user may make it reach that host). The table picks one; a review should consider both, because each suggests a different control.
One SSRF finding, read both ways. Each reading leads to a different control, and a review wants both.
SSRF has two root causes, and each suggests its own control flowchart LR F5["<b>F05 POST /api/avatar</b><br/><small>avatar_url fetched by the server</small>"] R1["<b>Interpreting input</b><br/><small>a URL decides where the server connects</small>"] R2["<b>Missing authorization</b><br/><small>may this user make it reach that host?</small>"] C1["<b>Allow-list of destinations</b>"] C2["<b>Check the requester, not the deputy</b>"] F5 --> R1 --> C1 F5 --> R2 --> C2 class F5 warn class R1,R2 neutral class C1,C2 good classDef neutral fill:#faf9f5,stroke:#5f5e55,color:#141413,stroke-width:1.5px classDef compute fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef memory fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef queue fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef io fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef default fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:1.5px classDef warn fill:#f9e2e9,stroke:#b3154f,color:#141413,stroke-width:2px,stroke-dasharray:5 3 classDef good fill:#e2efd8,stroke:#2b6a24,color:#141413,stroke-width:2px
CWE mapping is judgment. Two analysts can map the same bug to a parent class or a specific child. A header that sets identity could be filed as CWE-287 or as authentication bypass by spoofing (CWE-290). Pick a convention for your team and keep to it, so trends mean something.
The rule table is a toy. Real triage starts from a scanner report or a penetration test write-up, not from a neatly labeled
sinkfield. The hard part is deciding what the server did with the value; once that is known, the mapping is easy.The lists lag and skew. They are built from what was found and reported, so they favor what tools detect. Authorization flaws and business-logic abuse are more common in real applications than any count of CVEs suggests.
A CWE ID says nothing about severity. A reflected XSS on a static marketing page and a stored XSS in an admin console share CWE-79. Severity comes from context: who can trigger it, what it reaches, as in the ranking of the previous lesson.
Covering the Top 10 is not being secure. Passing a checklist of ten categories leaves out everything the categories do not name. Use the lists to check coverage and to speak a common language, not to define scope.
Remember this#
- OWASP Top 10: web risk categories for awareness. API Security Top 10: API risks, three of them authorization. CWE Top 25: ranked weakness types from CVE data.
- Tag findings with CWE IDs for precision; group them by root cause for fixes.
- Four root causes cover most of what an application team writes: missing authorization, interpreting input, trusting the client, unsafe defaults.
- Interpreting input is any path where data can change the structure of what an interpreter runs.
- CWE-862 means no check; CWE-863 means a wrong check.
- Severity comes from context, not from the category.
Try it#
- Run
root_causes.py. Add a rule for"argument passed to a shell"mapped to CWE-78 (OS command injection) and a finding that uses it. Which group grows? - Add a second root cause to F05 (SSRF) so that a finding can belong to more than one group. How does the summary change, and is the count of root causes still meaningful?
- Take the last security report you received, from a scanner, a penetration test, or a bug bounty. Tag each finding with a CWE ID and a root cause. How many distinct systemic fixes does it reduce to?
- For an API you know, go through the API Security Top 10 and write one line per entry: where it could occur, and which control you have today.
Check yourself#
What is the difference between an OWASP Top 10 category and a CWE ID?
Show answer
A Top 10 category is a broad risk area that maps to many CWEs, chosen for awareness and ranked from testing data and a survey. A CWE ID names one specific weakness type in a maintained catalog, such as CWE-89 for SQL injection. Categories are good for communication and coverage; CWE IDs are good for precise tagging and trend analysis.
Why do three of the ten OWASP API Security risks concern authorization?
Show answer
APIs expose object IDs, field names, and functions directly to the caller, and every one of them is a key the caller can change. Each must be checked against the caller’s authority: the object (API1), the properties within it (API3), and the function itself (API5). Missing any one of these checks is enough for an attacker who can edit a request.
SQL injection, cross-site scripting, path traversal, and SSRF share a root cause. What is it, and what does the shared fix look like?
Show answer
Interpreting input: attacker data reaches an interpreter (the database, the browser, the file system, the network stack) in a position where it can change the structure of what is interpreted. The fix keeps the structure fixed and passes data separately: bound parameters, contextual output encoding, path resolution checked against a base directory, and an allow-list of destinations.
A route has an authorization check that compares the caller’s tenant with the tenant in the request body instead of the tenant that owns the object. Is that CWE-862 or CWE-863?
Show answer
CWE-863, Incorrect Authorization. A check exists, but it tests the wrong thing: a value the client supplied rather than the owner recorded on the server. It is also an instance of trusting the client, which is why it passes casual review.
Why is the CWE Top 25 a poor guide, on its own, to what to fix first in a Python web service?
Show answer
It is ranked across all software, so memory-safety weaknesses from C and C++ code rank high but rarely apply directly. It is also built from reported CVEs, which underrepresent authorization and business-logic flaws in private applications. Prioritize from your own threat model and findings, and use the list as a cross-check.
Sources#
- OWASP Foundation, OWASP Top 10:2021, 2021.
- OWASP API Security Project, OWASP API Security Top 10, 2023 edition, 2023.
- MITRE, Common Weakness Enumeration, including entries CWE-862, CWE-863, CWE-639, CWE-89, CWE-79, CWE-918, CWE-22, CWE-502, CWE-287, CWE-352.
- MITRE and CISA, CWE Top 25 Most Dangerous Software Weaknesses, 2024 edition, November 2024.
- OWASP, Application Security Verification Standard.
← → previous and next lesson