circle-ir 4.14.0 → 4.14.2
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/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +9 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +11 -4
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +30 -15
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +11 -2
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +52 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.js +24 -3
- package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +95 -38
- 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 +11 -3
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +14 -5
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts +7 -4
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +132 -3
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.js +1 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +339 -64
- package/dist/core/circle-ir-core.cjs +159 -15
- package/dist/core/circle-ir-core.js +159 -15
- package/dist/core/extractors/calls.js +19 -0
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/dfg.js +38 -8
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/languages/plugins/go.d.ts.map +1 -1
- package/dist/languages/plugins/go.js +19 -0
- package/dist/languages/plugins/go.js.map +1 -1
- package/package.json +1 -1
|
@@ -8080,6 +8080,17 @@ function extractGoCallInfo(node) {
|
|
|
8080
8080
|
if (operand.type === "identifier") {
|
|
8081
8081
|
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
8082
8082
|
receiver = resolved !== null ? resolved : opText;
|
|
8083
|
+
} else if (operand.type === "selector_expression" && methodName === "Get") {
|
|
8084
|
+
const fieldNode = operand.childForFieldName("field");
|
|
8085
|
+
const inner = operand.childForFieldName("operand");
|
|
8086
|
+
const fieldName = fieldNode ? getNodeText(fieldNode) : "";
|
|
8087
|
+
const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
|
|
8088
|
+
const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
|
|
8089
|
+
if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
|
|
8090
|
+
receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
|
|
8091
|
+
} else {
|
|
8092
|
+
receiver = opText;
|
|
8093
|
+
}
|
|
8083
8094
|
} else {
|
|
8084
8095
|
receiver = opText;
|
|
8085
8096
|
}
|
|
@@ -10242,6 +10253,36 @@ function extractFieldDefs(body2, startId) {
|
|
|
10242
10253
|
function extractJSParameterDefs(params, startId) {
|
|
10243
10254
|
const defs = [];
|
|
10244
10255
|
let id = startId;
|
|
10256
|
+
const addPatternDefs = (patternNode, line) => {
|
|
10257
|
+
if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
|
|
10258
|
+
defs.push({
|
|
10259
|
+
id: id++,
|
|
10260
|
+
variable: getNodeText(patternNode),
|
|
10261
|
+
line,
|
|
10262
|
+
kind: "param"
|
|
10263
|
+
});
|
|
10264
|
+
return;
|
|
10265
|
+
}
|
|
10266
|
+
if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
|
|
10267
|
+
const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
|
|
10268
|
+
if (!nameNode) return;
|
|
10269
|
+
addPatternDefs(nameNode, line);
|
|
10270
|
+
return;
|
|
10271
|
+
}
|
|
10272
|
+
if (patternNode.type === "assignment_pattern") {
|
|
10273
|
+
const leftNode = patternNode.childForFieldName("left");
|
|
10274
|
+
if (leftNode) addPatternDefs(leftNode, line);
|
|
10275
|
+
return;
|
|
10276
|
+
}
|
|
10277
|
+
if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
|
|
10278
|
+
const destructDefs = extractDestructuringDefs(patternNode, id);
|
|
10279
|
+
for (const def of destructDefs) {
|
|
10280
|
+
def.kind = "param";
|
|
10281
|
+
defs.push(def);
|
|
10282
|
+
id++;
|
|
10283
|
+
}
|
|
10284
|
+
}
|
|
10285
|
+
};
|
|
10245
10286
|
for (let i2 = 0; i2 < params.childCount; i2++) {
|
|
10246
10287
|
const param = params.child(i2);
|
|
10247
10288
|
if (!param) continue;
|
|
@@ -10289,14 +10330,7 @@ function extractJSParameterDefs(params, startId) {
|
|
|
10289
10330
|
}
|
|
10290
10331
|
} else if (param.type === "required_parameter" || param.type === "optional_parameter") {
|
|
10291
10332
|
const patternNode = param.childForFieldName("pattern");
|
|
10292
|
-
if (patternNode
|
|
10293
|
-
defs.push({
|
|
10294
|
-
id: id++,
|
|
10295
|
-
variable: getNodeText(patternNode),
|
|
10296
|
-
line: param.startPosition.row + 1,
|
|
10297
|
-
kind: "param"
|
|
10298
|
-
});
|
|
10299
|
-
}
|
|
10333
|
+
if (patternNode) addPatternDefs(patternNode, param.startPosition.row + 1);
|
|
10300
10334
|
}
|
|
10301
10335
|
}
|
|
10302
10336
|
return defs;
|
|
@@ -12425,8 +12459,15 @@ var DEFAULT_SINKS = [
|
|
|
12425
12459
|
{ method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12426
12460
|
{ method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12427
12461
|
{ method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12428
|
-
// Wildfly/Undertow servlet resource manager
|
|
12429
|
-
|
|
12462
|
+
// Wildfly/Undertow servlet resource manager.
|
|
12463
|
+
// ResourceManager / PathResourceManager / FileResourceManager /
|
|
12464
|
+
// ClassPathResourceManager are the interface and implementations WildFly
|
|
12465
|
+
// calls through (cognium-dev#622, CVE-2018-1047). Class-qualified only.
|
|
12466
|
+
{ method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12467
|
+
{ method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12468
|
+
{ method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12469
|
+
{ method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12470
|
+
{ method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12430
12471
|
// Yamcs file system bucket
|
|
12431
12472
|
{ method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12432
12473
|
// RocketMQ validators
|
|
@@ -15066,7 +15107,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
15066
15107
|
if (skipMethods.includes(method.name)) continue;
|
|
15067
15108
|
for (const param of method.parameters) {
|
|
15068
15109
|
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
15069
|
-
const
|
|
15110
|
+
const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
|
|
15111
|
+
const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
15070
15112
|
if (isTaintable) {
|
|
15071
15113
|
const paramLine = param.line ?? method.start_line;
|
|
15072
15114
|
sources.push({
|
|
@@ -15296,7 +15338,8 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
15296
15338
|
}
|
|
15297
15339
|
function isParameterizedQueryCall(call, pattern) {
|
|
15298
15340
|
if (pattern.type !== "sql_injection") return false;
|
|
15299
|
-
const
|
|
15341
|
+
const positions = pattern.arg_positions ?? [];
|
|
15342
|
+
const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
|
|
15300
15343
|
const queryArg = call.arguments.find((a) => a.position === queryPos);
|
|
15301
15344
|
if (queryArg) {
|
|
15302
15345
|
const queryText = queryArg.literal ?? queryArg.expression ?? "";
|
|
@@ -17220,6 +17263,9 @@ function buildSanitizersByLine(sanitizers) {
|
|
|
17220
17263
|
}
|
|
17221
17264
|
return out2;
|
|
17222
17265
|
}
|
|
17266
|
+
function concatOutsideStrings(expression) {
|
|
17267
|
+
return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
|
|
17268
|
+
}
|
|
17223
17269
|
function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
|
|
17224
17270
|
let graph;
|
|
17225
17271
|
let sources;
|
|
@@ -17251,6 +17297,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17251
17297
|
}
|
|
17252
17298
|
const taintedVars = [];
|
|
17253
17299
|
const flows = [];
|
|
17300
|
+
const javaConcat = graph.ir.meta.language === "java";
|
|
17254
17301
|
const reachableSinks = /* @__PURE__ */ new Map();
|
|
17255
17302
|
const defsByLine = graph.defsByLine;
|
|
17256
17303
|
const usesByLine = graph.usesByLine;
|
|
@@ -17287,23 +17334,40 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17287
17334
|
continue;
|
|
17288
17335
|
}
|
|
17289
17336
|
}
|
|
17290
|
-
const
|
|
17337
|
+
const expr = typeof arg.expression === "string" ? arg.expression : "";
|
|
17338
|
+
const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
|
|
17339
|
+
const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
|
|
17291
17340
|
{
|
|
17292
17341
|
for (const use of candidateUses) {
|
|
17293
17342
|
if (use.def_id !== null) {
|
|
17294
17343
|
if (allTaintedDefIds.has(use.def_id)) {
|
|
17295
|
-
if (!arg.variable) {
|
|
17344
|
+
if (!arg.variable || compoundConcat) {
|
|
17296
17345
|
if (typeof arg.expression !== "string" || arg.expression.length === 0) continue;
|
|
17297
17346
|
const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
|
|
17298
17347
|
if (!re.test(arg.expression)) continue;
|
|
17299
17348
|
}
|
|
17300
17349
|
const taintInfo = taintByDefId.get(use.def_id);
|
|
17301
17350
|
if (taintInfo) {
|
|
17351
|
+
let sanMap = sanitizersByLine;
|
|
17352
|
+
if (identifierOutsideSanitizerCalls(
|
|
17353
|
+
use.variable,
|
|
17354
|
+
callsAtSink,
|
|
17355
|
+
sanitizersByLine.get(sink.line) ?? [],
|
|
17356
|
+
sink.type
|
|
17357
|
+
)) {
|
|
17358
|
+
const stripped = new Map(sanitizersByLine);
|
|
17359
|
+
const kept = (sanitizersByLine.get(sink.line) ?? []).filter(
|
|
17360
|
+
(s) => !sanitizerCoversSink(s, sink.type)
|
|
17361
|
+
);
|
|
17362
|
+
if (kept.length === 0) stripped.delete(sink.line);
|
|
17363
|
+
else stripped.set(sink.line, kept);
|
|
17364
|
+
sanMap = stripped;
|
|
17365
|
+
}
|
|
17302
17366
|
const isSanitized = checkSanitized(
|
|
17303
17367
|
taintInfo.line,
|
|
17304
17368
|
sink.line,
|
|
17305
17369
|
sink.type,
|
|
17306
|
-
|
|
17370
|
+
sanMap,
|
|
17307
17371
|
{
|
|
17308
17372
|
startDefId: use.def_id,
|
|
17309
17373
|
chainsByToDef: graph.chainsByToDef,
|
|
@@ -17457,6 +17521,86 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
|
|
|
17457
17521
|
"crlf",
|
|
17458
17522
|
"mass_assignment"
|
|
17459
17523
|
]);
|
|
17524
|
+
function readBalancedArg(text, openParen) {
|
|
17525
|
+
let depth = 0;
|
|
17526
|
+
let quote = null;
|
|
17527
|
+
let escaped = false;
|
|
17528
|
+
for (let i2 = openParen; i2 < text.length; i2++) {
|
|
17529
|
+
const c = text[i2];
|
|
17530
|
+
if (quote) {
|
|
17531
|
+
if (escaped) {
|
|
17532
|
+
escaped = false;
|
|
17533
|
+
continue;
|
|
17534
|
+
}
|
|
17535
|
+
if (c === "\\") {
|
|
17536
|
+
escaped = true;
|
|
17537
|
+
continue;
|
|
17538
|
+
}
|
|
17539
|
+
if (c === quote) quote = null;
|
|
17540
|
+
continue;
|
|
17541
|
+
}
|
|
17542
|
+
if (c === '"' || c === "'") {
|
|
17543
|
+
quote = c;
|
|
17544
|
+
continue;
|
|
17545
|
+
}
|
|
17546
|
+
if (c === "(") depth++;
|
|
17547
|
+
else if (c === ")") {
|
|
17548
|
+
depth--;
|
|
17549
|
+
if (depth === 0) return text.slice(openParen + 1, i2);
|
|
17550
|
+
}
|
|
17551
|
+
}
|
|
17552
|
+
return null;
|
|
17553
|
+
}
|
|
17554
|
+
function sanitizerCallRefs(sanitizers, sinkType) {
|
|
17555
|
+
const refs = [];
|
|
17556
|
+
for (const san of sanitizers) {
|
|
17557
|
+
if (!sanitizerCoversSink(san, sinkType)) continue;
|
|
17558
|
+
const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
|
|
17559
|
+
if (!m?.[2]) continue;
|
|
17560
|
+
refs.push({ receiver: m[1], methodName: m[2] });
|
|
17561
|
+
}
|
|
17562
|
+
return refs;
|
|
17563
|
+
}
|
|
17564
|
+
function maskSanitizerCalls(expr, refs) {
|
|
17565
|
+
let out2 = expr;
|
|
17566
|
+
for (const ref of refs) {
|
|
17567
|
+
const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
|
|
17568
|
+
for (const needle of needles) {
|
|
17569
|
+
const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
|
|
17570
|
+
for (let guard = 0; guard < 8; guard++) {
|
|
17571
|
+
const m = re.exec(out2);
|
|
17572
|
+
if (!m) break;
|
|
17573
|
+
const open = m.index + m[0].length - 1;
|
|
17574
|
+
const arg = readBalancedArg(out2, open);
|
|
17575
|
+
if (arg === null) break;
|
|
17576
|
+
out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
|
|
17577
|
+
}
|
|
17578
|
+
}
|
|
17579
|
+
}
|
|
17580
|
+
return out2;
|
|
17581
|
+
}
|
|
17582
|
+
function blankStringLiterals(expr) {
|
|
17583
|
+
return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
17584
|
+
}
|
|
17585
|
+
function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
|
|
17586
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(variable)) return false;
|
|
17587
|
+
const refs = sanitizerCallRefs(sanitizers, sinkType);
|
|
17588
|
+
if (refs.length === 0) return false;
|
|
17589
|
+
const re = new RegExp(
|
|
17590
|
+
`(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`
|
|
17591
|
+
);
|
|
17592
|
+
for (const call of calls) {
|
|
17593
|
+
if (refs.some((r) => r.methodName === call.method_name)) continue;
|
|
17594
|
+
for (const arg of call.arguments) {
|
|
17595
|
+
const expr = arg.expression ?? "";
|
|
17596
|
+
if (!expr) continue;
|
|
17597
|
+
const masked = maskSanitizerCalls(expr, refs);
|
|
17598
|
+
if (masked === expr) continue;
|
|
17599
|
+
if (re.test(blankStringLiterals(masked))) return true;
|
|
17600
|
+
}
|
|
17601
|
+
}
|
|
17602
|
+
return false;
|
|
17603
|
+
}
|
|
17460
17604
|
function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
17461
17605
|
const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
|
|
17462
17606
|
const sanitizersAtTarget = sanitizersByLine.get(toLine);
|
|
@@ -8014,6 +8014,17 @@ function extractGoCallInfo(node) {
|
|
|
8014
8014
|
if (operand.type === "identifier") {
|
|
8015
8015
|
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
8016
8016
|
receiver = resolved !== null ? resolved : opText;
|
|
8017
|
+
} else if (operand.type === "selector_expression" && methodName === "Get") {
|
|
8018
|
+
const fieldNode = operand.childForFieldName("field");
|
|
8019
|
+
const inner = operand.childForFieldName("operand");
|
|
8020
|
+
const fieldName = fieldNode ? getNodeText(fieldNode) : "";
|
|
8021
|
+
const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
|
|
8022
|
+
const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
|
|
8023
|
+
if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
|
|
8024
|
+
receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
|
|
8025
|
+
} else {
|
|
8026
|
+
receiver = opText;
|
|
8027
|
+
}
|
|
8017
8028
|
} else {
|
|
8018
8029
|
receiver = opText;
|
|
8019
8030
|
}
|
|
@@ -10176,6 +10187,36 @@ function extractFieldDefs(body2, startId) {
|
|
|
10176
10187
|
function extractJSParameterDefs(params, startId) {
|
|
10177
10188
|
const defs = [];
|
|
10178
10189
|
let id = startId;
|
|
10190
|
+
const addPatternDefs = (patternNode, line) => {
|
|
10191
|
+
if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
|
|
10192
|
+
defs.push({
|
|
10193
|
+
id: id++,
|
|
10194
|
+
variable: getNodeText(patternNode),
|
|
10195
|
+
line,
|
|
10196
|
+
kind: "param"
|
|
10197
|
+
});
|
|
10198
|
+
return;
|
|
10199
|
+
}
|
|
10200
|
+
if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
|
|
10201
|
+
const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
|
|
10202
|
+
if (!nameNode) return;
|
|
10203
|
+
addPatternDefs(nameNode, line);
|
|
10204
|
+
return;
|
|
10205
|
+
}
|
|
10206
|
+
if (patternNode.type === "assignment_pattern") {
|
|
10207
|
+
const leftNode = patternNode.childForFieldName("left");
|
|
10208
|
+
if (leftNode) addPatternDefs(leftNode, line);
|
|
10209
|
+
return;
|
|
10210
|
+
}
|
|
10211
|
+
if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
|
|
10212
|
+
const destructDefs = extractDestructuringDefs(patternNode, id);
|
|
10213
|
+
for (const def of destructDefs) {
|
|
10214
|
+
def.kind = "param";
|
|
10215
|
+
defs.push(def);
|
|
10216
|
+
id++;
|
|
10217
|
+
}
|
|
10218
|
+
}
|
|
10219
|
+
};
|
|
10179
10220
|
for (let i2 = 0; i2 < params.childCount; i2++) {
|
|
10180
10221
|
const param = params.child(i2);
|
|
10181
10222
|
if (!param) continue;
|
|
@@ -10223,14 +10264,7 @@ function extractJSParameterDefs(params, startId) {
|
|
|
10223
10264
|
}
|
|
10224
10265
|
} else if (param.type === "required_parameter" || param.type === "optional_parameter") {
|
|
10225
10266
|
const patternNode = param.childForFieldName("pattern");
|
|
10226
|
-
if (patternNode
|
|
10227
|
-
defs.push({
|
|
10228
|
-
id: id++,
|
|
10229
|
-
variable: getNodeText(patternNode),
|
|
10230
|
-
line: param.startPosition.row + 1,
|
|
10231
|
-
kind: "param"
|
|
10232
|
-
});
|
|
10233
|
-
}
|
|
10267
|
+
if (patternNode) addPatternDefs(patternNode, param.startPosition.row + 1);
|
|
10234
10268
|
}
|
|
10235
10269
|
}
|
|
10236
10270
|
return defs;
|
|
@@ -12359,8 +12393,15 @@ var DEFAULT_SINKS = [
|
|
|
12359
12393
|
{ method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12360
12394
|
{ method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12361
12395
|
{ method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12362
|
-
// Wildfly/Undertow servlet resource manager
|
|
12363
|
-
|
|
12396
|
+
// Wildfly/Undertow servlet resource manager.
|
|
12397
|
+
// ResourceManager / PathResourceManager / FileResourceManager /
|
|
12398
|
+
// ClassPathResourceManager are the interface and implementations WildFly
|
|
12399
|
+
// calls through (cognium-dev#622, CVE-2018-1047). Class-qualified only.
|
|
12400
|
+
{ method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12401
|
+
{ method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12402
|
+
{ method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12403
|
+
{ method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12404
|
+
{ method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12364
12405
|
// Yamcs file system bucket
|
|
12365
12406
|
{ method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12366
12407
|
// RocketMQ validators
|
|
@@ -15000,7 +15041,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
15000
15041
|
if (skipMethods.includes(method.name)) continue;
|
|
15001
15042
|
for (const param of method.parameters) {
|
|
15002
15043
|
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
15003
|
-
const
|
|
15044
|
+
const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
|
|
15045
|
+
const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
15004
15046
|
if (isTaintable) {
|
|
15005
15047
|
const paramLine = param.line ?? method.start_line;
|
|
15006
15048
|
sources.push({
|
|
@@ -15230,7 +15272,8 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
15230
15272
|
}
|
|
15231
15273
|
function isParameterizedQueryCall(call, pattern) {
|
|
15232
15274
|
if (pattern.type !== "sql_injection") return false;
|
|
15233
|
-
const
|
|
15275
|
+
const positions = pattern.arg_positions ?? [];
|
|
15276
|
+
const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
|
|
15234
15277
|
const queryArg = call.arguments.find((a) => a.position === queryPos);
|
|
15235
15278
|
if (queryArg) {
|
|
15236
15279
|
const queryText = queryArg.literal ?? queryArg.expression ?? "";
|
|
@@ -17154,6 +17197,9 @@ function buildSanitizersByLine(sanitizers) {
|
|
|
17154
17197
|
}
|
|
17155
17198
|
return out2;
|
|
17156
17199
|
}
|
|
17200
|
+
function concatOutsideStrings(expression) {
|
|
17201
|
+
return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
|
|
17202
|
+
}
|
|
17157
17203
|
function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
|
|
17158
17204
|
let graph;
|
|
17159
17205
|
let sources;
|
|
@@ -17185,6 +17231,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17185
17231
|
}
|
|
17186
17232
|
const taintedVars = [];
|
|
17187
17233
|
const flows = [];
|
|
17234
|
+
const javaConcat = graph.ir.meta.language === "java";
|
|
17188
17235
|
const reachableSinks = /* @__PURE__ */ new Map();
|
|
17189
17236
|
const defsByLine = graph.defsByLine;
|
|
17190
17237
|
const usesByLine = graph.usesByLine;
|
|
@@ -17221,23 +17268,40 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17221
17268
|
continue;
|
|
17222
17269
|
}
|
|
17223
17270
|
}
|
|
17224
|
-
const
|
|
17271
|
+
const expr = typeof arg.expression === "string" ? arg.expression : "";
|
|
17272
|
+
const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
|
|
17273
|
+
const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
|
|
17225
17274
|
{
|
|
17226
17275
|
for (const use of candidateUses) {
|
|
17227
17276
|
if (use.def_id !== null) {
|
|
17228
17277
|
if (allTaintedDefIds.has(use.def_id)) {
|
|
17229
|
-
if (!arg.variable) {
|
|
17278
|
+
if (!arg.variable || compoundConcat) {
|
|
17230
17279
|
if (typeof arg.expression !== "string" || arg.expression.length === 0) continue;
|
|
17231
17280
|
const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
|
|
17232
17281
|
if (!re.test(arg.expression)) continue;
|
|
17233
17282
|
}
|
|
17234
17283
|
const taintInfo = taintByDefId.get(use.def_id);
|
|
17235
17284
|
if (taintInfo) {
|
|
17285
|
+
let sanMap = sanitizersByLine;
|
|
17286
|
+
if (identifierOutsideSanitizerCalls(
|
|
17287
|
+
use.variable,
|
|
17288
|
+
callsAtSink,
|
|
17289
|
+
sanitizersByLine.get(sink.line) ?? [],
|
|
17290
|
+
sink.type
|
|
17291
|
+
)) {
|
|
17292
|
+
const stripped = new Map(sanitizersByLine);
|
|
17293
|
+
const kept = (sanitizersByLine.get(sink.line) ?? []).filter(
|
|
17294
|
+
(s) => !sanitizerCoversSink(s, sink.type)
|
|
17295
|
+
);
|
|
17296
|
+
if (kept.length === 0) stripped.delete(sink.line);
|
|
17297
|
+
else stripped.set(sink.line, kept);
|
|
17298
|
+
sanMap = stripped;
|
|
17299
|
+
}
|
|
17236
17300
|
const isSanitized = checkSanitized(
|
|
17237
17301
|
taintInfo.line,
|
|
17238
17302
|
sink.line,
|
|
17239
17303
|
sink.type,
|
|
17240
|
-
|
|
17304
|
+
sanMap,
|
|
17241
17305
|
{
|
|
17242
17306
|
startDefId: use.def_id,
|
|
17243
17307
|
chainsByToDef: graph.chainsByToDef,
|
|
@@ -17391,6 +17455,86 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
|
|
|
17391
17455
|
"crlf",
|
|
17392
17456
|
"mass_assignment"
|
|
17393
17457
|
]);
|
|
17458
|
+
function readBalancedArg(text, openParen) {
|
|
17459
|
+
let depth = 0;
|
|
17460
|
+
let quote = null;
|
|
17461
|
+
let escaped = false;
|
|
17462
|
+
for (let i2 = openParen; i2 < text.length; i2++) {
|
|
17463
|
+
const c = text[i2];
|
|
17464
|
+
if (quote) {
|
|
17465
|
+
if (escaped) {
|
|
17466
|
+
escaped = false;
|
|
17467
|
+
continue;
|
|
17468
|
+
}
|
|
17469
|
+
if (c === "\\") {
|
|
17470
|
+
escaped = true;
|
|
17471
|
+
continue;
|
|
17472
|
+
}
|
|
17473
|
+
if (c === quote) quote = null;
|
|
17474
|
+
continue;
|
|
17475
|
+
}
|
|
17476
|
+
if (c === '"' || c === "'") {
|
|
17477
|
+
quote = c;
|
|
17478
|
+
continue;
|
|
17479
|
+
}
|
|
17480
|
+
if (c === "(") depth++;
|
|
17481
|
+
else if (c === ")") {
|
|
17482
|
+
depth--;
|
|
17483
|
+
if (depth === 0) return text.slice(openParen + 1, i2);
|
|
17484
|
+
}
|
|
17485
|
+
}
|
|
17486
|
+
return null;
|
|
17487
|
+
}
|
|
17488
|
+
function sanitizerCallRefs(sanitizers, sinkType) {
|
|
17489
|
+
const refs = [];
|
|
17490
|
+
for (const san of sanitizers) {
|
|
17491
|
+
if (!sanitizerCoversSink(san, sinkType)) continue;
|
|
17492
|
+
const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
|
|
17493
|
+
if (!m?.[2]) continue;
|
|
17494
|
+
refs.push({ receiver: m[1], methodName: m[2] });
|
|
17495
|
+
}
|
|
17496
|
+
return refs;
|
|
17497
|
+
}
|
|
17498
|
+
function maskSanitizerCalls(expr, refs) {
|
|
17499
|
+
let out2 = expr;
|
|
17500
|
+
for (const ref of refs) {
|
|
17501
|
+
const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
|
|
17502
|
+
for (const needle of needles) {
|
|
17503
|
+
const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
|
|
17504
|
+
for (let guard = 0; guard < 8; guard++) {
|
|
17505
|
+
const m = re.exec(out2);
|
|
17506
|
+
if (!m) break;
|
|
17507
|
+
const open = m.index + m[0].length - 1;
|
|
17508
|
+
const arg = readBalancedArg(out2, open);
|
|
17509
|
+
if (arg === null) break;
|
|
17510
|
+
out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
|
|
17511
|
+
}
|
|
17512
|
+
}
|
|
17513
|
+
}
|
|
17514
|
+
return out2;
|
|
17515
|
+
}
|
|
17516
|
+
function blankStringLiterals(expr) {
|
|
17517
|
+
return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
17518
|
+
}
|
|
17519
|
+
function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
|
|
17520
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(variable)) return false;
|
|
17521
|
+
const refs = sanitizerCallRefs(sanitizers, sinkType);
|
|
17522
|
+
if (refs.length === 0) return false;
|
|
17523
|
+
const re = new RegExp(
|
|
17524
|
+
`(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`
|
|
17525
|
+
);
|
|
17526
|
+
for (const call of calls) {
|
|
17527
|
+
if (refs.some((r) => r.methodName === call.method_name)) continue;
|
|
17528
|
+
for (const arg of call.arguments) {
|
|
17529
|
+
const expr = arg.expression ?? "";
|
|
17530
|
+
if (!expr) continue;
|
|
17531
|
+
const masked = maskSanitizerCalls(expr, refs);
|
|
17532
|
+
if (masked === expr) continue;
|
|
17533
|
+
if (re.test(blankStringLiterals(masked))) return true;
|
|
17534
|
+
}
|
|
17535
|
+
}
|
|
17536
|
+
return false;
|
|
17537
|
+
}
|
|
17394
17538
|
function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
17395
17539
|
const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
|
|
17396
17540
|
const sanitizersAtTarget = sanitizersByLine.get(toLine);
|
|
@@ -2615,6 +2615,25 @@ function extractGoCallInfo(node) {
|
|
|
2615
2615
|
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
2616
2616
|
receiver = resolved !== null ? resolved : opText;
|
|
2617
2617
|
}
|
|
2618
|
+
else if (operand.type === 'selector_expression' && methodName === 'Get') {
|
|
2619
|
+
// cognium-dev #492 — `r.PostForm.Get` / `r.Form.Get` on a declared
|
|
2620
|
+
// `*http.Request`. The operand is `url.Values`, so a type-resolved
|
|
2621
|
+
// receiver would be `Values` and would also match a plain
|
|
2622
|
+
// `url.Values.Get`. Rewrite only this shape to a synthetic class
|
|
2623
|
+
// the Go plugin registers. Index form `r.Form[k][0]` is not a call;
|
|
2624
|
+
// LanguageSourcesPass covers it.
|
|
2625
|
+
const fieldNode = operand.childForFieldName('field');
|
|
2626
|
+
const inner = operand.childForFieldName('operand');
|
|
2627
|
+
const fieldName = fieldNode ? getNodeText(fieldNode) : '';
|
|
2628
|
+
const innerName = inner && inner.type === 'identifier' ? getNodeText(inner) : null;
|
|
2629
|
+
const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
|
|
2630
|
+
if (innerType === 'Request' && (fieldName === 'PostForm' || fieldName === 'Form')) {
|
|
2631
|
+
receiver = fieldName === 'PostForm' ? 'RequestPostForm' : 'RequestForm';
|
|
2632
|
+
}
|
|
2633
|
+
else {
|
|
2634
|
+
receiver = opText;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2618
2637
|
else {
|
|
2619
2638
|
receiver = opText;
|
|
2620
2639
|
}
|