|
| 1 | +"""Verified real world precedent for a weakness class. |
| 2 | +
|
| 3 | +The point is factual grounding. A language model asked for a real world |
| 4 | +example of a weakness will happily invent a CVE number, a company and a breach |
| 5 | +size. So the examples do not come from the model: they live here, vetted, each |
| 6 | +one a well documented public incident with a reference a reader can check. The |
| 7 | +triage prompt is handed the matching entry as a fact to reason from, and the |
| 8 | +report renders it straight from this table, so what a reader sees is sourced |
| 9 | +here, not from the model's memory. |
| 10 | +
|
| 11 | +Each entry is one weakness class: how an attacker abuses it, a documented real |
| 12 | +world case, and a checkable reference. A class is only listed when there is a |
| 13 | +genuinely well documented public incident for it; a low or informational |
| 14 | +finding with no real precedent gets none, rather than an inflated one. |
| 15 | +
|
| 16 | +Match order matters: the most specific, highest impact classes come first, so |
| 17 | +an injection that leads to code execution is read as code execution, not as a |
| 18 | +generic injection. |
| 19 | +""" |
| 20 | + |
| 21 | +from __future__ import annotations |
| 22 | + |
| 23 | +import re |
| 24 | + |
| 25 | +# Each: a matcher over the finding's text, the OWASP and CWE anchors, how the |
| 26 | +# class is abused, and a documented real world case with a reference. Keep the |
| 27 | +# facts conservative and checkable; never add a case that is not well sourced. |
| 28 | +PRECEDENTS = [ |
| 29 | + { |
| 30 | + "key": "rce", |
| 31 | + "match": re.compile(r"remote code|command inj|\brce\b|code exec|os command|" |
| 32 | + r"deserial|CWE-?(?:78|94|77|502)\b", re.I), |
| 33 | + "ref": "CVE-2017-5638; OWASP A03:2021", |
| 34 | + "abuse": "The attacker sends crafted input that the server runs as code, " |
| 35 | + "takes control of the host, then reaches the data and systems " |
| 36 | + "behind it.", |
| 37 | + "precedent": "The 2017 Equifax breach, which exposed data on about 147 " |
| 38 | + "million people, began with an unpatched remote code " |
| 39 | + "execution flaw in Apache Struts (CVE-2017-5638).", |
| 40 | + }, |
| 41 | + { |
| 42 | + "key": "ssrf", |
| 43 | + "match": re.compile(r"\bssrf\b|server.side request|CWE-?918\b", re.I), |
| 44 | + "ref": "OWASP A10:2021; CWE-918", |
| 45 | + "abuse": "The attacker makes the server fetch a URL of their choosing, " |
| 46 | + "reaching internal services the outside cannot, such as a cloud " |
| 47 | + "metadata endpoint, and reading back what it returns.", |
| 48 | + "precedent": "The 2019 Capital One breach, which affected about 100 " |
| 49 | + "million people, used server side request forgery to reach " |
| 50 | + "the cloud metadata service and take credentials from it.", |
| 51 | + }, |
| 52 | + { |
| 53 | + "key": "sqli", |
| 54 | + "match": re.compile(r"sql\s*inj|\bsqli\b|CWE-?89\b", re.I), |
| 55 | + "ref": "OWASP A03:2021; CWE-89", |
| 56 | + "abuse": "The attacker puts database syntax into a parameter to read or " |
| 57 | + "change data the query never meant to expose, often dumping whole " |
| 58 | + "tables of accounts or payment records.", |
| 59 | + "precedent": "SQL injection was the entry point for the 2008 Heartland " |
| 60 | + "Payment Systems breach, about 130 million card numbers, and " |
| 61 | + "the 2015 TalkTalk breach, for which the UK regulator fined " |
| 62 | + "the company.", |
| 63 | + }, |
| 64 | + { |
| 65 | + "key": "xss", |
| 66 | + "match": re.compile(r"\bxss\b|cross.site.script|CWE-?79\b", re.I), |
| 67 | + "ref": "OWASP A03:2021; CWE-79", |
| 68 | + "abuse": "The attacker gets script to run in another user's session, so it " |
| 69 | + "acts as that user: stealing the session, changing their account, " |
| 70 | + "or spreading to everyone who views the page.", |
| 71 | + "precedent": "The 2005 Samy worm used a stored cross site script to add " |
| 72 | + "over a million MySpace friends in under a day, by running in " |
| 73 | + "the session of everyone who viewed an infected profile.", |
| 74 | + }, |
| 75 | + { |
| 76 | + "key": "bola", |
| 77 | + "match": re.compile(r"\bidor\b|\bbola\b|broken object|object level auth|" |
| 78 | + r"insecure direct object|CWE-?639\b", re.I), |
| 79 | + "ref": "OWASP API1:2023; CWE-639", |
| 80 | + "abuse": "The attacker changes an id in a URL or request and reads or edits " |
| 81 | + "records that belong to other users, walking the ids to reach many " |
| 82 | + "accounts at once.", |
| 83 | + "precedent": "The 2019 First American Financial exposure, about 885 million " |
| 84 | + "documents, and the 2018 USPS Informed Visibility API both let " |
| 85 | + "anyone read other people's records by changing an id, each " |
| 86 | + "documented publicly at the time.", |
| 87 | + }, |
| 88 | + { |
| 89 | + "key": "secret", |
| 90 | + "match": re.compile(r"api.?key|secret|hardcoded|private key|credential|" |
| 91 | + r"access.key|\.htpasswd|\.npmrc|id_rsa|CWE-?798\b", re.I), |
| 92 | + "ref": "CWE-798; OWASP A05:2021", |
| 93 | + "abuse": "The attacker reads the exposed key or password and uses it to sign " |
| 94 | + "in to the service it belongs to, with whatever access that key " |
| 95 | + "was granted, no guessing required.", |
| 96 | + "precedent": "The 2016 Uber breach, about 57 million riders and drivers, " |
| 97 | + "happened because a cloud access key sat in a code repository " |
| 98 | + "the attackers were able to reach.", |
| 99 | + }, |
| 100 | + { |
| 101 | + "key": "graphql-amplification", |
| 102 | + "match": re.compile(r"amplif|aliased|batch|rate limit|CWE-?770\b|" |
| 103 | + r"API4:2023", re.I), |
| 104 | + "ref": "OWASP API4:2023; CWE-770", |
| 105 | + "abuse": "The attacker packs many operations into one request, so a single " |
| 106 | + "call becomes many login or one time code attempts, and the per " |
| 107 | + "request rate limit and edge counting never see the real volume.", |
| 108 | + "precedent": "Bypassing login and one time code rate limits by batching or " |
| 109 | + "aliasing GraphQL operations is a documented technique, " |
| 110 | + "recognised in the OWASP API Security Top 10 as unrestricted " |
| 111 | + "resource consumption.", |
| 112 | + }, |
| 113 | + { |
| 114 | + "key": "credential-stuffing", |
| 115 | + "match": re.compile(r"brute.?forc|credential stuff|weak password|" |
| 116 | + r"password spray|CWE-?307\b", re.I), |
| 117 | + "ref": "OWASP A07:2021; CWE-307", |
| 118 | + "abuse": "The attacker tries large lists of guessed or reused passwords " |
| 119 | + "against the login, and every account whose password is weak or " |
| 120 | + "reused is taken over.", |
| 121 | + "precedent": "Credential stuffing against Nintendo in 2020 compromised " |
| 122 | + "about 300,000 accounts by reusing passwords leaked from other " |
| 123 | + "sites.", |
| 124 | + }, |
| 125 | + { |
| 126 | + "key": "data-exposure", |
| 127 | + "match": re.compile(r"directory listing|directory index|exposed backup|" |
| 128 | + r"\.bak\b|\.sql\b|open to the world|publicly readable|" |
| 129 | + r"exposed .*(database|dump|backup)|CWE-?(?:538|548)\b", re.I), |
| 130 | + "ref": "OWASP A05:2021; CWE-200", |
| 131 | + "abuse": "The attacker reads files or listings the site left open, taking " |
| 132 | + "backups, database dumps or source that were never meant to be " |
| 133 | + "public.", |
| 134 | + "precedent": "Misconfigured, publicly readable cloud storage has exposed " |
| 135 | + "data repeatedly, such as the 2017 exposure of about 14 " |
| 136 | + "million Verizon customer records left open on a storage " |
| 137 | + "bucket.", |
| 138 | + }, |
| 139 | +] |
| 140 | + |
| 141 | + |
| 142 | +def match(f: dict): |
| 143 | + """The precedent for a finding's weakness class, or None. |
| 144 | +
|
| 145 | + Matched on the finding's own text, most specific class first, so nothing is |
| 146 | + attached unless the finding clearly belongs to a class with a documented |
| 147 | + real world case.""" |
| 148 | + text = " ".join(str(f.get(k) or "") for k in ("title", "cwe", "evidence", "url")) |
| 149 | + for p in PRECEDENTS: |
| 150 | + if p["match"].search(text): |
| 151 | + return {"class": p["key"], "abuse": p["abuse"], |
| 152 | + "precedent": p["precedent"], "reference": p["ref"]} |
| 153 | + return None |
| 154 | + |
| 155 | + |
| 156 | +def annotate(findings: list) -> list: |
| 157 | + """Add the verified abuse description, real world precedent and reference to |
| 158 | + each finding that belongs to a documented class, in place. Findings with no |
| 159 | + documented precedent are left untouched, never given an inflated one.""" |
| 160 | + for f in findings: |
| 161 | + p = match(f) |
| 162 | + if p: |
| 163 | + f["abuse"] = p["abuse"] |
| 164 | + f["precedent"] = p["precedent"] |
| 165 | + f["precedent_ref"] = p["reference"] |
| 166 | + f["weakness_class"] = p["class"] |
| 167 | + return findings |
0 commit comments