circle-ir 4.9.28 → 4.10.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/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +63 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +54 -7
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +117 -12
- 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 +41 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +12 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +1 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +260 -28
- package/dist/core/circle-ir-core.cjs +179 -12
- package/dist/core/circle-ir-core.js +179 -12
- package/dist/core/extractors/calls.js +29 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/types.d.ts +28 -1
- package/dist/core/extractors/types.d.ts.map +1 -1
- package/dist/core/extractors/types.js +136 -10
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +6 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/navigation-index.d.ts +208 -0
- package/dist/navigation/navigation-index.d.ts.map +1 -0
- package/dist/navigation/navigation-index.js +968 -0
- package/dist/navigation/navigation-index.js.map +1 -0
- package/dist/navigation/types.d.ts +160 -0
- package/dist/navigation/types.d.ts.map +1 -0
- package/dist/navigation/types.js +17 -0
- package/dist/navigation/types.js.map +1 -0
- package/dist/types/config.d.ts +14 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -4458,7 +4458,7 @@ function detectLanguage(tree) {
|
|
|
4458
4458
|
if (pythonScore > jsScore && pythonScore > javaScore) return "python";
|
|
4459
4459
|
return jsScore > javaScore ? "javascript" : "java";
|
|
4460
4460
|
}
|
|
4461
|
-
function extractTypes(tree, cache, language) {
|
|
4461
|
+
function extractTypes(tree, cache, language, options) {
|
|
4462
4462
|
const effectiveLanguage = language ?? detectLanguage(tree);
|
|
4463
4463
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
4464
4464
|
const isPython = effectiveLanguage === "python";
|
|
@@ -4478,7 +4478,22 @@ function extractTypes(tree, cache, language) {
|
|
|
4478
4478
|
if (effectiveLanguage === "csharp") {
|
|
4479
4479
|
return extractCSharpTypes(tree, cache);
|
|
4480
4480
|
}
|
|
4481
|
-
return extractJavaTypes(tree, cache);
|
|
4481
|
+
return extractJavaTypes(tree, cache, options);
|
|
4482
|
+
}
|
|
4483
|
+
function csharpMemberNodes(body2) {
|
|
4484
|
+
if (!body2) return [];
|
|
4485
|
+
const out2 = [];
|
|
4486
|
+
const visit = (parent, depth) => {
|
|
4487
|
+
if (depth > 32) return;
|
|
4488
|
+
for (let i2 = 0; i2 < parent.childCount; i2++) {
|
|
4489
|
+
const c = parent.child(i2);
|
|
4490
|
+
if (!c) continue;
|
|
4491
|
+
if (c.type.startsWith("preproc_")) visit(c, depth + 1);
|
|
4492
|
+
else out2.push(c);
|
|
4493
|
+
}
|
|
4494
|
+
};
|
|
4495
|
+
visit(body2, 0);
|
|
4496
|
+
return out2;
|
|
4482
4497
|
}
|
|
4483
4498
|
function extractCSharpTypes(tree, cache) {
|
|
4484
4499
|
const types = [];
|
|
@@ -4496,9 +4511,8 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4496
4511
|
const methods = [];
|
|
4497
4512
|
const fields = extractCSharpFields(body2);
|
|
4498
4513
|
if (body2) {
|
|
4499
|
-
for (
|
|
4500
|
-
|
|
4501
|
-
if (!m || m.type !== "method_declaration") continue;
|
|
4514
|
+
for (const m of csharpMemberNodes(body2)) {
|
|
4515
|
+
if (m.type !== "method_declaration") continue;
|
|
4502
4516
|
const mName = m.childForFieldName("name");
|
|
4503
4517
|
const paramList = m.childForFieldName("parameters");
|
|
4504
4518
|
const parameters = [];
|
|
@@ -4549,9 +4563,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4549
4563
|
function extractCSharpFields(body2) {
|
|
4550
4564
|
const fields = [];
|
|
4551
4565
|
if (!body2) return fields;
|
|
4552
|
-
for (
|
|
4553
|
-
const c = body2.child(i2);
|
|
4554
|
-
if (!c) continue;
|
|
4566
|
+
for (const c of csharpMemberNodes(body2)) {
|
|
4555
4567
|
if (c.type === "field_declaration") {
|
|
4556
4568
|
let varDecl = null;
|
|
4557
4569
|
for (let k = 0; k < c.childCount; k++) {
|
|
@@ -4607,7 +4619,7 @@ function extractCSharpModifiers(node) {
|
|
|
4607
4619
|
}
|
|
4608
4620
|
return mods;
|
|
4609
4621
|
}
|
|
4610
|
-
function extractJavaTypes(tree, cache) {
|
|
4622
|
+
function extractJavaTypes(tree, cache, options) {
|
|
4611
4623
|
const types = [];
|
|
4612
4624
|
const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
|
|
4613
4625
|
for (const cls of classes) {
|
|
@@ -4621,8 +4633,76 @@ function extractJavaTypes(tree, cache) {
|
|
|
4621
4633
|
for (const enumDecl of enums) {
|
|
4622
4634
|
types.push(extractEnumInfo(enumDecl));
|
|
4623
4635
|
}
|
|
4636
|
+
if (!options?.navigationTypes) return types;
|
|
4637
|
+
for (const rec of getNodesFromCache(tree.rootNode, "record_declaration", cache)) {
|
|
4638
|
+
const info2 = extractClassInfo(rec);
|
|
4639
|
+
info2.is_record = true;
|
|
4640
|
+
for (const comp of recordComponents(rec)) {
|
|
4641
|
+
if (!info2.fields.some((f) => f.name === comp.name)) {
|
|
4642
|
+
info2.fields.push({
|
|
4643
|
+
name: comp.name,
|
|
4644
|
+
type: comp.type,
|
|
4645
|
+
modifiers: ["private", "final"],
|
|
4646
|
+
annotations: []
|
|
4647
|
+
});
|
|
4648
|
+
}
|
|
4649
|
+
if (!info2.methods.some((m) => m.name === comp.name)) {
|
|
4650
|
+
info2.methods.push({
|
|
4651
|
+
name: comp.name,
|
|
4652
|
+
return_type: comp.type,
|
|
4653
|
+
parameters: [],
|
|
4654
|
+
annotations: [],
|
|
4655
|
+
modifiers: ["public"],
|
|
4656
|
+
start_line: rec.startPosition.row + 1,
|
|
4657
|
+
end_line: rec.startPosition.row + 1
|
|
4658
|
+
});
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
types.push(info2);
|
|
4662
|
+
}
|
|
4663
|
+
const declNodes = [
|
|
4664
|
+
...classes,
|
|
4665
|
+
...interfaces,
|
|
4666
|
+
...enums,
|
|
4667
|
+
...getNodesFromCache(tree.rootNode, "record_declaration", cache)
|
|
4668
|
+
];
|
|
4669
|
+
for (const t of types) {
|
|
4670
|
+
const node = declNodes.find(
|
|
4671
|
+
(n) => n.startPosition.row + 1 === t.start_line && getIdentifier(n, "name") === t.name
|
|
4672
|
+
);
|
|
4673
|
+
const path = node ? enclosingTypePath(node) : [];
|
|
4674
|
+
if (path.length > 0) t.enclosing_type = path.join(".");
|
|
4675
|
+
}
|
|
4624
4676
|
return types;
|
|
4625
4677
|
}
|
|
4678
|
+
function recordComponents(node) {
|
|
4679
|
+
const out2 = [];
|
|
4680
|
+
const params = node.childForFieldName("parameters");
|
|
4681
|
+
if (!params) return out2;
|
|
4682
|
+
for (let i2 = 0; i2 < params.childCount; i2++) {
|
|
4683
|
+
const c = params.child(i2);
|
|
4684
|
+
if (!c || c.type !== "formal_parameter") continue;
|
|
4685
|
+
const name2 = c.childForFieldName("name");
|
|
4686
|
+
const type = c.childForFieldName("type");
|
|
4687
|
+
if (name2) out2.push({ name: getNodeText(name2), type: type ? getNodeText(type) : null });
|
|
4688
|
+
}
|
|
4689
|
+
return out2;
|
|
4690
|
+
}
|
|
4691
|
+
function enclosingTypePath(node) {
|
|
4692
|
+
const DECLS = /* @__PURE__ */ new Set([
|
|
4693
|
+
"class_declaration",
|
|
4694
|
+
"interface_declaration",
|
|
4695
|
+
"enum_declaration",
|
|
4696
|
+
"record_declaration"
|
|
4697
|
+
]);
|
|
4698
|
+
const path = [];
|
|
4699
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
4700
|
+
if (!DECLS.has(p.type)) continue;
|
|
4701
|
+
const n = getIdentifier(p, "name");
|
|
4702
|
+
if (n) path.unshift(n);
|
|
4703
|
+
}
|
|
4704
|
+
return path;
|
|
4705
|
+
}
|
|
4626
4706
|
function extractJavaScriptTypes(tree, cache) {
|
|
4627
4707
|
const types = [];
|
|
4628
4708
|
const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
|
|
@@ -6085,6 +6165,13 @@ function extractCalls(tree, cache, language) {
|
|
|
6085
6165
|
function buildCSharpReceiverTypeMap(tree, cache) {
|
|
6086
6166
|
const map = /* @__PURE__ */ new Map();
|
|
6087
6167
|
const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
|
|
6168
|
+
for (const prm of getNodesFromCache(tree.rootNode, "parameter", cache)) {
|
|
6169
|
+
const nameNode = prm.childForFieldName("name");
|
|
6170
|
+
const typeNode = prm.childForFieldName("type");
|
|
6171
|
+
if (nameNode?.type !== "identifier" || !typeNode) continue;
|
|
6172
|
+
const t = getNodeText(typeNode);
|
|
6173
|
+
if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
|
|
6174
|
+
}
|
|
6088
6175
|
for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
|
|
6089
6176
|
const typeNode = vd.childForFieldName("type");
|
|
6090
6177
|
const declaredType = typeNode ? getNodeText(typeNode) : null;
|
|
@@ -6114,6 +6201,7 @@ function findFirstDescendant(node, type) {
|
|
|
6114
6201
|
}
|
|
6115
6202
|
return null;
|
|
6116
6203
|
}
|
|
6204
|
+
var CSHARP_RESPONSE_TYPES = /* @__PURE__ */ new Set(["HttpResponse", "HttpResponseBase"]);
|
|
6117
6205
|
var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
6118
6206
|
"ExecuteScalar",
|
|
6119
6207
|
"ExecuteReader",
|
|
@@ -6266,7 +6354,8 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6266
6354
|
if (recvType !== "DirectorySearcher") continue;
|
|
6267
6355
|
} else if (propName === "StatusDescription") {
|
|
6268
6356
|
const recv = exprNode ? getNodeText(exprNode) : "";
|
|
6269
|
-
|
|
6357
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6358
|
+
if (!/(^|\.)(Response|HttpResponse|Context\.Response)$/.test(recv) && !(recvType && CSHARP_RESPONSE_TYPES.has(recvType))) continue;
|
|
6270
6359
|
} else if (propName === "BaseAddress") {
|
|
6271
6360
|
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6272
6361
|
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
@@ -11139,6 +11228,23 @@ var DEFAULT_SOURCES = [
|
|
|
11139
11228
|
{ method: "next", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11140
11229
|
{ method: "nextInt", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11141
11230
|
// Database result sources
|
|
11231
|
+
// C# I/O, network and database reads (cognium-dev#502). Mirrors the Java
|
|
11232
|
+
// rows above one-for-one, including the severity split: console/file I/O is
|
|
11233
|
+
// `io_input` at high, a database row read is `db_input` at medium. There is
|
|
11234
|
+
// no separate confidence tier — `type` + `severity` IS the tier, exactly as
|
|
11235
|
+
// for `BufferedReader.readLine` vs `ResultSet.getString`.
|
|
11236
|
+
{ method: "ReadLine", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11237
|
+
{ method: "ReadLineAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11238
|
+
{ method: "ReadToEnd", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11239
|
+
{ method: "ReadToEndAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11240
|
+
// Socket and HTTP-client reads. `GetStream` / `AcceptTcpClient` /
|
|
11241
|
+
// `OpenRead` return the channel; the read off it is covered by the
|
|
11242
|
+
// StreamReader rows above, and seeding the channel too lets a flow form when
|
|
11243
|
+
// the read is inlined.
|
|
11244
|
+
{ method: "GetStream", class: "TcpClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11245
|
+
{ method: "AcceptTcpClient", class: "TcpListener", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11246
|
+
{ method: "OpenRead", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11247
|
+
{ method: "DownloadString", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11142
11248
|
{ method: "getString", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11143
11249
|
{ method: "getObject", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11144
11250
|
{ method: "getInt", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
@@ -13379,6 +13485,29 @@ var DEFAULT_SINKS = [
|
|
|
13379
13485
|
{ method: "deserialize", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13380
13486
|
{ method: "from_str", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13381
13487
|
{ method: "from_str", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13488
|
+
// cognium-dev#541 — the crates the removed classless rows were silently
|
|
13489
|
+
// covering. #484 replaced a classless `from_str` / `from_slice` with
|
|
13490
|
+
// class-scoped rows, but only for the four formats that already had entries;
|
|
13491
|
+
// every other serde data format lost its sink, and `rmp_serde::from_slice`
|
|
13492
|
+
// on bytes read straight off a `TcpStream` stopped being reported. Each row
|
|
13493
|
+
// below is a serde *data format* entry point — a function whose whole job is
|
|
13494
|
+
// to build a typed value out of untrusted bytes or text.
|
|
13495
|
+
{ method: "from_slice", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13496
|
+
{ method: "from_read", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13497
|
+
{ method: "from_read_ref", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13498
|
+
{ method: "from_slice", class: "serde_yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13499
|
+
{ method: "from_slice", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13500
|
+
{ method: "from_reader", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13501
|
+
{ method: "from_bytes", class: "postcard", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13502
|
+
{ method: "from_slice", class: "serde_cbor", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13503
|
+
{ method: "from_reader", class: "serde_cbor", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13504
|
+
{ method: "from_reader", class: "ciborium", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13505
|
+
{ method: "from_str", class: "serde_xml_rs", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13506
|
+
{ method: "from_reader", class: "serde_xml_rs", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13507
|
+
{ method: "from_str", class: "quick_xml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13508
|
+
{ method: "from_reader", class: "quick_xml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13509
|
+
{ method: "from_slice", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13510
|
+
{ method: "deserialize_from", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13382
13511
|
// #294/#484: no classless `from_str` / `from_slice` deserialization sink.
|
|
13383
13512
|
// Those rows matched EVERY `T::from_str` / `from_slice` — `u32::from_str`,
|
|
13384
13513
|
// `Url::from_str`, `PublicKey::from_slice` — i.e. ordinary `FromStr` / byte
|
|
@@ -13703,8 +13832,12 @@ var DEFAULT_SINKS = [
|
|
|
13703
13832
|
{ method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13704
13833
|
{ method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13705
13834
|
{ method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13706
|
-
|
|
13707
|
-
|
|
13835
|
+
// #502: the `(Stream)` overload takes no path — `new StreamReader(tcp.GetStream())`
|
|
13836
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13837
|
+
{ method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13838
|
+
// #502: the `(Stream)` overload takes no path — `new StreamWriter(tcp.GetStream())`
|
|
13839
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13840
|
+
{ method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13708
13841
|
// Remaining System.IO.File path APIs — class-scoped so they don't collide
|
|
13709
13842
|
// with unrelated methods. Copy/Move take a source AND destination path (both
|
|
13710
13843
|
// attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
|
|
@@ -13748,6 +13881,23 @@ var DEFAULT_SINKS = [
|
|
|
13748
13881
|
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
13749
13882
|
// infrastructure. Ctor arg 0, taint-gated.
|
|
13750
13883
|
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13884
|
+
// C# code injection — CodeDOM runtime compilation (CWE-94), cognium-dev#503
|
|
13885
|
+
// part 2. `provider.CompileAssemblyFromSource(parameters, source)` compiles
|
|
13886
|
+
// and can then execute attacker-supplied C#, so the source argument is the
|
|
13887
|
+
// injection point. The signature is
|
|
13888
|
+
// `CompileAssemblyFromSource(CompilerParameters, params string[] sources)`,
|
|
13889
|
+
// so the sources begin at position 1 — position 0 is the options object.
|
|
13890
|
+
//
|
|
13891
|
+
// These rows are only useful together with the #530 StringBuilder
|
|
13892
|
+
// propagation fix: the Juliet CWE-94 corpus accumulates the compiled source
|
|
13893
|
+
// through `sb.Append(...)` and reads it back with `ToString()`, so before
|
|
13894
|
+
// that fix the sink registered but no taint ever reached it.
|
|
13895
|
+
{ method: "CompileAssemblyFromSource", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13896
|
+
{ method: "CompileAssemblyFromSourceBatch", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13897
|
+
// Roslyn's equivalent pair. `ParseText` turns text into a syntax tree and
|
|
13898
|
+
// `Create` compiles it; either is the point where attacker text becomes code.
|
|
13899
|
+
{ method: "ParseText", class: "CSharpSyntaxTree", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13900
|
+
{ method: "ParseText", class: "SyntaxFactory", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13751
13901
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13752
13902
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13753
13903
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -15529,6 +15679,20 @@ function escapeRe(s) {
|
|
|
15529
15679
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15530
15680
|
}
|
|
15531
15681
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
15682
|
+
function argIsStreamExpression(call, position) {
|
|
15683
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
15684
|
+
if (!arg) return false;
|
|
15685
|
+
const expr = (arg.expression ?? "").trim();
|
|
15686
|
+
if (!expr) return false;
|
|
15687
|
+
return (
|
|
15688
|
+
// `new MemoryStream(...)`, `new FileStream(...)`, `new GZipStream(...)`
|
|
15689
|
+
/^new\s+[A-Za-z_][\w.]*Stream\s*\(/.test(expr) || // `tcp.GetStream()`, `resp.GetResponseStream()`, `file.OpenReadStream()`
|
|
15690
|
+
/\.\s*Get(?:Response)?Stream\s*\(/.test(expr) || /\.\s*OpenReadStream\s*\(/.test(expr) || // `wc.OpenRead(url)` returns a Stream (WebClient), as does `File.OpenRead`
|
|
15691
|
+
/\.\s*OpenRead\s*\(/.test(expr) || // `Request.Body`, `x.BaseStream`
|
|
15692
|
+
/\.\s*(?:Body|BaseStream)\s*$/.test(expr) || // a local whose name is stream-ish and is not a path string
|
|
15693
|
+
/^(?:[A-Za-z_]\w*\.)*[A-Za-z_]*[sS]tream\d*$/.test(expr)
|
|
15694
|
+
);
|
|
15695
|
+
}
|
|
15532
15696
|
function argIsStringLiteral(call, position) {
|
|
15533
15697
|
const arg = call.arguments.find((a) => a.position === position);
|
|
15534
15698
|
if (!arg) return false;
|
|
@@ -15766,6 +15930,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15766
15930
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
15767
15931
|
continue;
|
|
15768
15932
|
}
|
|
15933
|
+
if (pattern.safe_if_stream_arg_at !== void 0 && argIsStreamExpression(call, pattern.safe_if_stream_arg_at)) {
|
|
15934
|
+
continue;
|
|
15935
|
+
}
|
|
15769
15936
|
if (pattern.type === "command_injection") {
|
|
15770
15937
|
if (call.is_constructor) {
|
|
15771
15938
|
if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
|
|
@@ -4392,7 +4392,7 @@ function detectLanguage(tree) {
|
|
|
4392
4392
|
if (pythonScore > jsScore && pythonScore > javaScore) return "python";
|
|
4393
4393
|
return jsScore > javaScore ? "javascript" : "java";
|
|
4394
4394
|
}
|
|
4395
|
-
function extractTypes(tree, cache, language) {
|
|
4395
|
+
function extractTypes(tree, cache, language, options) {
|
|
4396
4396
|
const effectiveLanguage = language ?? detectLanguage(tree);
|
|
4397
4397
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
4398
4398
|
const isPython = effectiveLanguage === "python";
|
|
@@ -4412,7 +4412,22 @@ function extractTypes(tree, cache, language) {
|
|
|
4412
4412
|
if (effectiveLanguage === "csharp") {
|
|
4413
4413
|
return extractCSharpTypes(tree, cache);
|
|
4414
4414
|
}
|
|
4415
|
-
return extractJavaTypes(tree, cache);
|
|
4415
|
+
return extractJavaTypes(tree, cache, options);
|
|
4416
|
+
}
|
|
4417
|
+
function csharpMemberNodes(body2) {
|
|
4418
|
+
if (!body2) return [];
|
|
4419
|
+
const out2 = [];
|
|
4420
|
+
const visit = (parent, depth) => {
|
|
4421
|
+
if (depth > 32) return;
|
|
4422
|
+
for (let i2 = 0; i2 < parent.childCount; i2++) {
|
|
4423
|
+
const c = parent.child(i2);
|
|
4424
|
+
if (!c) continue;
|
|
4425
|
+
if (c.type.startsWith("preproc_")) visit(c, depth + 1);
|
|
4426
|
+
else out2.push(c);
|
|
4427
|
+
}
|
|
4428
|
+
};
|
|
4429
|
+
visit(body2, 0);
|
|
4430
|
+
return out2;
|
|
4416
4431
|
}
|
|
4417
4432
|
function extractCSharpTypes(tree, cache) {
|
|
4418
4433
|
const types = [];
|
|
@@ -4430,9 +4445,8 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4430
4445
|
const methods = [];
|
|
4431
4446
|
const fields = extractCSharpFields(body2);
|
|
4432
4447
|
if (body2) {
|
|
4433
|
-
for (
|
|
4434
|
-
|
|
4435
|
-
if (!m || m.type !== "method_declaration") continue;
|
|
4448
|
+
for (const m of csharpMemberNodes(body2)) {
|
|
4449
|
+
if (m.type !== "method_declaration") continue;
|
|
4436
4450
|
const mName = m.childForFieldName("name");
|
|
4437
4451
|
const paramList = m.childForFieldName("parameters");
|
|
4438
4452
|
const parameters = [];
|
|
@@ -4483,9 +4497,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4483
4497
|
function extractCSharpFields(body2) {
|
|
4484
4498
|
const fields = [];
|
|
4485
4499
|
if (!body2) return fields;
|
|
4486
|
-
for (
|
|
4487
|
-
const c = body2.child(i2);
|
|
4488
|
-
if (!c) continue;
|
|
4500
|
+
for (const c of csharpMemberNodes(body2)) {
|
|
4489
4501
|
if (c.type === "field_declaration") {
|
|
4490
4502
|
let varDecl = null;
|
|
4491
4503
|
for (let k = 0; k < c.childCount; k++) {
|
|
@@ -4541,7 +4553,7 @@ function extractCSharpModifiers(node) {
|
|
|
4541
4553
|
}
|
|
4542
4554
|
return mods;
|
|
4543
4555
|
}
|
|
4544
|
-
function extractJavaTypes(tree, cache) {
|
|
4556
|
+
function extractJavaTypes(tree, cache, options) {
|
|
4545
4557
|
const types = [];
|
|
4546
4558
|
const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
|
|
4547
4559
|
for (const cls of classes) {
|
|
@@ -4555,8 +4567,76 @@ function extractJavaTypes(tree, cache) {
|
|
|
4555
4567
|
for (const enumDecl of enums) {
|
|
4556
4568
|
types.push(extractEnumInfo(enumDecl));
|
|
4557
4569
|
}
|
|
4570
|
+
if (!options?.navigationTypes) return types;
|
|
4571
|
+
for (const rec of getNodesFromCache(tree.rootNode, "record_declaration", cache)) {
|
|
4572
|
+
const info2 = extractClassInfo(rec);
|
|
4573
|
+
info2.is_record = true;
|
|
4574
|
+
for (const comp of recordComponents(rec)) {
|
|
4575
|
+
if (!info2.fields.some((f) => f.name === comp.name)) {
|
|
4576
|
+
info2.fields.push({
|
|
4577
|
+
name: comp.name,
|
|
4578
|
+
type: comp.type,
|
|
4579
|
+
modifiers: ["private", "final"],
|
|
4580
|
+
annotations: []
|
|
4581
|
+
});
|
|
4582
|
+
}
|
|
4583
|
+
if (!info2.methods.some((m) => m.name === comp.name)) {
|
|
4584
|
+
info2.methods.push({
|
|
4585
|
+
name: comp.name,
|
|
4586
|
+
return_type: comp.type,
|
|
4587
|
+
parameters: [],
|
|
4588
|
+
annotations: [],
|
|
4589
|
+
modifiers: ["public"],
|
|
4590
|
+
start_line: rec.startPosition.row + 1,
|
|
4591
|
+
end_line: rec.startPosition.row + 1
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
types.push(info2);
|
|
4596
|
+
}
|
|
4597
|
+
const declNodes = [
|
|
4598
|
+
...classes,
|
|
4599
|
+
...interfaces,
|
|
4600
|
+
...enums,
|
|
4601
|
+
...getNodesFromCache(tree.rootNode, "record_declaration", cache)
|
|
4602
|
+
];
|
|
4603
|
+
for (const t of types) {
|
|
4604
|
+
const node = declNodes.find(
|
|
4605
|
+
(n) => n.startPosition.row + 1 === t.start_line && getIdentifier(n, "name") === t.name
|
|
4606
|
+
);
|
|
4607
|
+
const path = node ? enclosingTypePath(node) : [];
|
|
4608
|
+
if (path.length > 0) t.enclosing_type = path.join(".");
|
|
4609
|
+
}
|
|
4558
4610
|
return types;
|
|
4559
4611
|
}
|
|
4612
|
+
function recordComponents(node) {
|
|
4613
|
+
const out2 = [];
|
|
4614
|
+
const params = node.childForFieldName("parameters");
|
|
4615
|
+
if (!params) return out2;
|
|
4616
|
+
for (let i2 = 0; i2 < params.childCount; i2++) {
|
|
4617
|
+
const c = params.child(i2);
|
|
4618
|
+
if (!c || c.type !== "formal_parameter") continue;
|
|
4619
|
+
const name2 = c.childForFieldName("name");
|
|
4620
|
+
const type = c.childForFieldName("type");
|
|
4621
|
+
if (name2) out2.push({ name: getNodeText(name2), type: type ? getNodeText(type) : null });
|
|
4622
|
+
}
|
|
4623
|
+
return out2;
|
|
4624
|
+
}
|
|
4625
|
+
function enclosingTypePath(node) {
|
|
4626
|
+
const DECLS = /* @__PURE__ */ new Set([
|
|
4627
|
+
"class_declaration",
|
|
4628
|
+
"interface_declaration",
|
|
4629
|
+
"enum_declaration",
|
|
4630
|
+
"record_declaration"
|
|
4631
|
+
]);
|
|
4632
|
+
const path = [];
|
|
4633
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
4634
|
+
if (!DECLS.has(p.type)) continue;
|
|
4635
|
+
const n = getIdentifier(p, "name");
|
|
4636
|
+
if (n) path.unshift(n);
|
|
4637
|
+
}
|
|
4638
|
+
return path;
|
|
4639
|
+
}
|
|
4560
4640
|
function extractJavaScriptTypes(tree, cache) {
|
|
4561
4641
|
const types = [];
|
|
4562
4642
|
const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
|
|
@@ -6019,6 +6099,13 @@ function extractCalls(tree, cache, language) {
|
|
|
6019
6099
|
function buildCSharpReceiverTypeMap(tree, cache) {
|
|
6020
6100
|
const map = /* @__PURE__ */ new Map();
|
|
6021
6101
|
const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
|
|
6102
|
+
for (const prm of getNodesFromCache(tree.rootNode, "parameter", cache)) {
|
|
6103
|
+
const nameNode = prm.childForFieldName("name");
|
|
6104
|
+
const typeNode = prm.childForFieldName("type");
|
|
6105
|
+
if (nameNode?.type !== "identifier" || !typeNode) continue;
|
|
6106
|
+
const t = getNodeText(typeNode);
|
|
6107
|
+
if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
|
|
6108
|
+
}
|
|
6022
6109
|
for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
|
|
6023
6110
|
const typeNode = vd.childForFieldName("type");
|
|
6024
6111
|
const declaredType = typeNode ? getNodeText(typeNode) : null;
|
|
@@ -6048,6 +6135,7 @@ function findFirstDescendant(node, type) {
|
|
|
6048
6135
|
}
|
|
6049
6136
|
return null;
|
|
6050
6137
|
}
|
|
6138
|
+
var CSHARP_RESPONSE_TYPES = /* @__PURE__ */ new Set(["HttpResponse", "HttpResponseBase"]);
|
|
6051
6139
|
var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
6052
6140
|
"ExecuteScalar",
|
|
6053
6141
|
"ExecuteReader",
|
|
@@ -6200,7 +6288,8 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6200
6288
|
if (recvType !== "DirectorySearcher") continue;
|
|
6201
6289
|
} else if (propName === "StatusDescription") {
|
|
6202
6290
|
const recv = exprNode ? getNodeText(exprNode) : "";
|
|
6203
|
-
|
|
6291
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6292
|
+
if (!/(^|\.)(Response|HttpResponse|Context\.Response)$/.test(recv) && !(recvType && CSHARP_RESPONSE_TYPES.has(recvType))) continue;
|
|
6204
6293
|
} else if (propName === "BaseAddress") {
|
|
6205
6294
|
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6206
6295
|
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
@@ -11073,6 +11162,23 @@ var DEFAULT_SOURCES = [
|
|
|
11073
11162
|
{ method: "next", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11074
11163
|
{ method: "nextInt", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11075
11164
|
// Database result sources
|
|
11165
|
+
// C# I/O, network and database reads (cognium-dev#502). Mirrors the Java
|
|
11166
|
+
// rows above one-for-one, including the severity split: console/file I/O is
|
|
11167
|
+
// `io_input` at high, a database row read is `db_input` at medium. There is
|
|
11168
|
+
// no separate confidence tier — `type` + `severity` IS the tier, exactly as
|
|
11169
|
+
// for `BufferedReader.readLine` vs `ResultSet.getString`.
|
|
11170
|
+
{ method: "ReadLine", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11171
|
+
{ method: "ReadLineAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11172
|
+
{ method: "ReadToEnd", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11173
|
+
{ method: "ReadToEndAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11174
|
+
// Socket and HTTP-client reads. `GetStream` / `AcceptTcpClient` /
|
|
11175
|
+
// `OpenRead` return the channel; the read off it is covered by the
|
|
11176
|
+
// StreamReader rows above, and seeding the channel too lets a flow form when
|
|
11177
|
+
// the read is inlined.
|
|
11178
|
+
{ method: "GetStream", class: "TcpClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11179
|
+
{ method: "AcceptTcpClient", class: "TcpListener", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11180
|
+
{ method: "OpenRead", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11181
|
+
{ method: "DownloadString", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11076
11182
|
{ method: "getString", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11077
11183
|
{ method: "getObject", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11078
11184
|
{ method: "getInt", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
@@ -13313,6 +13419,29 @@ var DEFAULT_SINKS = [
|
|
|
13313
13419
|
{ method: "deserialize", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13314
13420
|
{ method: "from_str", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13315
13421
|
{ method: "from_str", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13422
|
+
// cognium-dev#541 — the crates the removed classless rows were silently
|
|
13423
|
+
// covering. #484 replaced a classless `from_str` / `from_slice` with
|
|
13424
|
+
// class-scoped rows, but only for the four formats that already had entries;
|
|
13425
|
+
// every other serde data format lost its sink, and `rmp_serde::from_slice`
|
|
13426
|
+
// on bytes read straight off a `TcpStream` stopped being reported. Each row
|
|
13427
|
+
// below is a serde *data format* entry point — a function whose whole job is
|
|
13428
|
+
// to build a typed value out of untrusted bytes or text.
|
|
13429
|
+
{ method: "from_slice", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13430
|
+
{ method: "from_read", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13431
|
+
{ method: "from_read_ref", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13432
|
+
{ method: "from_slice", class: "serde_yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13433
|
+
{ method: "from_slice", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13434
|
+
{ method: "from_reader", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13435
|
+
{ method: "from_bytes", class: "postcard", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13436
|
+
{ method: "from_slice", class: "serde_cbor", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13437
|
+
{ method: "from_reader", class: "serde_cbor", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13438
|
+
{ method: "from_reader", class: "ciborium", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13439
|
+
{ method: "from_str", class: "serde_xml_rs", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13440
|
+
{ method: "from_reader", class: "serde_xml_rs", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13441
|
+
{ method: "from_str", class: "quick_xml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13442
|
+
{ method: "from_reader", class: "quick_xml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13443
|
+
{ method: "from_slice", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13444
|
+
{ method: "deserialize_from", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13316
13445
|
// #294/#484: no classless `from_str` / `from_slice` deserialization sink.
|
|
13317
13446
|
// Those rows matched EVERY `T::from_str` / `from_slice` — `u32::from_str`,
|
|
13318
13447
|
// `Url::from_str`, `PublicKey::from_slice` — i.e. ordinary `FromStr` / byte
|
|
@@ -13637,8 +13766,12 @@ var DEFAULT_SINKS = [
|
|
|
13637
13766
|
{ method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13638
13767
|
{ method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13639
13768
|
{ method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13640
|
-
|
|
13641
|
-
|
|
13769
|
+
// #502: the `(Stream)` overload takes no path — `new StreamReader(tcp.GetStream())`
|
|
13770
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13771
|
+
{ method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13772
|
+
// #502: the `(Stream)` overload takes no path — `new StreamWriter(tcp.GetStream())`
|
|
13773
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13774
|
+
{ method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13642
13775
|
// Remaining System.IO.File path APIs — class-scoped so they don't collide
|
|
13643
13776
|
// with unrelated methods. Copy/Move take a source AND destination path (both
|
|
13644
13777
|
// attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
|
|
@@ -13682,6 +13815,23 @@ var DEFAULT_SINKS = [
|
|
|
13682
13815
|
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
13683
13816
|
// infrastructure. Ctor arg 0, taint-gated.
|
|
13684
13817
|
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13818
|
+
// C# code injection — CodeDOM runtime compilation (CWE-94), cognium-dev#503
|
|
13819
|
+
// part 2. `provider.CompileAssemblyFromSource(parameters, source)` compiles
|
|
13820
|
+
// and can then execute attacker-supplied C#, so the source argument is the
|
|
13821
|
+
// injection point. The signature is
|
|
13822
|
+
// `CompileAssemblyFromSource(CompilerParameters, params string[] sources)`,
|
|
13823
|
+
// so the sources begin at position 1 — position 0 is the options object.
|
|
13824
|
+
//
|
|
13825
|
+
// These rows are only useful together with the #530 StringBuilder
|
|
13826
|
+
// propagation fix: the Juliet CWE-94 corpus accumulates the compiled source
|
|
13827
|
+
// through `sb.Append(...)` and reads it back with `ToString()`, so before
|
|
13828
|
+
// that fix the sink registered but no taint ever reached it.
|
|
13829
|
+
{ method: "CompileAssemblyFromSource", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13830
|
+
{ method: "CompileAssemblyFromSourceBatch", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13831
|
+
// Roslyn's equivalent pair. `ParseText` turns text into a syntax tree and
|
|
13832
|
+
// `Create` compiles it; either is the point where attacker text becomes code.
|
|
13833
|
+
{ method: "ParseText", class: "CSharpSyntaxTree", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13834
|
+
{ method: "ParseText", class: "SyntaxFactory", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13685
13835
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13686
13836
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13687
13837
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -15463,6 +15613,20 @@ function escapeRe(s) {
|
|
|
15463
15613
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15464
15614
|
}
|
|
15465
15615
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
15616
|
+
function argIsStreamExpression(call, position) {
|
|
15617
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
15618
|
+
if (!arg) return false;
|
|
15619
|
+
const expr = (arg.expression ?? "").trim();
|
|
15620
|
+
if (!expr) return false;
|
|
15621
|
+
return (
|
|
15622
|
+
// `new MemoryStream(...)`, `new FileStream(...)`, `new GZipStream(...)`
|
|
15623
|
+
/^new\s+[A-Za-z_][\w.]*Stream\s*\(/.test(expr) || // `tcp.GetStream()`, `resp.GetResponseStream()`, `file.OpenReadStream()`
|
|
15624
|
+
/\.\s*Get(?:Response)?Stream\s*\(/.test(expr) || /\.\s*OpenReadStream\s*\(/.test(expr) || // `wc.OpenRead(url)` returns a Stream (WebClient), as does `File.OpenRead`
|
|
15625
|
+
/\.\s*OpenRead\s*\(/.test(expr) || // `Request.Body`, `x.BaseStream`
|
|
15626
|
+
/\.\s*(?:Body|BaseStream)\s*$/.test(expr) || // a local whose name is stream-ish and is not a path string
|
|
15627
|
+
/^(?:[A-Za-z_]\w*\.)*[A-Za-z_]*[sS]tream\d*$/.test(expr)
|
|
15628
|
+
);
|
|
15629
|
+
}
|
|
15466
15630
|
function argIsStringLiteral(call, position) {
|
|
15467
15631
|
const arg = call.arguments.find((a) => a.position === position);
|
|
15468
15632
|
if (!arg) return false;
|
|
@@ -15700,6 +15864,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15700
15864
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
15701
15865
|
continue;
|
|
15702
15866
|
}
|
|
15867
|
+
if (pattern.safe_if_stream_arg_at !== void 0 && argIsStreamExpression(call, pattern.safe_if_stream_arg_at)) {
|
|
15868
|
+
continue;
|
|
15869
|
+
}
|
|
15703
15870
|
if (pattern.type === "command_injection") {
|
|
15704
15871
|
if (call.is_constructor) {
|
|
15705
15872
|
if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
|
|
@@ -118,6 +118,20 @@ export function extractCalls(tree, cache, language) {
|
|
|
118
118
|
function buildCSharpReceiverTypeMap(tree, cache) {
|
|
119
119
|
const map = new Map();
|
|
120
120
|
const simple = (t) => t.replace(/<[^>]*>/g, '').split('.').pop()?.trim() ?? t;
|
|
121
|
+
// cognium-dev#542 — method/constructor parameters. A receiver is just as often a parameter as a
|
|
122
|
+
// local — `void Bad(HttpRequest req, HttpResponse resp)` is the canonical
|
|
123
|
+
// ASP.NET handler shape — and omitting them meant every receiver gate keyed on
|
|
124
|
+
// a declared type silently failed for parameters. Locals below win on a name
|
|
125
|
+
// clash, since an inner declaration shadows the parameter.
|
|
126
|
+
for (const prm of getNodesFromCache(tree.rootNode, 'parameter', cache)) {
|
|
127
|
+
const nameNode = prm.childForFieldName('name');
|
|
128
|
+
const typeNode = prm.childForFieldName('type');
|
|
129
|
+
if (nameNode?.type !== 'identifier' || !typeNode)
|
|
130
|
+
continue;
|
|
131
|
+
const t = getNodeText(typeNode);
|
|
132
|
+
if (t && t !== 'var')
|
|
133
|
+
map.set(getNodeText(nameNode), simple(t));
|
|
134
|
+
}
|
|
121
135
|
for (const vd of getNodesFromCache(tree.rootNode, 'variable_declaration', cache)) {
|
|
122
136
|
const typeNode = vd.childForFieldName('type');
|
|
123
137
|
const declaredType = typeNode ? getNodeText(typeNode) : null;
|
|
@@ -155,6 +169,12 @@ function findFirstDescendant(node, type) {
|
|
|
155
169
|
}
|
|
156
170
|
return null;
|
|
157
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Declared types that make a receiver an HTTP response. `HttpResponse` is
|
|
174
|
+
* classic ASP.NET and ASP.NET Core; `HttpResponseBase` is the testable
|
|
175
|
+
* abstraction MVC hands controllers.
|
|
176
|
+
*/
|
|
177
|
+
const CSHARP_RESPONSE_TYPES = new Set(['HttpResponse', 'HttpResponseBase']);
|
|
158
178
|
/** ADO.NET command-execution methods whose taint rides the receiver object. */
|
|
159
179
|
const CSHARP_COMMAND_EXECUTE_METHODS = new Set([
|
|
160
180
|
'ExecuteScalar', 'ExecuteReader', 'ExecuteNonQuery',
|
|
@@ -381,7 +401,15 @@ function extractCSharpCalls(tree, cache) {
|
|
|
381
401
|
// a Response-shaped name so an unrelated `.StatusDescription =` on some
|
|
382
402
|
// other type is not surfaced.
|
|
383
403
|
const recv = exprNode ? getNodeText(exprNode) : '';
|
|
384
|
-
|
|
404
|
+
const recvType = recv ? typeMap.get(recv) : undefined;
|
|
405
|
+
// cognium-dev#542 — two ways to be a response: named like the ASP.NET
|
|
406
|
+
// intrinsic (`Response`, `Context.Response`), which has no declaration to
|
|
407
|
+
// resolve, or *declared* as one. Juliet names the parameter `resp`, so the
|
|
408
|
+
// name test alone scored CWE-81 at 0/9 — the same defect #501 fixed on the
|
|
409
|
+
// source side, where `HttpRequest req` was missed because it was not
|
|
410
|
+
// spelled `Request`.
|
|
411
|
+
if (!/(^|\.)(Response|HttpResponse|Context\.Response)$/.test(recv)
|
|
412
|
+
&& !(recvType && CSHARP_RESPONSE_TYPES.has(recvType)))
|
|
385
413
|
continue;
|
|
386
414
|
}
|
|
387
415
|
else if (propName === 'BaseAddress') {
|