@sikkuumiii/defuse 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +235 -0
- package/README.md +561 -2
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +619 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/core/analyze.d.ts +178 -0
- package/dist/src/core/analyze.js +834 -0
- package/dist/src/core/analyze.js.map +1 -0
- package/dist/src/core/coverage.d.ts +87 -0
- package/dist/src/core/coverage.js +181 -0
- package/dist/src/core/coverage.js.map +1 -0
- package/dist/src/core/finding.d.ts +196 -0
- package/dist/src/core/finding.js +178 -0
- package/dist/src/core/finding.js.map +1 -0
- package/dist/src/core/licence.d.ts +50 -0
- package/dist/src/core/licence.js +56 -0
- package/dist/src/core/licence.js.map +1 -0
- package/dist/src/core/paths.d.ts +21 -0
- package/dist/src/core/paths.js +58 -0
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/score.d.ts +137 -0
- package/dist/src/core/score.js +232 -0
- package/dist/src/core/score.js.map +1 -0
- package/dist/src/dast/targets.d.ts +30 -0
- package/dist/src/dast/targets.js +79 -0
- package/dist/src/dast/targets.js.map +1 -0
- package/dist/src/dast/zap.d.ts +42 -0
- package/dist/src/dast/zap.js +184 -0
- package/dist/src/dast/zap.js.map +1 -0
- package/dist/src/engine/scan.d.ts +80 -0
- package/dist/src/engine/scan.js +129 -0
- package/dist/src/engine/scan.js.map +1 -0
- package/dist/src/engine/shapes.d.ts +47 -0
- package/dist/src/engine/shapes.js +328 -0
- package/dist/src/engine/shapes.js.map +1 -0
- package/dist/src/engine/walk.d.ts +60 -0
- package/dist/src/engine/walk.js +156 -0
- package/dist/src/engine/walk.js.map +1 -0
- package/dist/src/parse/astDump.d.ts +31 -0
- package/dist/src/parse/astDump.js +66 -0
- package/dist/src/parse/astDump.js.map +1 -0
- package/dist/src/parse/languages.d.ts +70 -0
- package/dist/src/parse/languages.js +174 -0
- package/dist/src/parse/languages.js.map +1 -0
- package/dist/src/parse/location.d.ts +17 -0
- package/dist/src/parse/location.js +38 -0
- package/dist/src/parse/location.js.map +1 -0
- package/dist/src/parse/node-grammars.d.ts +12 -0
- package/dist/src/parse/node-grammars.js +35 -0
- package/dist/src/parse/node-grammars.js.map +1 -0
- package/dist/src/parse/parser.d.ts +126 -0
- package/dist/src/parse/parser.js +202 -0
- package/dist/src/parse/parser.js.map +1 -0
- package/dist/src/parse/tree-store.d.ts +139 -0
- package/dist/src/parse/tree-store.js +236 -0
- package/dist/src/parse/tree-store.js.map +1 -0
- package/dist/src/platform/dependency-rule.d.ts +27 -0
- package/dist/src/platform/dependency-rule.js +21 -0
- package/dist/src/platform/dependency-rule.js.map +1 -0
- package/dist/src/platform/deps.d.ts +86 -0
- package/dist/src/platform/deps.js +304 -0
- package/dist/src/platform/deps.js.map +1 -0
- package/dist/src/platform/php-facts.d.ts +49 -0
- package/dist/src/platform/php-facts.js +67 -0
- package/dist/src/platform/php-facts.js.map +1 -0
- package/dist/src/platform/runtime-version.d.ts +78 -0
- package/dist/src/platform/runtime-version.js +573 -0
- package/dist/src/platform/runtime-version.js.map +1 -0
- package/dist/src/report/colors.d.ts +50 -0
- package/dist/src/report/colors.js +97 -0
- package/dist/src/report/colors.js.map +1 -0
- package/dist/src/report/coverage-table.d.ts +90 -0
- package/dist/src/report/coverage-table.js +291 -0
- package/dist/src/report/coverage-table.js.map +1 -0
- package/dist/src/report/html-report.d.ts +66 -0
- package/dist/src/report/html-report.js +489 -0
- package/dist/src/report/html-report.js.map +1 -0
- package/dist/src/report/human.d.ts +44 -0
- package/dist/src/report/human.js +575 -0
- package/dist/src/report/human.js.map +1 -0
- package/dist/src/report/json.d.ts +24 -0
- package/dist/src/report/json.js +178 -0
- package/dist/src/report/json.js.map +1 -0
- package/dist/src/report/mark.d.ts +17 -0
- package/dist/src/report/mark.js +39 -0
- package/dist/src/report/mark.js.map +1 -0
- package/dist/src/report/pricing.d.ts +33 -0
- package/dist/src/report/pricing.js +343 -0
- package/dist/src/report/pricing.js.map +1 -0
- package/dist/src/report/sarif.d.ts +39 -0
- package/dist/src/report/sarif.js +219 -0
- package/dist/src/report/sarif.js.map +1 -0
- package/dist/src/report/site.d.ts +87 -0
- package/dist/src/report/site.js +742 -0
- package/dist/src/report/site.js.map +1 -0
- package/dist/src/rules/code-injection.d.ts +47 -0
- package/dist/src/rules/code-injection.js +326 -0
- package/dist/src/rules/code-injection.js.map +1 -0
- package/dist/src/rules/command-injection.d.ts +30 -0
- package/dist/src/rules/command-injection.js +286 -0
- package/dist/src/rules/command-injection.js.map +1 -0
- package/dist/src/rules/contract.d.ts +198 -0
- package/dist/src/rules/contract.js +39 -0
- package/dist/src/rules/contract.js.map +1 -0
- package/dist/src/rules/format-string.d.ts +46 -0
- package/dist/src/rules/format-string.js +146 -0
- package/dist/src/rules/format-string.js.map +1 -0
- package/dist/src/rules/hardcoded-secret.d.ts +38 -0
- package/dist/src/rules/hardcoded-secret.js +440 -0
- package/dist/src/rules/hardcoded-secret.js.map +1 -0
- package/dist/src/rules/lib/guards.d.ts +168 -0
- package/dist/src/rules/lib/guards.js +1179 -0
- package/dist/src/rules/lib/guards.js.map +1 -0
- package/dist/src/rules/lib/html-safety.d.ts +75 -0
- package/dist/src/rules/lib/html-safety.js +755 -0
- package/dist/src/rules/lib/html-safety.js.map +1 -0
- package/dist/src/rules/lib/local-build.d.ts +59 -0
- package/dist/src/rules/lib/local-build.js +320 -0
- package/dist/src/rules/lib/local-build.js.map +1 -0
- package/dist/src/rules/lib/strings.d.ts +208 -0
- package/dist/src/rules/lib/strings.js +1970 -0
- package/dist/src/rules/lib/strings.js.map +1 -0
- package/dist/src/rules/path-traversal.d.ts +33 -0
- package/dist/src/rules/path-traversal.js +96 -0
- package/dist/src/rules/path-traversal.js.map +1 -0
- package/dist/src/rules/registry.d.ts +19 -0
- package/dist/src/rules/registry.js +77 -0
- package/dist/src/rules/registry.js.map +1 -0
- package/dist/src/rules/sql-injection.d.ts +36 -0
- package/dist/src/rules/sql-injection.js +299 -0
- package/dist/src/rules/sql-injection.js.map +1 -0
- package/dist/src/rules/ssrf.d.ts +39 -0
- package/dist/src/rules/ssrf.js +111 -0
- package/dist/src/rules/ssrf.js.map +1 -0
- package/dist/src/rules/unbounded-copy.d.ts +37 -0
- package/dist/src/rules/unbounded-copy.js +147 -0
- package/dist/src/rules/unbounded-copy.js.map +1 -0
- package/dist/src/rules/unsafe-deserialization.d.ts +43 -0
- package/dist/src/rules/unsafe-deserialization.js +276 -0
- package/dist/src/rules/unsafe-deserialization.js.map +1 -0
- package/dist/src/rules/weak-hash.d.ts +37 -0
- package/dist/src/rules/weak-hash.js +197 -0
- package/dist/src/rules/weak-hash.js.map +1 -0
- package/dist/src/rules/xss.d.ts +38 -0
- package/dist/src/rules/xss.js +527 -0
- package/dist/src/rules/xss.js.map +1 -0
- package/dist/src/taint/content-type.d.ts +19 -0
- package/dist/src/taint/content-type.js +20 -0
- package/dist/src/taint/content-type.js.map +1 -0
- package/dist/src/taint/dictionaries.d.ts +23 -0
- package/dist/src/taint/dictionaries.js +1778 -0
- package/dist/src/taint/dictionaries.js.map +1 -0
- package/dist/src/taint/fold.d.ts +59 -0
- package/dist/src/taint/fold.js +1177 -0
- package/dist/src/taint/fold.js.map +1 -0
- package/dist/src/taint/java-scope.d.ts +61 -0
- package/dist/src/taint/java-scope.js +212 -0
- package/dist/src/taint/java-scope.js.map +1 -0
- package/dist/src/taint/local-map.d.ts +79 -0
- package/dist/src/taint/local-map.js +603 -0
- package/dist/src/taint/local-map.js.map +1 -0
- package/dist/src/taint/project.d.ts +172 -0
- package/dist/src/taint/project.js +617 -0
- package/dist/src/taint/project.js.map +1 -0
- package/dist/src/taint/tracer.d.ts +265 -0
- package/dist/src/taint/tracer.js +3817 -0
- package/dist/src/taint/tracer.js.map +1 -0
- package/dist/src/taint/types.d.ts +484 -0
- package/dist/src/taint/types.js +47 -0
- package/dist/src/taint/types.js.map +1 -0
- package/dist/src/taint/visibility.d.ts +25 -0
- package/dist/src/taint/visibility.js +96 -0
- package/dist/src/taint/visibility.js.map +1 -0
- package/docs/examples/defuse.json +20 -0
- package/docs/examples/dvwa-urls.json +14 -0
- package/docs/examples/github-code-scanning.yml +54 -0
- package/docs/examples/tiny.js +4 -0
- package/package.json +73 -4
- package/src/cli.ts +747 -0
- package/src/core/analyze.ts +1072 -0
- package/src/core/coverage.ts +269 -0
- package/src/core/finding.ts +311 -0
- package/src/core/licence.ts +61 -0
- package/src/core/paths.ts +59 -0
- package/src/core/score.ts +353 -0
- package/src/dast/targets.ts +121 -0
- package/src/dast/zap.ts +246 -0
- package/src/engine/scan.ts +200 -0
- package/src/engine/shapes.ts +348 -0
- package/src/engine/walk.ts +196 -0
- package/src/parse/astDump.ts +85 -0
- package/src/parse/languages.ts +232 -0
- package/src/parse/location.ts +44 -0
- package/src/parse/node-grammars.ts +41 -0
- package/src/parse/parser.ts +262 -0
- package/src/parse/tree-store.ts +301 -0
- package/src/platform/dependency-rule.ts +33 -0
- package/src/platform/deps.ts +374 -0
- package/src/platform/php-facts.ts +85 -0
- package/src/platform/runtime-version.ts +710 -0
- package/src/report/colors.ts +107 -0
- package/src/report/coverage-table.ts +359 -0
- package/src/report/html-report.ts +601 -0
- package/src/report/human.ts +811 -0
- package/src/report/json.ts +189 -0
- package/src/report/mark.ts +47 -0
- package/src/report/pricing.ts +374 -0
- package/src/report/sarif.ts +235 -0
- package/src/report/site.ts +760 -0
- package/src/rules/code-injection.ts +346 -0
- package/src/rules/command-injection.ts +296 -0
- package/src/rules/contract.ts +220 -0
- package/src/rules/format-string.ts +158 -0
- package/src/rules/hardcoded-secret.ts +485 -0
- package/src/rules/lib/guards.ts +1235 -0
- package/src/rules/lib/html-safety.ts +793 -0
- package/src/rules/lib/local-build.ts +329 -0
- package/src/rules/lib/strings.ts +2034 -0
- package/src/rules/path-traversal.ts +101 -0
- package/src/rules/registry.ts +90 -0
- package/src/rules/sql-injection.ts +311 -0
- package/src/rules/ssrf.ts +118 -0
- package/src/rules/unbounded-copy.ts +162 -0
- package/src/rules/unsafe-deserialization.ts +291 -0
- package/src/rules/weak-hash.ts +226 -0
- package/src/rules/xss.ts +572 -0
- package/src/taint/content-type.ts +20 -0
- package/src/taint/dictionaries.ts +1855 -0
- package/src/taint/fold.ts +1206 -0
- package/src/taint/java-scope.ts +200 -0
- package/src/taint/local-map.ts +594 -0
- package/src/taint/project.ts +774 -0
- package/src/taint/tracer.ts +4298 -0
- package/src/taint/types.ts +511 -0
- package/src/taint/visibility.ts +100 -0
- package/tsconfig.json +61 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WEAK / BROKEN CRYPTOGRAPHIC HASH (CWE-327, CWE-328,
|
|
3
|
+
* OWASP A04:2025 - Cryptographic Failures)
|
|
4
|
+
*
|
|
5
|
+
* WHAT A HASH IS, in plain language:
|
|
6
|
+
* A hash function turns any input into a fixed-length fingerprint. Two
|
|
7
|
+
* properties are supposed to hold: you cannot work backwards from the
|
|
8
|
+
* fingerprint to the input, and you cannot find two different inputs with the
|
|
9
|
+
* same fingerprint (a "collision").
|
|
10
|
+
*
|
|
11
|
+
* MD5 and SHA-1 no longer have the second property. Collisions in MD5 are
|
|
12
|
+
* trivial today - seconds on a laptop. SHA-1 collisions were demonstrated in
|
|
13
|
+
* 2017 and have only got cheaper. Anything that relies on "this fingerprint
|
|
14
|
+
* uniquely identifies this content" - signatures, integrity checks, tokens -
|
|
15
|
+
* is broken when built on them.
|
|
16
|
+
*
|
|
17
|
+
* PASSWORDS ARE A SEPARATE PROBLEM, and a worse one:
|
|
18
|
+
* MD5 and SHA-1 are FAST, and for password storage fast is the flaw. A modern
|
|
19
|
+
* GPU tries billions of MD5 guesses per second, so a stolen table of MD5
|
|
20
|
+
* password hashes is effectively a table of passwords. Password hashing needs a
|
|
21
|
+
* function designed to be SLOW and memory-hungry - bcrypt, scrypt, or Argon2 -
|
|
22
|
+
* with a unique random salt per password.
|
|
23
|
+
*
|
|
24
|
+
* WHAT TO USE INSTEAD:
|
|
25
|
+
* integrity / fingerprints -> SHA-256 or SHA-3
|
|
26
|
+
* passwords -> Argon2id, scrypt, or bcrypt
|
|
27
|
+
* message authentication -> HMAC-SHA256
|
|
28
|
+
*
|
|
29
|
+
* HONESTY NOTE, and it is a real one:
|
|
30
|
+
* MD5 is still perfectly reasonable for NON-security work - cache keys, ETags,
|
|
31
|
+
* sharding, deduplication, "has this file changed". A scanner cannot tell why
|
|
32
|
+
* you called it. So this rule is severity MEDIUM, not high, and every finding
|
|
33
|
+
* says out loud that purpose is unknown. Flagging a cache key as a critical
|
|
34
|
+
* vulnerability is how security tools train people to ignore them.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import type { LanguageId } from '../parse/languages.js';
|
|
38
|
+
import { asCall, type Rule, type RuleHit } from './contract.js';
|
|
39
|
+
import { unquote } from './lib/strings.js';
|
|
40
|
+
|
|
41
|
+
/** Constructors whose NAME alone identifies the algorithm. */
|
|
42
|
+
const DIRECT_WEAK_CALLS: Record<LanguageId, readonly string[]> = {
|
|
43
|
+
javascript: [],
|
|
44
|
+
typescript: [],
|
|
45
|
+
python: ['md5', 'sha1'], // hashlib.md5(...)
|
|
46
|
+
java: ['md5Hex', 'sha1Hex', 'md5', 'sha1'], // Apache DigestUtils
|
|
47
|
+
go: ['New', 'Sum', 'New224'], // md5.New(), sha1.Sum() - receiver tells us which
|
|
48
|
+
php: ['md5', 'sha1', 'md5_file', 'sha1_file', 'crc32'],
|
|
49
|
+
/*
|
|
50
|
+
* OpenSSL's one-shot and streaming APIs, which is how virtually all C code
|
|
51
|
+
* hashes anything. MD5(), SHA1() and their _Init/_Update/_Final triples name
|
|
52
|
+
* the algorithm in the symbol, so the name alone is the whole signal - no
|
|
53
|
+
* receiver scoping needed, unlike Go's `md5.New()`.
|
|
54
|
+
*/
|
|
55
|
+
c: ['MD5', 'SHA1', 'MD5_Init', 'SHA1_Init', 'MD4', 'MD4_Init', 'CC_MD5', 'CC_SHA1'],
|
|
56
|
+
cpp: ['MD5', 'SHA1', 'MD5_Init', 'SHA1_Init', 'MD4', 'MD4_Init', 'CC_MD5', 'CC_SHA1'],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Calls that take the algorithm as a STRING argument. */
|
|
60
|
+
const ALGORITHM_ARG_CALLS: Record<LanguageId, readonly string[]> = {
|
|
61
|
+
javascript: ['createHash', 'createHmac'],
|
|
62
|
+
typescript: ['createHash', 'createHmac'],
|
|
63
|
+
python: ['new'], // hashlib.new("md5")
|
|
64
|
+
java: ['getInstance'], // MessageDigest.getInstance("MD5")
|
|
65
|
+
go: [],
|
|
66
|
+
php: ['hash', 'hash_file', 'hash_init'], // hash("md5", $x)
|
|
67
|
+
// EVP_get_digestbyname("md5") is the string-argument form in OpenSSL.
|
|
68
|
+
c: ['EVP_get_digestbyname', 'EVP_MD_fetch'],
|
|
69
|
+
cpp: ['EVP_get_digestbyname', 'EVP_MD_fetch'],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const WEAK_ALGORITHMS = /^(md[245]|sha-?1|md5-sess|ripemd128?)$/i;
|
|
73
|
+
|
|
74
|
+
/** Receivers that identify a weak package, e.g. Go's `md5.New()`. */
|
|
75
|
+
const WEAK_RECEIVERS = /^(md5|sha1)$/i;
|
|
76
|
+
|
|
77
|
+
const PASSWORDISH = /pass(word|wd)?|pwd|credential|login|auth/i;
|
|
78
|
+
|
|
79
|
+
export const weakHashRule: Rule = {
|
|
80
|
+
id: 'weak-hash',
|
|
81
|
+
name: 'Broken or unsuitable hash algorithm',
|
|
82
|
+
cwe: 'CWE-327',
|
|
83
|
+
owasp: 'A04:2025 Cryptographic Failures',
|
|
84
|
+
severity: 'medium',
|
|
85
|
+
summary: 'MD5 or SHA-1 is used where security depends on it, and both can be broken cheaply today.',
|
|
86
|
+
fix:
|
|
87
|
+
'Use SHA-256 for integrity, HMAC-SHA256 for authentication, and Argon2id or bcrypt for passwords.',
|
|
88
|
+
explanation:
|
|
89
|
+
'MD5 and SHA-1 are broken for security use: finding two inputs with the same ' +
|
|
90
|
+
'fingerprint is cheap today, so they cannot be trusted for signatures or integrity. ' +
|
|
91
|
+
'They are also far too fast for password storage, where a GPU can try billions of ' +
|
|
92
|
+
'guesses per second. Use SHA-256 for integrity, HMAC-SHA256 for authentication, ' +
|
|
93
|
+
'and Argon2id/scrypt/bcrypt for passwords.',
|
|
94
|
+
limitations:
|
|
95
|
+
'UNVERIFIED (signature-based): we identified the algorithm from the call, not the ' +
|
|
96
|
+
'purpose. MD5 used for a cache key, an ETag or deduplication is FINE and this ' +
|
|
97
|
+
'finding is then a false positive. We cannot see what the digest is USED for. The ' +
|
|
98
|
+
'taint engine follows attacker input forwards into sinks; answering "what happens to ' +
|
|
99
|
+
'this hash afterwards" is the opposite direction and is not implemented. The one ' +
|
|
100
|
+
'exception is Python\'s `usedforsecurity=False`, which states the purpose in the call: ' +
|
|
101
|
+
'such a call is withdrawn and listed as proved clean, and the receipt says it rests ' +
|
|
102
|
+
'on that statement being true.',
|
|
103
|
+
shapes: ['call'],
|
|
104
|
+
support: {
|
|
105
|
+
javascript: {
|
|
106
|
+
status: 'implemented',
|
|
107
|
+
note: "Node crypto.createHash('md5'|'sha1') and createHmac. Third-party libraries with their own APIs (crypto-js, blueimp-md5) are not covered.",
|
|
108
|
+
},
|
|
109
|
+
typescript: { status: 'implemented', note: 'Identical to JavaScript.' },
|
|
110
|
+
python: {
|
|
111
|
+
status: 'implemented',
|
|
112
|
+
note: 'hashlib.md5/sha1 and hashlib.new("md5"). A call passing the literal keyword `usedforsecurity=False` (Python 3.9+) is withdrawn with a proved-clean receipt: it is the programmer stating in the code that the digest is not for security. A flag held in a variable is not read, and still reports.',
|
|
113
|
+
},
|
|
114
|
+
java: {
|
|
115
|
+
status: 'implemented',
|
|
116
|
+
note: 'MessageDigest.getInstance("MD5"/"SHA-1") and Apache Commons DigestUtils.md5Hex/sha1Hex. Algorithm names built from a variable are not resolved.',
|
|
117
|
+
},
|
|
118
|
+
go: {
|
|
119
|
+
status: 'partial',
|
|
120
|
+
note: 'PARTIAL: matched by receiver package name (md5.New, sha1.Sum). If the package is imported under an alias (`import h "crypto/md5"`) we will miss it, because we match on the receiver text rather than on resolved imports.',
|
|
121
|
+
},
|
|
122
|
+
php: {
|
|
123
|
+
status: 'implemented',
|
|
124
|
+
note: 'Covers md5(), sha1(), their _file variants, crc32(), and hash("md5", ...). PHP names the algorithm in the function itself far more often than the others do, which makes this the most reliable weak-hash coverage in the tool.',
|
|
125
|
+
},
|
|
126
|
+
c: {
|
|
127
|
+
status: 'implemented',
|
|
128
|
+
note:
|
|
129
|
+
'OpenSSL\'s one-shot and streaming APIs, which is how nearly all C code hashes: MD5, SHA1, MD4 and their _Init variants, plus Apple\'s CC_MD5/CC_SHA1 and the string-argument form EVP_get_digestbyname("md5"). The algorithm is named in the symbol itself, so no receiver scoping is needed - which makes this more reliable in C than the Go coverage, where the package name is the only clue.',
|
|
130
|
+
},
|
|
131
|
+
cpp: {
|
|
132
|
+
status: 'implemented',
|
|
133
|
+
note:
|
|
134
|
+
'Every C entry applies unchanged, since C++ inherits the whole libc surface and real code still uses it. Namespace-qualified calls (std::system) and method calls on objects are recognised in addition.',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
check(shape, ctx): RuleHit | null {
|
|
138
|
+
const call = asCall(shape);
|
|
139
|
+
if (!call) return null;
|
|
140
|
+
const language = ctx.language;
|
|
141
|
+
|
|
142
|
+
const report = (algorithm: string, evidence: string): RuleHit => {
|
|
143
|
+
const wholeCall = call.node.text ?? '';
|
|
144
|
+
const looksLikePasswordUse = PASSWORDISH.test(wholeCall);
|
|
145
|
+
return {
|
|
146
|
+
node: call.node,
|
|
147
|
+
severity: looksLikePasswordUse ? 'high' : 'medium',
|
|
148
|
+
message: `${algorithm.toUpperCase()} used via ${call.calleeText}()`,
|
|
149
|
+
reasoning:
|
|
150
|
+
`${evidence} ${algorithm.toUpperCase()} is a broken hash for security purposes: ` +
|
|
151
|
+
`producing two different inputs with the same digest is cheap on ordinary ` +
|
|
152
|
+
`hardware, and the function is fast enough that guessing inputs is cheap too. ` +
|
|
153
|
+
(looksLikePasswordUse
|
|
154
|
+
? `The surrounding code mentions a password or credential, which makes this a ` +
|
|
155
|
+
`likely password-hashing use - the worst case. Use Argon2id, scrypt or bcrypt.`
|
|
156
|
+
: `We cannot tell what this digest is for. If it is a cache key or a checksum ` +
|
|
157
|
+
`this is harmless; if it protects anything, move to SHA-256 or HMAC-SHA256.`),
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
* PYTHON SAYS WHAT THE HASH IS FOR. `hashlib.md5(x, usedforsecurity=False)`
|
|
163
|
+
* is the one place the purpose this rule cannot see is written in the call
|
|
164
|
+
* (Python 3.9+). The keyword must be a real keyword argument with the
|
|
165
|
+
* literal False - not text in a comment or a string, not a variable - and
|
|
166
|
+
* the withdrawal leaves a receipt saying what it rests on
|
|
167
|
+
* (tests/fixtures/vulnerable/hash-purpose.py).
|
|
168
|
+
*/
|
|
169
|
+
const declaredNotForSecurity =
|
|
170
|
+
language === 'python' &&
|
|
171
|
+
call.args.some(
|
|
172
|
+
(arg) =>
|
|
173
|
+
arg.type === 'keyword_argument' &&
|
|
174
|
+
arg.childForFieldName('name')?.text === 'usedforsecurity' &&
|
|
175
|
+
arg.childForFieldName('value')?.type === 'false',
|
|
176
|
+
);
|
|
177
|
+
const withdraw = (algorithm: string): null => {
|
|
178
|
+
ctx.noteClean?.(
|
|
179
|
+
'weak-hash',
|
|
180
|
+
call.node,
|
|
181
|
+
`The call passes usedforsecurity=False: Python's own declaration that this ` +
|
|
182
|
+
`${algorithm.toUpperCase()} digest is not used for security (a cache key, an ETag, a ` +
|
|
183
|
+
`checksum), so it is not reported. This rests on that statement being true - if the ` +
|
|
184
|
+
`digest does protect something, the declaration is wrong and so is this receipt.`,
|
|
185
|
+
);
|
|
186
|
+
return null;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/* ---- Algorithm named in a string argument: createHash("md5") ---- */
|
|
190
|
+
if (ALGORITHM_ARG_CALLS[language].includes(call.calleeName)) {
|
|
191
|
+
for (const arg of call.args) {
|
|
192
|
+
const text = unquote((arg.text ?? '').trim());
|
|
193
|
+
if (WEAK_ALGORITHMS.test(text)) {
|
|
194
|
+
if (declaredNotForSecurity) return withdraw(text);
|
|
195
|
+
return report(
|
|
196
|
+
text,
|
|
197
|
+
`\`${call.calleeText}("${text}")\` selects the algorithm by name.`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ---- Algorithm implied by the function or package name ---- */
|
|
205
|
+
if (DIRECT_WEAK_CALLS[language].includes(call.calleeName)) {
|
|
206
|
+
// Go: md5.New() - the receiver is the package. Python: hashlib.md5().
|
|
207
|
+
if (WEAK_RECEIVERS.test(call.receiverText)) {
|
|
208
|
+
return report(
|
|
209
|
+
call.receiverText,
|
|
210
|
+
`\`${call.calleeText}()\` comes from the ${call.receiverText} package.`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (WEAK_ALGORITHMS.test(call.calleeName)) {
|
|
214
|
+
if (declaredNotForSecurity) return withdraw(call.calleeName);
|
|
215
|
+
return report(call.calleeName, `\`${call.calleeText}()\` names the algorithm directly.`);
|
|
216
|
+
}
|
|
217
|
+
// DigestUtils.md5Hex / sha1Hex
|
|
218
|
+
const stripped = call.calleeName.replace(/(Hex|Base64|Digest)$/i, '');
|
|
219
|
+
if (WEAK_ALGORITHMS.test(stripped)) {
|
|
220
|
+
return report(stripped, `\`${call.calleeText}()\` names the algorithm directly.`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
},
|
|
226
|
+
};
|