circle-ir 3.80.0 → 3.81.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/configs/sinks/xss.yaml +2 -1
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +6 -1
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/_credential-helpers.d.ts +40 -0
- package/dist/analysis/passes/_credential-helpers.d.ts.map +1 -0
- package/dist/analysis/passes/_credential-helpers.js +152 -0
- package/dist/analysis/passes/_credential-helpers.js.map +1 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +42 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.js +196 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.d.ts +47 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.d.ts.map +1 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.js +159 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.js.map +1 -0
- package/dist/analysis/passes/weak-password-encoding-pass.d.ts +40 -0
- package/dist/analysis/passes/weak-password-encoding-pass.d.ts.map +1 -0
- package/dist/analysis/passes/weak-password-encoding-pass.js +157 -0
- package/dist/analysis/passes/weak-password-encoding-pass.js.map +1 -0
- package/dist/analysis/passes/weak-password-hash-pass.d.ts +49 -0
- package/dist/analysis/passes/weak-password-hash-pass.d.ts.map +1 -0
- package/dist/analysis/passes/weak-password-hash-pass.js +225 -0
- package/dist/analysis/passes/weak-password-hash-pass.js.map +1 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +12 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +564 -1
- package/dist/core/circle-ir-core.cjs +6 -1
- package/dist/core/circle-ir-core.js +6 -1
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: plaintext-password-storage (CWE-256, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects writing a credential-named identifier to a persistent store
|
|
5
|
+
* (file, KV store, cookie, database) without first passing it through a
|
|
6
|
+
* cryptographic hash / KDF.
|
|
7
|
+
*
|
|
8
|
+
* Detection per language:
|
|
9
|
+
* Python:
|
|
10
|
+
* - `open(...).write(password)` / `f.write(password)`
|
|
11
|
+
* - `pickle.dump(password, ...)` / `json.dump(...)` / `yaml.dump(...)`
|
|
12
|
+
* - `redis.set(key, password)`
|
|
13
|
+
* JS/TS:
|
|
14
|
+
* - `fs.writeFile|writeFileSync|appendFile(path, password)`
|
|
15
|
+
* - `localStorage.setItem(key, password)` / `sessionStorage.setItem`
|
|
16
|
+
* - `redis.set(key, password)`
|
|
17
|
+
* Java:
|
|
18
|
+
* - `Files.write|writeString(path, password)`
|
|
19
|
+
* - `FileWriter.write(password)`
|
|
20
|
+
* Go:
|
|
21
|
+
* - `os.WriteFile(name, []byte(password), ...)`
|
|
22
|
+
* - `f.WriteString(password)` / `f.Write([]byte(password))`
|
|
23
|
+
*
|
|
24
|
+
* Heuristic for "not hashed": intraprocedural — walk all calls earlier
|
|
25
|
+
* in the same `in_method` scope; if any of them is a known hash/KDF
|
|
26
|
+
* (see _credential-helpers `isHashFunctionCall`) and consumes the
|
|
27
|
+
* credential identifier, suppress.
|
|
28
|
+
*
|
|
29
|
+
* This is intentionally lightweight (no full DFG); FP risk skewed toward
|
|
30
|
+
* recall loss for cross-function hashing (controller → service.hash →
|
|
31
|
+
* repo.store), which is acceptable for v1.
|
|
32
|
+
*/
|
|
33
|
+
import { argLooksLikeCredential, priorHashOf, } from './_credential-helpers.js';
|
|
34
|
+
function isWriteStorageCall(call, language) {
|
|
35
|
+
const method = call.method_name ?? '';
|
|
36
|
+
const receiver = call.receiver ?? '';
|
|
37
|
+
const recvLower = receiver.toLowerCase();
|
|
38
|
+
if (language === 'python') {
|
|
39
|
+
// open(...).write(pw) — receiver is a file handle; we approximate by
|
|
40
|
+
// method name `write` and check arg credential below.
|
|
41
|
+
if (method === 'write' || method === 'writelines') {
|
|
42
|
+
return { credPos: 0, api: `<file>.${method}` };
|
|
43
|
+
}
|
|
44
|
+
if ((recvLower === 'pickle' || recvLower === 'json' || recvLower === 'yaml') &&
|
|
45
|
+
(method === 'dump' || method === 'dumps')) {
|
|
46
|
+
return { credPos: 0, api: `${receiver}.${method}` };
|
|
47
|
+
}
|
|
48
|
+
if (recvLower === 'redis' && (method === 'set' || method === 'setex' || method === 'hset')) {
|
|
49
|
+
return { credPos: 1, api: `redis.${method}` };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
53
|
+
if ((recvLower === 'fs' || recvLower.endsWith('.fs')) &&
|
|
54
|
+
(method === 'writeFile' || method === 'writeFileSync' ||
|
|
55
|
+
method === 'appendFile' || method === 'appendFileSync')) {
|
|
56
|
+
return { credPos: 1, api: `fs.${method}` };
|
|
57
|
+
}
|
|
58
|
+
if ((recvLower === 'localstorage' || recvLower === 'sessionstorage') &&
|
|
59
|
+
method === 'setItem') {
|
|
60
|
+
return { credPos: 1, api: `${receiver}.setItem` };
|
|
61
|
+
}
|
|
62
|
+
if (recvLower === 'redis' && (method === 'set' || method === 'setex' || method === 'hset')) {
|
|
63
|
+
return { credPos: 1, api: `redis.${method}` };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (language === 'java') {
|
|
67
|
+
if ((receiver === 'Files' || receiver.endsWith('.Files')) &&
|
|
68
|
+
(method === 'write' || method === 'writeString')) {
|
|
69
|
+
return { credPos: 1, api: `Files.${method}` };
|
|
70
|
+
}
|
|
71
|
+
// FileWriter.write(pw) — instance call, single arg.
|
|
72
|
+
if (method === 'write') {
|
|
73
|
+
// Heuristic: receiver name contains "writer" / "file" / "stream".
|
|
74
|
+
const lc = (receiver ?? '').toLowerCase();
|
|
75
|
+
if (lc.includes('writer') || lc.includes('file') || lc.includes('stream')) {
|
|
76
|
+
return { credPos: 0, api: `${receiver}.write` };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (language === 'go') {
|
|
81
|
+
if (receiver === 'os' || receiver.endsWith('/os')) {
|
|
82
|
+
if (method === 'WriteFile')
|
|
83
|
+
return { credPos: 1, api: 'os.WriteFile' };
|
|
84
|
+
}
|
|
85
|
+
if (receiver === 'ioutil' || receiver.endsWith('/ioutil')) {
|
|
86
|
+
if (method === 'WriteFile')
|
|
87
|
+
return { credPos: 1, api: 'ioutil.WriteFile' };
|
|
88
|
+
}
|
|
89
|
+
if (method === 'WriteString' || method === 'Write') {
|
|
90
|
+
return { credPos: 0, api: `<file>.${method}` };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
export class PlaintextPasswordStoragePass {
|
|
96
|
+
name = 'plaintext-password-storage';
|
|
97
|
+
category = 'security';
|
|
98
|
+
run(ctx) {
|
|
99
|
+
const { graph, language } = ctx;
|
|
100
|
+
const file = graph.ir.meta.file;
|
|
101
|
+
const findings = [];
|
|
102
|
+
// Group calls by in_method for cheap prior-hash lookup.
|
|
103
|
+
const callsByScope = new Map();
|
|
104
|
+
for (const call of graph.ir.calls) {
|
|
105
|
+
const scope = call.in_method ?? '<top>';
|
|
106
|
+
const arr = callsByScope.get(scope) ?? [];
|
|
107
|
+
arr.push(call);
|
|
108
|
+
callsByScope.set(scope, arr);
|
|
109
|
+
}
|
|
110
|
+
for (const call of graph.ir.calls) {
|
|
111
|
+
const spec = isWriteStorageCall(call, language);
|
|
112
|
+
if (!spec)
|
|
113
|
+
continue;
|
|
114
|
+
const credArg = call.arguments.find((a) => a.position === spec.credPos);
|
|
115
|
+
if (!credArg)
|
|
116
|
+
continue;
|
|
117
|
+
if (!argLooksLikeCredential(credArg))
|
|
118
|
+
continue;
|
|
119
|
+
// Resolve the credential identifier name.
|
|
120
|
+
const identExpr = (credArg.expression ?? '').trim();
|
|
121
|
+
const head = identExpr.split(/[.\s(]/, 1)[0] ?? '';
|
|
122
|
+
const identifier = credArg.variable ?? head;
|
|
123
|
+
if (!identifier)
|
|
124
|
+
continue;
|
|
125
|
+
// Suppress if the identifier was hashed earlier in the same scope.
|
|
126
|
+
const scope = call.in_method ?? '<top>';
|
|
127
|
+
const scopeCalls = callsByScope.get(scope) ?? [];
|
|
128
|
+
const prior = scopeCalls.filter((c) => c.location.line < call.location.line);
|
|
129
|
+
if (priorHashOf(identifier, prior))
|
|
130
|
+
continue;
|
|
131
|
+
// Suppress if the credArg expression itself contains a hash call
|
|
132
|
+
// inline: `f.write(bcrypt.hashpw(pw))`.
|
|
133
|
+
if (/\b(?:hashpw|hash|sha\d+|md5|bcrypt|argon2|pbkdf2|digest)\b/i
|
|
134
|
+
.test(credArg.expression ?? '')) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const line = call.location.line;
|
|
138
|
+
findings.push({ line, language, api: spec.api, identifier });
|
|
139
|
+
ctx.addFinding({
|
|
140
|
+
id: `${this.name}-${file}-${line}`,
|
|
141
|
+
pass: this.name,
|
|
142
|
+
category: this.category,
|
|
143
|
+
rule_id: this.name,
|
|
144
|
+
cwe: 'CWE-256',
|
|
145
|
+
severity: 'high',
|
|
146
|
+
level: 'warning',
|
|
147
|
+
message: `Credential \`${identifier}\` written in plaintext via \`${spec.api}\`. ` +
|
|
148
|
+
'Passwords / secrets must be hashed (Argon2id, bcrypt) before storage.',
|
|
149
|
+
file,
|
|
150
|
+
line,
|
|
151
|
+
fix: 'Hash the credential with Argon2id / bcrypt before writing it to ' +
|
|
152
|
+
'disk, cookie, KV store, or database.',
|
|
153
|
+
evidence: { identifier, api: spec.api, language },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return { findings };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=plaintext-password-storage-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plaintext-password-storage-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/plaintext-password-storage-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: weak-password-encoding (CWE-261, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects use of an encoding (base64 / hex) on a credential-named identifier.
|
|
5
|
+
* Encoding is NOT encryption — base64-encoding a password before storing or
|
|
6
|
+
* transmitting it provides no confidentiality. Common anti-pattern.
|
|
7
|
+
*
|
|
8
|
+
* Detection per language:
|
|
9
|
+
* Python:
|
|
10
|
+
* - `base64.b64encode(password)` / `.urlsafe_b64encode(...)`
|
|
11
|
+
* - `binascii.hexlify(password)`
|
|
12
|
+
* JS/TS:
|
|
13
|
+
* - `Buffer.from(password).toString('base64')` / `.toString('hex')`
|
|
14
|
+
* - `btoa(password)`
|
|
15
|
+
* Java:
|
|
16
|
+
* - `Base64.getEncoder().encodeToString(passwordBytes)`
|
|
17
|
+
* - `Base64.getUrlEncoder().encodeToString(...)`
|
|
18
|
+
* - `Hex.encodeHexString(passwordBytes)`
|
|
19
|
+
* Go:
|
|
20
|
+
* - `base64.StdEncoding.EncodeToString(passwordBytes)`
|
|
21
|
+
* - `hex.EncodeToString(...)`
|
|
22
|
+
*
|
|
23
|
+
* FP-guard: skip when the encoded value is part of an HTTP Basic auth
|
|
24
|
+
* header construction (`"Basic " + base64(...)`) — that IS the spec.
|
|
25
|
+
*/
|
|
26
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
27
|
+
export interface WeakPasswordEncodingResult {
|
|
28
|
+
findings: Array<{
|
|
29
|
+
line: number;
|
|
30
|
+
language: string;
|
|
31
|
+
api: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export declare class WeakPasswordEncodingPass implements AnalysisPass<WeakPasswordEncodingResult> {
|
|
35
|
+
readonly name = "weak-password-encoding";
|
|
36
|
+
readonly category: "security";
|
|
37
|
+
run(ctx: PassContext): WeakPasswordEncodingResult;
|
|
38
|
+
private detect;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=weak-password-encoding-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weak-password-encoding-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-encoding-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAO9E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AAcD,qBAAa,wBAAyB,YAAW,YAAY,CAAC,0BAA0B,CAAC;IACvF,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,0BAA0B;IAoCjD,OAAO,CAAC,MAAM;CAsFf"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: weak-password-encoding (CWE-261, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects use of an encoding (base64 / hex) on a credential-named identifier.
|
|
5
|
+
* Encoding is NOT encryption — base64-encoding a password before storing or
|
|
6
|
+
* transmitting it provides no confidentiality. Common anti-pattern.
|
|
7
|
+
*
|
|
8
|
+
* Detection per language:
|
|
9
|
+
* Python:
|
|
10
|
+
* - `base64.b64encode(password)` / `.urlsafe_b64encode(...)`
|
|
11
|
+
* - `binascii.hexlify(password)`
|
|
12
|
+
* JS/TS:
|
|
13
|
+
* - `Buffer.from(password).toString('base64')` / `.toString('hex')`
|
|
14
|
+
* - `btoa(password)`
|
|
15
|
+
* Java:
|
|
16
|
+
* - `Base64.getEncoder().encodeToString(passwordBytes)`
|
|
17
|
+
* - `Base64.getUrlEncoder().encodeToString(...)`
|
|
18
|
+
* - `Hex.encodeHexString(passwordBytes)`
|
|
19
|
+
* Go:
|
|
20
|
+
* - `base64.StdEncoding.EncodeToString(passwordBytes)`
|
|
21
|
+
* - `hex.EncodeToString(...)`
|
|
22
|
+
*
|
|
23
|
+
* FP-guard: skip when the encoded value is part of an HTTP Basic auth
|
|
24
|
+
* header construction (`"Basic " + base64(...)`) — that IS the spec.
|
|
25
|
+
*/
|
|
26
|
+
import { argLooksLikeCredential, literalAt, } from './_credential-helpers.js';
|
|
27
|
+
function isBasicAuthContext(call, code) {
|
|
28
|
+
// Look at the source line for "Basic " literal nearby — heuristic
|
|
29
|
+
// for HTTP Basic auth construction where base64 is part of the spec.
|
|
30
|
+
const line = call.location.line;
|
|
31
|
+
if (line < 1)
|
|
32
|
+
return false;
|
|
33
|
+
const lines = code.split('\n');
|
|
34
|
+
const start = Math.max(0, line - 2);
|
|
35
|
+
const end = Math.min(lines.length, line + 1);
|
|
36
|
+
const window = lines.slice(start, end).join('\n');
|
|
37
|
+
return /["'`]Basic\s/i.test(window);
|
|
38
|
+
}
|
|
39
|
+
export class WeakPasswordEncodingPass {
|
|
40
|
+
name = 'weak-password-encoding';
|
|
41
|
+
category = 'security';
|
|
42
|
+
run(ctx) {
|
|
43
|
+
const { graph, language, code } = ctx;
|
|
44
|
+
const file = graph.ir.meta.file;
|
|
45
|
+
const findings = [];
|
|
46
|
+
for (const call of graph.ir.calls) {
|
|
47
|
+
const api = this.detect(call, language);
|
|
48
|
+
if (!api)
|
|
49
|
+
continue;
|
|
50
|
+
if (isBasicAuthContext(call, code))
|
|
51
|
+
continue;
|
|
52
|
+
const line = call.location.line;
|
|
53
|
+
findings.push({ line, language, api });
|
|
54
|
+
ctx.addFinding({
|
|
55
|
+
id: `${this.name}-${file}-${line}`,
|
|
56
|
+
pass: this.name,
|
|
57
|
+
category: this.category,
|
|
58
|
+
rule_id: this.name,
|
|
59
|
+
cwe: 'CWE-261',
|
|
60
|
+
severity: 'medium',
|
|
61
|
+
level: 'warning',
|
|
62
|
+
message: `Credential encoded via \`${api}\` — encoding is NOT encryption. ` +
|
|
63
|
+
'Base64/hex provide no confidentiality; anyone with the encoded value can decode it.',
|
|
64
|
+
file,
|
|
65
|
+
line,
|
|
66
|
+
fix: 'For storage, use a password hash (Argon2id / bcrypt). ' +
|
|
67
|
+
'For transport, use TLS. For symmetric secrecy, use authenticated encryption (AES-GCM).',
|
|
68
|
+
evidence: { api, language },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return { findings };
|
|
72
|
+
}
|
|
73
|
+
detect(call, language) {
|
|
74
|
+
const method = call.method_name ?? '';
|
|
75
|
+
const receiver = call.receiver ?? '';
|
|
76
|
+
const recvLower = receiver.toLowerCase();
|
|
77
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
78
|
+
if (language === 'python') {
|
|
79
|
+
// base64.b64encode(password)
|
|
80
|
+
if (recvLower === 'base64' &&
|
|
81
|
+
(method === 'b64encode' || method === 'urlsafe_b64encode' ||
|
|
82
|
+
method === 'standard_b64encode')) {
|
|
83
|
+
if (argLooksLikeCredential(arg0))
|
|
84
|
+
return `base64.${method}`;
|
|
85
|
+
}
|
|
86
|
+
// binascii.hexlify(password)
|
|
87
|
+
if (recvLower === 'binascii' && method === 'hexlify') {
|
|
88
|
+
if (argLooksLikeCredential(arg0))
|
|
89
|
+
return 'binascii.hexlify';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
93
|
+
// Buffer.from(password).toString('base64')
|
|
94
|
+
if (method === 'toString') {
|
|
95
|
+
const encoding = literalAt(call, 0);
|
|
96
|
+
if (encoding === 'base64' || encoding === 'hex' || encoding === 'base64url') {
|
|
97
|
+
// Receiver expression should look like `Buffer.from(<credential>)`.
|
|
98
|
+
// Conservative: check if receiver text contains "Buffer.from" and a
|
|
99
|
+
// credential keyword.
|
|
100
|
+
const recv = (receiver ?? '').toLowerCase();
|
|
101
|
+
if (recv.includes('buffer.from') &&
|
|
102
|
+
/(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|private[_-]?key|access[_-]?key|credential)/i
|
|
103
|
+
.test(receiver ?? '')) {
|
|
104
|
+
return `Buffer.from().toString('${encoding}')`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// btoa(password)
|
|
109
|
+
if (method === 'btoa' && receiver === '') {
|
|
110
|
+
if (argLooksLikeCredential(arg0))
|
|
111
|
+
return 'btoa';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (language === 'java') {
|
|
115
|
+
// Base64.getEncoder().encodeToString(passwordBytes)
|
|
116
|
+
// Or Base64.getUrlEncoder().encodeToString(...).
|
|
117
|
+
if (method === 'encodeToString') {
|
|
118
|
+
const recv = (receiver ?? '').toLowerCase();
|
|
119
|
+
if (recv.includes('encoder') || recv.includes('base64')) {
|
|
120
|
+
// arg[0] expr typically looks like `password.getBytes()`.
|
|
121
|
+
const expr = (arg0?.expression ?? '').trim();
|
|
122
|
+
const head = expr.split(/[.\s(]/, 1)[0] ?? '';
|
|
123
|
+
if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
|
|
124
|
+
return 'Base64.encodeToString';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Hex.encodeHexString(passwordBytes)
|
|
129
|
+
if (method === 'encodeHexString' &&
|
|
130
|
+
(receiver === 'Hex' || receiver.endsWith('.Hex'))) {
|
|
131
|
+
const expr = (arg0?.expression ?? '').trim();
|
|
132
|
+
const head = expr.split(/[.\s(]/, 1)[0] ?? '';
|
|
133
|
+
if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
|
|
134
|
+
return 'Hex.encodeHexString';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (language === 'go') {
|
|
139
|
+
// base64.StdEncoding.EncodeToString(passwordBytes)
|
|
140
|
+
if (method === 'EncodeToString') {
|
|
141
|
+
const recv = (receiver ?? '').toLowerCase();
|
|
142
|
+
if (recv.includes('base64') || recv.includes('hex') ||
|
|
143
|
+
recv.includes('encoding')) {
|
|
144
|
+
const expr = (arg0?.expression ?? '').trim();
|
|
145
|
+
// Strip `[]byte(...)` wrapper.
|
|
146
|
+
const inner = expr.replace(/^\[\]byte\s*\(\s*/, '').replace(/\s*\)\s*$/, '');
|
|
147
|
+
const head = inner.split(/[.\s(]/, 1)[0] ?? '';
|
|
148
|
+
if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
|
|
149
|
+
return recv.includes('hex') ? 'hex.EncodeToString' : 'base64.EncodeToString';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=weak-password-encoding-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weak-password-encoding-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-encoding-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: weak-password-hash (CWE-916, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects use of a fast / unsalted hash, or a KDF with insufficient
|
|
5
|
+
* computational cost, applied to a credential-named identifier.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from `weak-hash` (CWE-328):
|
|
8
|
+
* - `weak-hash` flags broken algorithms (MD2/MD4/MD5/SHA-1) at any call site.
|
|
9
|
+
* - `weak-password-hash` flags algorithm/cost choices that are SAFE for
|
|
10
|
+
* general digests but UNSAFE for password storage (e.g. plain SHA-256
|
|
11
|
+
* of a password, bcrypt cost < 10, PBKDF2 iterations < 100k).
|
|
12
|
+
*
|
|
13
|
+
* Detection per language:
|
|
14
|
+
* Python:
|
|
15
|
+
* - `hashlib.sha256(password)` / `.sha512(...)` / etc. where the
|
|
16
|
+
* argument is a credential-named identifier.
|
|
17
|
+
* - `bcrypt.hashpw(pw, bcrypt.gensalt(rounds=N))` where N < 10.
|
|
18
|
+
* - `PBKDF2HMAC(..., iterations=N).derive(pw)` where N < 100000.
|
|
19
|
+
* JS/TS:
|
|
20
|
+
* - `crypto.createHash('sha256').update(password).digest()`.
|
|
21
|
+
* - `bcrypt.hash(pw, N)` / `bcrypt.hashSync(pw, N)` where N < 10.
|
|
22
|
+
* - `crypto.pbkdf2Sync(pw, salt, N, ...)` where N < 100000.
|
|
23
|
+
* Java:
|
|
24
|
+
* - `MessageDigest.getInstance("SHA-256")` followed by `.update(pw)` —
|
|
25
|
+
* conservative: detect `MessageDigest.getInstance` + `.update(credIdent)`
|
|
26
|
+
* on any non-broken algorithm. (Broken algos already flagged by weak-hash.)
|
|
27
|
+
* - `PBEKeySpec(pw, salt, N, ...)` where N < 100000.
|
|
28
|
+
* Go:
|
|
29
|
+
* - `sha256.Sum256([]byte(password))`, `sha512.Sum512([]byte(password))`.
|
|
30
|
+
* - `bcrypt.GenerateFromPassword(pw, cost)` where cost < 10.
|
|
31
|
+
*
|
|
32
|
+
* Aligned with: OWASP ASVS 2.4.1, NIST SP 800-63B §5.1.1.2, gosec G401-style.
|
|
33
|
+
*/
|
|
34
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
35
|
+
export interface WeakPasswordHashResult {
|
|
36
|
+
findings: Array<{
|
|
37
|
+
line: number;
|
|
38
|
+
language: string;
|
|
39
|
+
kind: 'fast-unsalted-hash' | 'low-bcrypt-cost' | 'low-pbkdf2-iterations';
|
|
40
|
+
api: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
export declare class WeakPasswordHashPass implements AnalysisPass<WeakPasswordHashResult> {
|
|
44
|
+
readonly name = "weak-password-hash";
|
|
45
|
+
readonly category: "security";
|
|
46
|
+
run(ctx: PassContext): WeakPasswordHashResult;
|
|
47
|
+
private detect;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=weak-password-hash-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weak-password-hash-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-hash-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAmB9E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,oBAAoB,GAAG,iBAAiB,GAAG,uBAAuB,CAAC;QACzE,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AAsBD,qBAAa,oBAAqB,YAAW,YAAY,CAAC,sBAAsB,CAAC;IAC/E,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,sBAAsB;IA0C7C,OAAO,CAAC,MAAM;CAyIf"}
|