circle-ir 4.14.2 → 4.15.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/dist/analysis/const-table-lookup.d.ts +29 -0
- package/dist/analysis/const-table-lookup.d.ts.map +1 -0
- package/dist/analysis/const-table-lookup.js +145 -0
- package/dist/analysis/const-table-lookup.js.map +1 -0
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +15 -4
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/go-strings-replacer.d.ts +21 -0
- package/dist/analysis/go-strings-replacer.d.ts.map +1 -0
- package/dist/analysis/go-strings-replacer.js +145 -0
- package/dist/analysis/go-strings-replacer.js.map +1 -0
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +53 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/java-canonical-prefix.d.ts +42 -0
- package/dist/analysis/java-canonical-prefix.d.ts.map +1 -0
- package/dist/analysis/java-canonical-prefix.js +145 -0
- package/dist/analysis/java-canonical-prefix.js.map +1 -0
- package/dist/analysis/java-crlf-strip.d.ts +30 -0
- package/dist/analysis/java-crlf-strip.d.ts.map +1 -0
- package/dist/analysis/java-crlf-strip.js +195 -0
- package/dist/analysis/java-crlf-strip.js.map +1 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +2 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cleartext-credential-transport-pass.js +64 -12
- package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +43 -0
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +18 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts +28 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +20 -1
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +41 -2
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +842 -10
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts +8 -0
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js +162 -6
- package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +207 -1
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +257 -2
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/scalar-derivation.d.ts +25 -0
- package/dist/analysis/scalar-derivation.d.ts.map +1 -0
- package/dist/analysis/scalar-derivation.js +196 -0
- package/dist/analysis/scalar-derivation.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +167 -6
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +149 -19
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +7 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3140 -1186
- package/dist/core/circle-ir-core.cjs +485 -27
- package/dist/core/circle-ir-core.js +485 -27
- package/dist/core/extractors/dfg.js +240 -6
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/graph/code-graph.d.ts +9 -0
- package/dist/graph/code-graph.d.ts.map +1 -1
- package/dist/graph/code-graph.js +11 -0
- package/dist/graph/code-graph.js.map +1 -1
- package/dist/resolution/cross-file.d.ts +7 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +10 -1
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/package.json +3 -3
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scalar-derivation taint barrier (#568, parent #349).
|
|
3
|
+
*
|
|
4
|
+
* A local whose whole initializer measures or tests a value — its length, a
|
|
5
|
+
* match count, a boolean from a regex test or a containment check — holds an
|
|
6
|
+
* integer or a boolean. That value carries the magnitude of the input, not
|
|
7
|
+
* its content, so it cannot deliver a string payload to an injection sink.
|
|
8
|
+
*
|
|
9
|
+
* This is a type rule, not sanitizer credit for the call: a string-returning
|
|
10
|
+
* matcher (`re.FindString(text)`, `text.match(re)`) still propagates taint.
|
|
11
|
+
* Numeric parses (`strconv.Atoi`, `parseInt`) are not covered here; a parsed
|
|
12
|
+
* number is attacker-chosen and stays tainted for size-sensitive consumers.
|
|
13
|
+
*
|
|
14
|
+
* The test reads the definition's own line. An initializer that is not a
|
|
15
|
+
* single complete expression on that line is not credited.
|
|
16
|
+
*/
|
|
17
|
+
/** Go calls that return an int or a bool computed from their argument. */
|
|
18
|
+
const GO_SCALAR_FUNCS = new Set(['len', 'utf8.RuneCountInString', 'utf8.RuneCount']);
|
|
19
|
+
/** `strings.X` / `bytes.X` functions with an int or bool result. */
|
|
20
|
+
const GO_STRINGS_SCALAR = new Set([
|
|
21
|
+
'Contains', 'ContainsAny', 'ContainsRune', 'HasPrefix', 'HasSuffix', 'EqualFold',
|
|
22
|
+
'Count', 'Index', 'IndexAny', 'IndexByte', 'IndexRune', 'LastIndex', 'Compare',
|
|
23
|
+
]);
|
|
24
|
+
/** `*regexp.Regexp` methods with a bool result, on any receiver. */
|
|
25
|
+
const GO_REGEXP_BOOL = new Set(['MatchString', 'Match']);
|
|
26
|
+
/**
|
|
27
|
+
* JS/TS members that yield a number or a boolean. `search` is left out: on
|
|
28
|
+
* search-index objects (`idx.search(q)`) it returns result data, not a number.
|
|
29
|
+
*/
|
|
30
|
+
const JS_SCALAR_METHODS = new Set([
|
|
31
|
+
'test', 'includes', 'startsWith', 'endsWith', 'indexOf', 'lastIndexOf',
|
|
32
|
+
]);
|
|
33
|
+
function scalarLanguage(language) {
|
|
34
|
+
return language === 'go' || language === 'javascript' || language === 'typescript' ? language : null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Split `expr` at depth 0 into its trailing segment. Returns null when the
|
|
38
|
+
* expression is not one postfix chain (an operator, a comma, whitespace or an
|
|
39
|
+
* unbalanced bracket at depth 0).
|
|
40
|
+
*
|
|
41
|
+
* The result names the last member, whether the chain ends in a call of it,
|
|
42
|
+
* and `head`, the chain text before that call (or before `.member`). A JS
|
|
43
|
+
* regex literal may open the chain (`/^\d+$/.test(x)`).
|
|
44
|
+
*/
|
|
45
|
+
function lastChainSegment(expr) {
|
|
46
|
+
let depth = 0;
|
|
47
|
+
let lastDot = -1;
|
|
48
|
+
let lastGroupOpen = -1;
|
|
49
|
+
let endsWithGroup = false;
|
|
50
|
+
let start = 0;
|
|
51
|
+
if (expr.startsWith('/')) {
|
|
52
|
+
let inClass = false;
|
|
53
|
+
let j = 1;
|
|
54
|
+
for (; j < expr.length; j++) {
|
|
55
|
+
const ch = expr[j];
|
|
56
|
+
if (ch === '\\') {
|
|
57
|
+
j++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (ch === '[')
|
|
61
|
+
inClass = true;
|
|
62
|
+
else if (ch === ']')
|
|
63
|
+
inClass = false;
|
|
64
|
+
else if (ch === '/' && !inClass)
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (j >= expr.length)
|
|
68
|
+
return null;
|
|
69
|
+
start = j + 1;
|
|
70
|
+
}
|
|
71
|
+
for (let i = start; i < expr.length; i++) {
|
|
72
|
+
const c = expr[i];
|
|
73
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
74
|
+
if (c === '`' && depth === 0)
|
|
75
|
+
return null; // template text may interpolate
|
|
76
|
+
let j = i + 1;
|
|
77
|
+
for (; j < expr.length && expr[j] !== c; j++)
|
|
78
|
+
if (expr[j] === '\\')
|
|
79
|
+
j++;
|
|
80
|
+
if (j >= expr.length)
|
|
81
|
+
return null;
|
|
82
|
+
i = j;
|
|
83
|
+
endsWithGroup = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (c === '(' || c === '[' || c === '{') {
|
|
87
|
+
if (depth === 0)
|
|
88
|
+
lastGroupOpen = i;
|
|
89
|
+
depth++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (c === ')' || c === ']' || c === '}') {
|
|
93
|
+
depth--;
|
|
94
|
+
if (depth < 0)
|
|
95
|
+
return null;
|
|
96
|
+
if (depth === 0)
|
|
97
|
+
endsWithGroup = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (depth > 0)
|
|
101
|
+
continue;
|
|
102
|
+
if (c === '.') {
|
|
103
|
+
// `?.` optional chaining is still one chain
|
|
104
|
+
lastDot = i;
|
|
105
|
+
endsWithGroup = false;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (c === '?' && expr[i + 1] === '.')
|
|
109
|
+
continue;
|
|
110
|
+
if (/[\w$]/.test(c)) {
|
|
111
|
+
endsWithGroup = false;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (depth !== 0)
|
|
117
|
+
return null;
|
|
118
|
+
if (endsWithGroup) {
|
|
119
|
+
// Trailing `( … )` must be the call of the last member.
|
|
120
|
+
if (expr[lastGroupOpen] !== '(')
|
|
121
|
+
return null;
|
|
122
|
+
const callee = expr.slice(0, lastGroupOpen).replace(/\?$/, '');
|
|
123
|
+
const m = /(?:^|\.)([\w$]+)$/.exec(callee);
|
|
124
|
+
if (!m || lastDot > lastGroupOpen)
|
|
125
|
+
return null;
|
|
126
|
+
return { member: m[1], isCall: true, head: callee };
|
|
127
|
+
}
|
|
128
|
+
const m = /\.([\w$]+)$/.exec(expr);
|
|
129
|
+
if (!m)
|
|
130
|
+
return null;
|
|
131
|
+
return { member: m[1], isCall: false, head: expr.slice(0, expr.length - m[0].length) };
|
|
132
|
+
}
|
|
133
|
+
/** True when `rhs` is a whole-expression measure or test of a value. */
|
|
134
|
+
export function isScalarDerivation(rhs, language) {
|
|
135
|
+
const lang = scalarLanguage(language);
|
|
136
|
+
if (!lang)
|
|
137
|
+
return false;
|
|
138
|
+
let e = rhs.trim();
|
|
139
|
+
// A leading negation keeps a boolean a boolean.
|
|
140
|
+
while (e.startsWith('!'))
|
|
141
|
+
e = e.slice(1).trim();
|
|
142
|
+
if (e.length === 0)
|
|
143
|
+
return false;
|
|
144
|
+
const seg = lastChainSegment(e);
|
|
145
|
+
if (!seg)
|
|
146
|
+
return false;
|
|
147
|
+
if (lang === 'go') {
|
|
148
|
+
if (!seg.isCall)
|
|
149
|
+
return false;
|
|
150
|
+
if (GO_SCALAR_FUNCS.has(seg.head))
|
|
151
|
+
return true;
|
|
152
|
+
const pkg = /^(strings|bytes)\.(\w+)$/.exec(seg.head);
|
|
153
|
+
if (pkg)
|
|
154
|
+
return GO_STRINGS_SCALAR.has(pkg[2]);
|
|
155
|
+
return seg.head.includes('.') && GO_REGEXP_BOOL.has(seg.member);
|
|
156
|
+
}
|
|
157
|
+
if (!seg.isCall)
|
|
158
|
+
return seg.member === 'length';
|
|
159
|
+
return seg.head.includes('.') && JS_SCALAR_METHODS.has(seg.member);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The initializer of a single-variable local declared or assigned on `line`,
|
|
163
|
+
* or null when the line is not a complete single-target assignment of `name`.
|
|
164
|
+
*/
|
|
165
|
+
function initializerOf(line, name, lang) {
|
|
166
|
+
const esc = name.replace(/[$]/g, '\\$');
|
|
167
|
+
const code = line.replace(/\s*\/\/.*$/, '').trim().replace(/;$/, '').trim();
|
|
168
|
+
const re = lang === 'go'
|
|
169
|
+
? new RegExp(`^(?:var\\s+)?${esc}(?:\\s+\\w+)?\\s*(?::=|=)\\s*(.+)$`)
|
|
170
|
+
: new RegExp(`^(?:(?:const|let|var)\\s+)?${esc}(?:\\s*:\\s*(?:number|boolean))?\\s*=(?!=)\\s*(.+)$`);
|
|
171
|
+
const m = re.exec(code);
|
|
172
|
+
return m ? m[1] : null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* DFG def ids whose value is an integer or boolean derived from another value
|
|
176
|
+
* by a whole-expression measure or test. Taint does not flow into these defs.
|
|
177
|
+
*/
|
|
178
|
+
export function scalarDerivedDefIds(defs, code, language) {
|
|
179
|
+
const out = new Set();
|
|
180
|
+
const lang = scalarLanguage(language);
|
|
181
|
+
if (!lang)
|
|
182
|
+
return out;
|
|
183
|
+
const lines = code.split('\n');
|
|
184
|
+
for (const def of defs) {
|
|
185
|
+
if (def.kind !== 'local')
|
|
186
|
+
continue;
|
|
187
|
+
const text = lines[def.line - 1];
|
|
188
|
+
if (text === undefined)
|
|
189
|
+
continue;
|
|
190
|
+
const rhs = initializerOf(text, def.variable, lang);
|
|
191
|
+
if (rhs !== null && isScalarDerivation(rhs, lang))
|
|
192
|
+
out.add(def.id);
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=scalar-derivation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scalar-derivation.js","sourceRoot":"","sources":["../../src/analysis/scalar-derivation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taint-matcher.d.ts","sourceRoot":"","sources":["../../src/analysis/taint-matcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAgB,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAkB,KAAK,EAAwB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAElK,OAAO,KAAK,EAAE,WAAW,EAAiB,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AACpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"taint-matcher.d.ts","sourceRoot":"","sources":["../../src/analysis/taint-matcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAgB,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAkB,KAAK,EAAwB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAElK,OAAO,KAAK,EAAE,WAAW,EAAiB,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AACpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAyF7E,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EAAE,EACjB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,GAAE,WAAgC,EACxC,aAAa,CAAC,EAAE,qBAAqB,EACrC,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,IAAI,CAAC,EAAE,MAAM,GACZ,KAAK,CAWP;AA0JD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EAAE,EACtB,KAAK,EAAE,SAAS,EAAE,EAClB,IAAI,EAAE,MAAM,GACX,IAAI,CAYN;AAgoGD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,WAAW,GACnB,OAAO,CAET"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { DOM_ASSIGNMENT_SINK_METHODS, isProvablyLiteralExpression } from './literal-expression.js';
|
|
7
7
|
import { getDefaultConfig } from './config-loader.js';
|
|
8
|
+
import { javaReplaceCrLfCoverage } from './java-crlf-strip.js';
|
|
8
9
|
import { resolveIdentifierType, resolveCallReturnType, isBoundedClassType, } from './arg-type-resolver.js';
|
|
9
10
|
/**
|
|
10
11
|
* Python tainted access patterns (regex-based).
|
|
@@ -86,7 +87,7 @@ export function analyzeTaint(calls, types, config = getDefaultConfig(), typeHier
|
|
|
86
87
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
87
88
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
88
89
|
const gatedSinks = language === 'csharp' ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
89
|
-
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
90
|
+
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
90
91
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
@@ -1890,6 +1891,49 @@ function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
|
|
|
1890
1891
|
}
|
|
1891
1892
|
return true;
|
|
1892
1893
|
}
|
|
1894
|
+
/**
|
|
1895
|
+
* JDK types whose `getOutputStream()` is an outbound channel (a remote server,
|
|
1896
|
+
* a peer socket, a child process) rather than the servlet response.
|
|
1897
|
+
*/
|
|
1898
|
+
const JAVA_OUTBOUND_STREAM_OWNERS = 'HttpURLConnection|HttpsURLConnection|URLConnection|JarURLConnection|Socket|SSLSocket|Process';
|
|
1899
|
+
/**
|
|
1900
|
+
* #671 — true when a Java xss sink is `<base>.getOutputStream().<write>(...)`
|
|
1901
|
+
* and `<base>` is declared in this file as a `URLConnection` / `Socket` /
|
|
1902
|
+
* `Process` (or the chain is `….openConnection().getOutputStream()`). Bytes
|
|
1903
|
+
* written there leave for another host or process; nothing is rendered in the
|
|
1904
|
+
* requester's browser. Positive evidence only: an unresolved base keeps the
|
|
1905
|
+
* sink.
|
|
1906
|
+
*/
|
|
1907
|
+
function isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines) {
|
|
1908
|
+
if (language !== 'java' || pattern.type !== 'xss')
|
|
1909
|
+
return false;
|
|
1910
|
+
const receiver = (call.receiver ?? '').replace(/\s+/g, '');
|
|
1911
|
+
if (!receiver.endsWith('.getOutputStream()'))
|
|
1912
|
+
return false;
|
|
1913
|
+
if (/\.openConnection\(\)\)?\.getOutputStream\(\)$/.test(receiver))
|
|
1914
|
+
return true;
|
|
1915
|
+
const m = receiver.match(/^\(?([A-Za-z_]\w*)\)?\.getOutputStream\(\)$/);
|
|
1916
|
+
if (!m || !sourceLines)
|
|
1917
|
+
return false;
|
|
1918
|
+
const declRe = new RegExp(`\\b(?:[\\w.]+\\.)?(?:${JAVA_OUTBOUND_STREAM_OWNERS})\\s+${m[1]}\\s*[=;,)]`);
|
|
1919
|
+
// Nearest declaration above the call wins; a re-declaration with another
|
|
1920
|
+
// type (shadowing) stops the walk.
|
|
1921
|
+
// `var x = …` (inferred type) also shadows, and its type is unknown here.
|
|
1922
|
+
const anyDeclRe = new RegExp(`\\b(?:[A-Z][\\w.<>,\\[\\]]*|var)\\s+${m[1]}\\s*[=;,)]`);
|
|
1923
|
+
for (let i = call.location.line - 1; i >= 0; i--) {
|
|
1924
|
+
// Strip string literals, `//` comments and `/* … */` block comments so a
|
|
1925
|
+
// commented-out declaration cannot match either regex.
|
|
1926
|
+
const line = (sourceLines[i] ?? '')
|
|
1927
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, '""')
|
|
1928
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
1929
|
+
.replace(/\/\/.*$/, '');
|
|
1930
|
+
if (declRe.test(line))
|
|
1931
|
+
return true;
|
|
1932
|
+
if (anyDeclRe.test(line))
|
|
1933
|
+
return false;
|
|
1934
|
+
}
|
|
1935
|
+
return false;
|
|
1936
|
+
}
|
|
1893
1937
|
/**
|
|
1894
1938
|
* Gate for Go `json.Unmarshal(data, &dst)` / `json.NewDecoder(r).Decode(&dst)`
|
|
1895
1939
|
* deserialization (CWE-502) sinks. Returns `true` when the destination is a
|
|
@@ -2271,6 +2315,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
2271
2315
|
if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
|
|
2272
2316
|
continue;
|
|
2273
2317
|
}
|
|
2318
|
+
// #671 — `conn.getOutputStream().write(...)` on an outbound
|
|
2319
|
+
// URLConnection / Socket / Process writes to a remote peer or child
|
|
2320
|
+
// process, not to the HTTP response. Not xss.
|
|
2321
|
+
if (isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines)) {
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2274
2324
|
// #147 — Jinja2 safe render-context. Template("literal").render(**ctx)
|
|
2275
2325
|
// and render_template_string("literal", **ctx) and Template("literal")
|
|
2276
2326
|
// alone are safe: the template SOURCE is a constant string and Jinja2
|
|
@@ -2806,6 +2856,21 @@ function receiverMightBeClassImpl(receiver, className) {
|
|
|
2806
2856
|
if (receiver === className) {
|
|
2807
2857
|
return true;
|
|
2808
2858
|
}
|
|
2859
|
+
// Inline CommonJS require receiver: `require('child_process').exec(x)`.
|
|
2860
|
+
// The module is named at the call, so match it as if it were bound to a
|
|
2861
|
+
// name (`const cp = require('child_process'); cp.exec(x)`). (cognium-dev#682)
|
|
2862
|
+
const inlineRequire = receiver.match(/^require\(\s*['"`](?:node:)?([\w./-]+)['"`]\s*\)$/);
|
|
2863
|
+
if (inlineRequire && inlineRequire[1] === className) {
|
|
2864
|
+
return true;
|
|
2865
|
+
}
|
|
2866
|
+
// Chained JDK ClassLoader accessors: `Thread.currentThread()
|
|
2867
|
+
// .getContextClassLoader().loadClass(x)`, `getClass().getClassLoader()
|
|
2868
|
+
// .loadClass(x)`, `ClassLoader.getSystemClassLoader().loadClass(x)`.
|
|
2869
|
+
// These return a ClassLoader, so match the receiver as one. (cognium-dev#623)
|
|
2870
|
+
if (className === 'ClassLoader' &&
|
|
2871
|
+
/(?:^|\.)(?:getContextClassLoader|getClassLoader|getSystemClassLoader)\(\s*\)$/.test(receiver.replace(/\s+/g, ''))) {
|
|
2872
|
+
return true;
|
|
2873
|
+
}
|
|
2809
2874
|
// Constructor-call receiver: `ClassName(args)` — Python `Path(raw)`,
|
|
2810
2875
|
// JS `new URL(s)` (handled separately), etc. When the receiver is a
|
|
2811
2876
|
// direct function call whose function name equals the target class
|
|
@@ -3195,8 +3260,11 @@ export function isInDangerousPosition(argPosition, pattern) {
|
|
|
3195
3260
|
/**
|
|
3196
3261
|
* Find sanitizers in method calls and annotated parameters.
|
|
3197
3262
|
*/
|
|
3198
|
-
function findSanitizers(calls, types, patterns, sourceLines) {
|
|
3263
|
+
function findSanitizers(calls, types, patterns, sourceLines, language) {
|
|
3199
3264
|
const sanitizers = [];
|
|
3265
|
+
const javaReplace = (call) => language === 'java' && sourceLines && (call.method_name === 'replace' || call.method_name === 'replaceAll')
|
|
3266
|
+
? javaReplaceChainCredit(javaChainTextAt(sourceLines, call.location.line, call.location.column))
|
|
3267
|
+
: null;
|
|
3200
3268
|
// Build a set of method names with @sanitizer annotation (from Javadoc comments)
|
|
3201
3269
|
const sanitizerMethods = new Set();
|
|
3202
3270
|
for (const type of types) {
|
|
@@ -3240,10 +3308,16 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
3240
3308
|
continue;
|
|
3241
3309
|
const innerCall = inside[0];
|
|
3242
3310
|
let matched;
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3311
|
+
const replaceCredit = javaReplace(innerCall);
|
|
3312
|
+
if (replaceCredit) {
|
|
3313
|
+
matched = replaceCredit.length > 0 ? { method: innerCall.method_name, removes: replaceCredit } : undefined;
|
|
3314
|
+
}
|
|
3315
|
+
else {
|
|
3316
|
+
for (const pattern of patterns) {
|
|
3317
|
+
if (matchesSanitizerPattern(innerCall, pattern)) {
|
|
3318
|
+
matched = pattern;
|
|
3319
|
+
break;
|
|
3320
|
+
}
|
|
3247
3321
|
}
|
|
3248
3322
|
}
|
|
3249
3323
|
if (!matched || !matched.removes || matched.removes.length === 0)
|
|
@@ -3306,6 +3380,18 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
3306
3380
|
});
|
|
3307
3381
|
continue; // Skip pattern matching - already added as sanitizer
|
|
3308
3382
|
}
|
|
3383
|
+
const replaceCredit = javaReplace(call);
|
|
3384
|
+
if (replaceCredit) {
|
|
3385
|
+
if (replaceCredit.length > 0) {
|
|
3386
|
+
sanitizers.push({
|
|
3387
|
+
type: 'method_call',
|
|
3388
|
+
method: formatSanitizerMethod(call),
|
|
3389
|
+
line: call.location.line,
|
|
3390
|
+
sanitizes: replaceCredit,
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
continue;
|
|
3394
|
+
}
|
|
3309
3395
|
for (const pattern of patterns) {
|
|
3310
3396
|
if (matchesSanitizerPattern(call, pattern)) {
|
|
3311
3397
|
sanitizers.push({
|
|
@@ -3336,6 +3422,81 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
3336
3422
|
}
|
|
3337
3423
|
return sanitizers;
|
|
3338
3424
|
}
|
|
3425
|
+
const JAVA_LIT = String.raw `(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)')`;
|
|
3426
|
+
const JAVA_REPLACE_ARGS_RE = new RegExp(String.raw `\.(replace|replaceAll)\(\s*${JAVA_LIT}\s*,\s*${JAVA_LIT}\s*\)`, 'g');
|
|
3427
|
+
/**
|
|
3428
|
+
* The text of the call chain that starts at (line, column): the receiver
|
|
3429
|
+
* expression plus the `.method(...)` segments after it, up to the first
|
|
3430
|
+
* character that cannot continue the chain at nesting depth 0 (`;`, `,`, an
|
|
3431
|
+
* operator, an unmatched closing bracket). A call reports the start of the
|
|
3432
|
+
* whole chain it ends, so `a.replace("\n", "")` and `b.replace("\r", "")` on one
|
|
3433
|
+
* line start at different columns and never credit each other. The chain may
|
|
3434
|
+
* continue over following lines.
|
|
3435
|
+
*/
|
|
3436
|
+
function javaChainTextAt(sourceLines, line, column) {
|
|
3437
|
+
const text = [(sourceLines[line - 1] ?? '').slice(column), ...sourceLines.slice(line, line + 7)].join('\n');
|
|
3438
|
+
let depth = 0;
|
|
3439
|
+
let i = 0;
|
|
3440
|
+
for (; i < text.length; i++) {
|
|
3441
|
+
const c = text[i];
|
|
3442
|
+
if (c === '"' || c === "'") {
|
|
3443
|
+
for (i++; i < text.length && text[i] !== c; i++)
|
|
3444
|
+
if (text[i] === '\\')
|
|
3445
|
+
i++;
|
|
3446
|
+
continue;
|
|
3447
|
+
}
|
|
3448
|
+
if (c === '(' || c === '[') {
|
|
3449
|
+
depth++;
|
|
3450
|
+
continue;
|
|
3451
|
+
}
|
|
3452
|
+
if (c === ')' || c === ']') {
|
|
3453
|
+
if (depth === 0)
|
|
3454
|
+
break;
|
|
3455
|
+
depth--;
|
|
3456
|
+
continue;
|
|
3457
|
+
}
|
|
3458
|
+
if (depth === 0 && !/[\w$.\s]/.test(c))
|
|
3459
|
+
break;
|
|
3460
|
+
}
|
|
3461
|
+
return text.slice(0, i);
|
|
3462
|
+
}
|
|
3463
|
+
/**
|
|
3464
|
+
* Java `String.replace` / `replaceAll` credit, decided from the literal
|
|
3465
|
+
* arguments of the replace chain that contains the call instead of the method name
|
|
3466
|
+
* (cognium-dev#562 / #605 / #517). The class-less `replace` row credited any
|
|
3467
|
+
* replace as a log-injection sanitizer, so an LF-only strip silenced the
|
|
3468
|
+
* finding while `replaceAll("[\r\n]", "")` got no credit at all.
|
|
3469
|
+
* - `log_injection`: the chain strips both CR and LF (double-quoted String or single-quoted char literals).
|
|
3470
|
+
* - `xss`: the chain maps both `<` and `>` to an entity.
|
|
3471
|
+
* Returns the credited sink types (possibly empty).
|
|
3472
|
+
*/
|
|
3473
|
+
function javaReplaceChainCredit(chainText) {
|
|
3474
|
+
let cr = false;
|
|
3475
|
+
let lf = false;
|
|
3476
|
+
let lt = false;
|
|
3477
|
+
let gt = false;
|
|
3478
|
+
for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
|
|
3479
|
+
const kind = m[1];
|
|
3480
|
+
const rawFrom = m[2] ?? m[3] ?? '';
|
|
3481
|
+
const from = rawFrom.replace(/\\\\/g, '\\');
|
|
3482
|
+
const to = m[4] ?? m[5] ?? '';
|
|
3483
|
+
const cov = javaReplaceCrLfCoverage(kind, rawFrom);
|
|
3484
|
+
if (cov.cr)
|
|
3485
|
+
cr = true;
|
|
3486
|
+
if (cov.lf)
|
|
3487
|
+
lf = true;
|
|
3488
|
+
if (from === '<' && to.startsWith('&'))
|
|
3489
|
+
lt = true;
|
|
3490
|
+
if (from === '>' && to.startsWith('&'))
|
|
3491
|
+
gt = true;
|
|
3492
|
+
}
|
|
3493
|
+
const out = [];
|
|
3494
|
+
if (cr && lf)
|
|
3495
|
+
out.push('log_injection');
|
|
3496
|
+
if (lt && gt)
|
|
3497
|
+
out.push('xss');
|
|
3498
|
+
return out;
|
|
3499
|
+
}
|
|
3339
3500
|
/**
|
|
3340
3501
|
* Check if a call matches a sanitizer pattern.
|
|
3341
3502
|
*/
|