circle-ir 4.9.27 → 4.9.29
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 +58 -5
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +130 -0
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +63 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +162 -8
- package/dist/analysis/passes/language-sources-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 +214 -16
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/variable-shadowing-pass.d.ts.map +1 -1
- package/dist/analysis/passes/variable-shadowing-pass.js +15 -0
- package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +62 -1
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +486 -33
- package/dist/core/circle-ir-core.cjs +173 -13
- package/dist/core/circle-ir-core.js +173 -13
- package/dist/core/extractors/calls.js +155 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/types.js +50 -7
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/types/config.d.ts +14 -0
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -4480,6 +4480,21 @@ function extractTypes(tree, cache, language) {
|
|
|
4480
4480
|
}
|
|
4481
4481
|
return extractJavaTypes(tree, cache);
|
|
4482
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;
|
|
4497
|
+
}
|
|
4483
4498
|
function extractCSharpTypes(tree, cache) {
|
|
4484
4499
|
const types = [];
|
|
4485
4500
|
const KINDS = {
|
|
@@ -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++) {
|
|
@@ -6085,6 +6097,13 @@ function extractCalls(tree, cache, language) {
|
|
|
6085
6097
|
function buildCSharpReceiverTypeMap(tree, cache) {
|
|
6086
6098
|
const map = /* @__PURE__ */ new Map();
|
|
6087
6099
|
const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
|
|
6100
|
+
for (const prm of getNodesFromCache(tree.rootNode, "parameter", cache)) {
|
|
6101
|
+
const nameNode = prm.childForFieldName("name");
|
|
6102
|
+
const typeNode = prm.childForFieldName("type");
|
|
6103
|
+
if (nameNode?.type !== "identifier" || !typeNode) continue;
|
|
6104
|
+
const t = getNodeText(typeNode);
|
|
6105
|
+
if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
|
|
6106
|
+
}
|
|
6088
6107
|
for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
|
|
6089
6108
|
const typeNode = vd.childForFieldName("type");
|
|
6090
6109
|
const declaredType = typeNode ? getNodeText(typeNode) : null;
|
|
@@ -6114,6 +6133,7 @@ function findFirstDescendant(node, type) {
|
|
|
6114
6133
|
}
|
|
6115
6134
|
return null;
|
|
6116
6135
|
}
|
|
6136
|
+
var CSHARP_RESPONSE_TYPES = /* @__PURE__ */ new Set(["HttpResponse", "HttpResponseBase"]);
|
|
6117
6137
|
var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
6118
6138
|
"ExecuteScalar",
|
|
6119
6139
|
"ExecuteReader",
|
|
@@ -6230,6 +6250,31 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6230
6250
|
});
|
|
6231
6251
|
continue;
|
|
6232
6252
|
}
|
|
6253
|
+
if (left?.type === "identifier" && asn.parent?.type === "initializer_expression") {
|
|
6254
|
+
const creation = asn.parent.parent;
|
|
6255
|
+
if (creation?.type !== "object_creation_expression") continue;
|
|
6256
|
+
const ctorType = creation.childForFieldName("type");
|
|
6257
|
+
const right2 = asn.childForFieldName("right");
|
|
6258
|
+
if (!ctorType || !right2) continue;
|
|
6259
|
+
const typeName = csharpBareName(ctorType);
|
|
6260
|
+
const rhsText2 = getNodeText(right2);
|
|
6261
|
+
calls.push({
|
|
6262
|
+
method_name: getNodeText(left),
|
|
6263
|
+
receiver: typeName,
|
|
6264
|
+
receiver_type: typeName,
|
|
6265
|
+
receiver_type_fqn: null,
|
|
6266
|
+
arguments: [{
|
|
6267
|
+
position: 0,
|
|
6268
|
+
expression: rhsText2,
|
|
6269
|
+
variable: right2.type === "identifier" ? rhsText2 : null,
|
|
6270
|
+
literal: CSHARP_LITERAL_NODE_TYPES.has(right2.type) ? rhsText2 : null,
|
|
6271
|
+
value: null
|
|
6272
|
+
}],
|
|
6273
|
+
location: { line: asn.startPosition.row + 1, column: asn.startPosition.column },
|
|
6274
|
+
in_method: findEnclosingMethod(asn)
|
|
6275
|
+
});
|
|
6276
|
+
continue;
|
|
6277
|
+
}
|
|
6233
6278
|
if (left?.type !== "member_access_expression") continue;
|
|
6234
6279
|
const nameNode = left.childForFieldName("name");
|
|
6235
6280
|
if (!nameNode) continue;
|
|
@@ -6239,6 +6284,14 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6239
6284
|
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6240
6285
|
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6241
6286
|
if (recvType !== "DirectorySearcher") continue;
|
|
6287
|
+
} else if (propName === "StatusDescription") {
|
|
6288
|
+
const recv = exprNode ? getNodeText(exprNode) : "";
|
|
6289
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6290
|
+
if (!/(^|\.)(Response|HttpResponse|Context\.Response)$/.test(recv) && !(recvType && CSHARP_RESPONSE_TYPES.has(recvType))) continue;
|
|
6291
|
+
} else if (propName === "BaseAddress") {
|
|
6292
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6293
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6294
|
+
if (recvType !== "HttpClient") continue;
|
|
6242
6295
|
} else if (propName !== "CommandText") {
|
|
6243
6296
|
continue;
|
|
6244
6297
|
}
|
|
@@ -6261,8 +6314,34 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6261
6314
|
in_method: findEnclosingMethod(asn)
|
|
6262
6315
|
});
|
|
6263
6316
|
}
|
|
6317
|
+
const casts = getNodesFromCache(tree.rootNode, "cast_expression", cache);
|
|
6318
|
+
for (const cast of casts) {
|
|
6319
|
+
const typeNode = cast.childForFieldName("type");
|
|
6320
|
+
const valueNode = cast.childForFieldName("value");
|
|
6321
|
+
if (!typeNode || !valueNode) continue;
|
|
6322
|
+
const typeName = csharpBareName(typeNode);
|
|
6323
|
+
if (!CSHARP_RAW_MARKUP_CAST_TYPES.has(typeName)) continue;
|
|
6324
|
+
const valueText = getNodeText(valueNode);
|
|
6325
|
+
calls.push({
|
|
6326
|
+
method_name: typeName,
|
|
6327
|
+
receiver: null,
|
|
6328
|
+
receiver_type: null,
|
|
6329
|
+
receiver_type_fqn: null,
|
|
6330
|
+
arguments: [{
|
|
6331
|
+
position: 0,
|
|
6332
|
+
expression: valueText,
|
|
6333
|
+
variable: valueNode.type === "identifier" ? valueText : null,
|
|
6334
|
+
literal: CSHARP_LITERAL_NODE_TYPES.has(valueNode.type) ? valueText : null,
|
|
6335
|
+
value: null
|
|
6336
|
+
}],
|
|
6337
|
+
location: { line: cast.startPosition.row + 1, column: cast.startPosition.column },
|
|
6338
|
+
in_method: findEnclosingMethod(cast),
|
|
6339
|
+
is_constructor: true
|
|
6340
|
+
});
|
|
6341
|
+
}
|
|
6264
6342
|
return calls;
|
|
6265
6343
|
}
|
|
6344
|
+
var CSHARP_RAW_MARKUP_CAST_TYPES = /* @__PURE__ */ new Set(["MarkupString"]);
|
|
6266
6345
|
function extractCSharpArguments(argsNode) {
|
|
6267
6346
|
const args2 = [];
|
|
6268
6347
|
let position = 0;
|
|
@@ -6282,12 +6361,22 @@ function extractCSharpArguments(argsNode) {
|
|
|
6282
6361
|
position: position++,
|
|
6283
6362
|
expression: text,
|
|
6284
6363
|
variable: expr?.type === "identifier" ? text : null,
|
|
6285
|
-
literal: expr
|
|
6364
|
+
literal: expr && CSHARP_LITERAL_NODE_TYPES.has(expr.type) ? text : null,
|
|
6286
6365
|
value: null
|
|
6287
6366
|
});
|
|
6288
6367
|
}
|
|
6289
6368
|
return args2;
|
|
6290
6369
|
}
|
|
6370
|
+
var CSHARP_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
6371
|
+
"string_literal",
|
|
6372
|
+
"verbatim_string_literal",
|
|
6373
|
+
"raw_string_literal",
|
|
6374
|
+
"character_literal",
|
|
6375
|
+
"integer_literal",
|
|
6376
|
+
"real_literal",
|
|
6377
|
+
"boolean_literal",
|
|
6378
|
+
"null_literal"
|
|
6379
|
+
]);
|
|
6291
6380
|
function extractJavaScriptCalls(tree, cache) {
|
|
6292
6381
|
const calls = [];
|
|
6293
6382
|
const context = buildJSResolutionContext(tree, cache);
|
|
@@ -11071,6 +11160,23 @@ var DEFAULT_SOURCES = [
|
|
|
11071
11160
|
{ method: "next", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11072
11161
|
{ method: "nextInt", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11073
11162
|
// Database result sources
|
|
11163
|
+
// C# I/O, network and database reads (cognium-dev#502). Mirrors the Java
|
|
11164
|
+
// rows above one-for-one, including the severity split: console/file I/O is
|
|
11165
|
+
// `io_input` at high, a database row read is `db_input` at medium. There is
|
|
11166
|
+
// no separate confidence tier — `type` + `severity` IS the tier, exactly as
|
|
11167
|
+
// for `BufferedReader.readLine` vs `ResultSet.getString`.
|
|
11168
|
+
{ method: "ReadLine", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11169
|
+
{ method: "ReadLineAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11170
|
+
{ method: "ReadToEnd", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11171
|
+
{ method: "ReadToEndAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11172
|
+
// Socket and HTTP-client reads. `GetStream` / `AcceptTcpClient` /
|
|
11173
|
+
// `OpenRead` return the channel; the read off it is covered by the
|
|
11174
|
+
// StreamReader rows above, and seeding the channel too lets a flow form when
|
|
11175
|
+
// the read is inlined.
|
|
11176
|
+
{ method: "GetStream", class: "TcpClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11177
|
+
{ method: "AcceptTcpClient", class: "TcpListener", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11178
|
+
{ method: "OpenRead", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11179
|
+
{ method: "DownloadString", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11074
11180
|
{ method: "getString", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11075
11181
|
{ method: "getObject", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11076
11182
|
{ method: "getInt", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
@@ -13311,9 +13417,14 @@ var DEFAULT_SINKS = [
|
|
|
13311
13417
|
{ method: "deserialize", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13312
13418
|
{ method: "from_str", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13313
13419
|
{ method: "from_str", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13314
|
-
//
|
|
13315
|
-
|
|
13316
|
-
|
|
13420
|
+
// #294/#484: no classless `from_str` / `from_slice` deserialization sink.
|
|
13421
|
+
// Those rows matched EVERY `T::from_str` / `from_slice` — `u32::from_str`,
|
|
13422
|
+
// `Url::from_str`, `PublicKey::from_slice` — i.e. ordinary `FromStr` / byte
|
|
13423
|
+
// parsing, which cannot instantiate an attacker-chosen type. Removing them
|
|
13424
|
+
// was previously blocked because the class-scoped rows below could not match
|
|
13425
|
+
// the IMPORTED call form (`use serde_json::from_str; from_str(&body)`); the
|
|
13426
|
+
// `::` path-separator fix in taint-matcher.ts makes them match, so the
|
|
13427
|
+
// classless rows are no longer load-bearing.
|
|
13317
13428
|
// Rust XSS (actix-web, rocket, axum response body)
|
|
13318
13429
|
{ method: "body", class: "HttpResponseBuilder", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
13319
13430
|
{ method: "body", class: "HttpResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -13630,8 +13741,12 @@ var DEFAULT_SINKS = [
|
|
|
13630
13741
|
{ method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13631
13742
|
{ method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13632
13743
|
{ method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13633
|
-
|
|
13634
|
-
|
|
13744
|
+
// #502: the `(Stream)` overload takes no path — `new StreamReader(tcp.GetStream())`
|
|
13745
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13746
|
+
{ method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13747
|
+
// #502: the `(Stream)` overload takes no path — `new StreamWriter(tcp.GetStream())`
|
|
13748
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13749
|
+
{ method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13635
13750
|
// Remaining System.IO.File path APIs — class-scoped so they don't collide
|
|
13636
13751
|
// with unrelated methods. Copy/Move take a source AND destination path (both
|
|
13637
13752
|
// attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
|
|
@@ -13675,6 +13790,23 @@ var DEFAULT_SINKS = [
|
|
|
13675
13790
|
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
13676
13791
|
// infrastructure. Ctor arg 0, taint-gated.
|
|
13677
13792
|
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13793
|
+
// C# code injection — CodeDOM runtime compilation (CWE-94), cognium-dev#503
|
|
13794
|
+
// part 2. `provider.CompileAssemblyFromSource(parameters, source)` compiles
|
|
13795
|
+
// and can then execute attacker-supplied C#, so the source argument is the
|
|
13796
|
+
// injection point. The signature is
|
|
13797
|
+
// `CompileAssemblyFromSource(CompilerParameters, params string[] sources)`,
|
|
13798
|
+
// so the sources begin at position 1 — position 0 is the options object.
|
|
13799
|
+
//
|
|
13800
|
+
// These rows are only useful together with the #530 StringBuilder
|
|
13801
|
+
// propagation fix: the Juliet CWE-94 corpus accumulates the compiled source
|
|
13802
|
+
// through `sb.Append(...)` and reads it back with `ToString()`, so before
|
|
13803
|
+
// that fix the sink registered but no taint ever reached it.
|
|
13804
|
+
{ method: "CompileAssemblyFromSource", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13805
|
+
{ method: "CompileAssemblyFromSourceBatch", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13806
|
+
// Roslyn's equivalent pair. `ParseText` turns text into a syntax tree and
|
|
13807
|
+
// `Create` compiles it; either is the point where attacker text becomes code.
|
|
13808
|
+
{ method: "ParseText", class: "CSharpSyntaxTree", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13809
|
+
{ method: "ParseText", class: "SyntaxFactory", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13678
13810
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13679
13811
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13680
13812
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13719,6 +13851,10 @@ var DEFAULT_SINKS = [
|
|
|
13719
13851
|
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13720
13852
|
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13721
13853
|
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13854
|
+
// #503 (part 1): `Response.StatusDescription = "Bad " + data` writes
|
|
13855
|
+
// attacker text into the HTTP status line (CWE-81). Surfaced by the
|
|
13856
|
+
// property-assignment synthesiser in the C# extractor, receiver-gated there.
|
|
13857
|
+
{ method: "StatusDescription", class: "Response", type: "xss", cwe: "CWE-81", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13722
13858
|
// `return Content(html, "text/html")` on a controller writes arg 0 to the
|
|
13723
13859
|
// response body unescaped (cognium-dev#275). Taint-gated on arg 0; the common
|
|
13724
13860
|
// `Content(constantString)` and the JSON/plain-text content types are
|
|
@@ -13733,6 +13869,12 @@ var DEFAULT_SINKS = [
|
|
|
13733
13869
|
// filter is the constructor argument.
|
|
13734
13870
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13735
13871
|
{ method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13872
|
+
// #336: a tainted `HttpClient.BaseAddress` gives the attacker the scheme,
|
|
13873
|
+
// host and port of every RELATIVE request made on that client, so the
|
|
13874
|
+
// request arguments can all be constant and the destination still be
|
|
13875
|
+
// attacker-chosen. Surfaced by the property-assignment synthesiser, which
|
|
13876
|
+
// class-gates it to a resolved `HttpClient`.
|
|
13877
|
+
{ method: "BaseAddress", class: "HttpClient", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13736
13878
|
// C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
|
|
13737
13879
|
// cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
|
|
13738
13880
|
// an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
|
|
@@ -15446,6 +15588,20 @@ function escapeRe(s) {
|
|
|
15446
15588
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15447
15589
|
}
|
|
15448
15590
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
15591
|
+
function argIsStreamExpression(call, position) {
|
|
15592
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
15593
|
+
if (!arg) return false;
|
|
15594
|
+
const expr = (arg.expression ?? "").trim();
|
|
15595
|
+
if (!expr) return false;
|
|
15596
|
+
return (
|
|
15597
|
+
// `new MemoryStream(...)`, `new FileStream(...)`, `new GZipStream(...)`
|
|
15598
|
+
/^new\s+[A-Za-z_][\w.]*Stream\s*\(/.test(expr) || // `tcp.GetStream()`, `resp.GetResponseStream()`, `file.OpenReadStream()`
|
|
15599
|
+
/\.\s*Get(?:Response)?Stream\s*\(/.test(expr) || /\.\s*OpenReadStream\s*\(/.test(expr) || // `wc.OpenRead(url)` returns a Stream (WebClient), as does `File.OpenRead`
|
|
15600
|
+
/\.\s*OpenRead\s*\(/.test(expr) || // `Request.Body`, `x.BaseStream`
|
|
15601
|
+
/\.\s*(?:Body|BaseStream)\s*$/.test(expr) || // a local whose name is stream-ish and is not a path string
|
|
15602
|
+
/^(?:[A-Za-z_]\w*\.)*[A-Za-z_]*[sS]tream\d*$/.test(expr)
|
|
15603
|
+
);
|
|
15604
|
+
}
|
|
15449
15605
|
function argIsStringLiteral(call, position) {
|
|
15450
15606
|
const arg = call.arguments.find((a) => a.position === position);
|
|
15451
15607
|
if (!arg) return false;
|
|
@@ -15683,6 +15839,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15683
15839
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
15684
15840
|
continue;
|
|
15685
15841
|
}
|
|
15842
|
+
if (pattern.safe_if_stream_arg_at !== void 0 && argIsStreamExpression(call, pattern.safe_if_stream_arg_at)) {
|
|
15843
|
+
continue;
|
|
15844
|
+
}
|
|
15686
15845
|
if (pattern.type === "command_injection") {
|
|
15687
15846
|
if (call.is_constructor) {
|
|
15688
15847
|
if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
|
|
@@ -16077,8 +16236,9 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
16077
16236
|
return false;
|
|
16078
16237
|
} else if (!call.receiver && !call.receiver_type) {
|
|
16079
16238
|
const target = call.resolution?.target;
|
|
16239
|
+
const normalisedTarget = language === "rust" ? target?.replace(/::/g, ".") : target;
|
|
16080
16240
|
const expectedTail = `${pattern.class}.${pattern.method}`;
|
|
16081
|
-
if (
|
|
16241
|
+
if (normalisedTarget && (normalisedTarget === expectedTail || normalisedTarget.endsWith("." + expectedTail))) {
|
|
16082
16242
|
} else {
|
|
16083
16243
|
return false;
|
|
16084
16244
|
}
|
|
@@ -4414,6 +4414,21 @@ function extractTypes(tree, cache, language) {
|
|
|
4414
4414
|
}
|
|
4415
4415
|
return extractJavaTypes(tree, cache);
|
|
4416
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;
|
|
4431
|
+
}
|
|
4417
4432
|
function extractCSharpTypes(tree, cache) {
|
|
4418
4433
|
const types = [];
|
|
4419
4434
|
const KINDS = {
|
|
@@ -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++) {
|
|
@@ -6019,6 +6031,13 @@ function extractCalls(tree, cache, language) {
|
|
|
6019
6031
|
function buildCSharpReceiverTypeMap(tree, cache) {
|
|
6020
6032
|
const map = /* @__PURE__ */ new Map();
|
|
6021
6033
|
const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
|
|
6034
|
+
for (const prm of getNodesFromCache(tree.rootNode, "parameter", cache)) {
|
|
6035
|
+
const nameNode = prm.childForFieldName("name");
|
|
6036
|
+
const typeNode = prm.childForFieldName("type");
|
|
6037
|
+
if (nameNode?.type !== "identifier" || !typeNode) continue;
|
|
6038
|
+
const t = getNodeText(typeNode);
|
|
6039
|
+
if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
|
|
6040
|
+
}
|
|
6022
6041
|
for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
|
|
6023
6042
|
const typeNode = vd.childForFieldName("type");
|
|
6024
6043
|
const declaredType = typeNode ? getNodeText(typeNode) : null;
|
|
@@ -6048,6 +6067,7 @@ function findFirstDescendant(node, type) {
|
|
|
6048
6067
|
}
|
|
6049
6068
|
return null;
|
|
6050
6069
|
}
|
|
6070
|
+
var CSHARP_RESPONSE_TYPES = /* @__PURE__ */ new Set(["HttpResponse", "HttpResponseBase"]);
|
|
6051
6071
|
var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
6052
6072
|
"ExecuteScalar",
|
|
6053
6073
|
"ExecuteReader",
|
|
@@ -6164,6 +6184,31 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6164
6184
|
});
|
|
6165
6185
|
continue;
|
|
6166
6186
|
}
|
|
6187
|
+
if (left?.type === "identifier" && asn.parent?.type === "initializer_expression") {
|
|
6188
|
+
const creation = asn.parent.parent;
|
|
6189
|
+
if (creation?.type !== "object_creation_expression") continue;
|
|
6190
|
+
const ctorType = creation.childForFieldName("type");
|
|
6191
|
+
const right2 = asn.childForFieldName("right");
|
|
6192
|
+
if (!ctorType || !right2) continue;
|
|
6193
|
+
const typeName = csharpBareName(ctorType);
|
|
6194
|
+
const rhsText2 = getNodeText(right2);
|
|
6195
|
+
calls.push({
|
|
6196
|
+
method_name: getNodeText(left),
|
|
6197
|
+
receiver: typeName,
|
|
6198
|
+
receiver_type: typeName,
|
|
6199
|
+
receiver_type_fqn: null,
|
|
6200
|
+
arguments: [{
|
|
6201
|
+
position: 0,
|
|
6202
|
+
expression: rhsText2,
|
|
6203
|
+
variable: right2.type === "identifier" ? rhsText2 : null,
|
|
6204
|
+
literal: CSHARP_LITERAL_NODE_TYPES.has(right2.type) ? rhsText2 : null,
|
|
6205
|
+
value: null
|
|
6206
|
+
}],
|
|
6207
|
+
location: { line: asn.startPosition.row + 1, column: asn.startPosition.column },
|
|
6208
|
+
in_method: findEnclosingMethod(asn)
|
|
6209
|
+
});
|
|
6210
|
+
continue;
|
|
6211
|
+
}
|
|
6167
6212
|
if (left?.type !== "member_access_expression") continue;
|
|
6168
6213
|
const nameNode = left.childForFieldName("name");
|
|
6169
6214
|
if (!nameNode) continue;
|
|
@@ -6173,6 +6218,14 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6173
6218
|
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6174
6219
|
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6175
6220
|
if (recvType !== "DirectorySearcher") continue;
|
|
6221
|
+
} else if (propName === "StatusDescription") {
|
|
6222
|
+
const recv = exprNode ? getNodeText(exprNode) : "";
|
|
6223
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6224
|
+
if (!/(^|\.)(Response|HttpResponse|Context\.Response)$/.test(recv) && !(recvType && CSHARP_RESPONSE_TYPES.has(recvType))) continue;
|
|
6225
|
+
} else if (propName === "BaseAddress") {
|
|
6226
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6227
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6228
|
+
if (recvType !== "HttpClient") continue;
|
|
6176
6229
|
} else if (propName !== "CommandText") {
|
|
6177
6230
|
continue;
|
|
6178
6231
|
}
|
|
@@ -6195,8 +6248,34 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6195
6248
|
in_method: findEnclosingMethod(asn)
|
|
6196
6249
|
});
|
|
6197
6250
|
}
|
|
6251
|
+
const casts = getNodesFromCache(tree.rootNode, "cast_expression", cache);
|
|
6252
|
+
for (const cast of casts) {
|
|
6253
|
+
const typeNode = cast.childForFieldName("type");
|
|
6254
|
+
const valueNode = cast.childForFieldName("value");
|
|
6255
|
+
if (!typeNode || !valueNode) continue;
|
|
6256
|
+
const typeName = csharpBareName(typeNode);
|
|
6257
|
+
if (!CSHARP_RAW_MARKUP_CAST_TYPES.has(typeName)) continue;
|
|
6258
|
+
const valueText = getNodeText(valueNode);
|
|
6259
|
+
calls.push({
|
|
6260
|
+
method_name: typeName,
|
|
6261
|
+
receiver: null,
|
|
6262
|
+
receiver_type: null,
|
|
6263
|
+
receiver_type_fqn: null,
|
|
6264
|
+
arguments: [{
|
|
6265
|
+
position: 0,
|
|
6266
|
+
expression: valueText,
|
|
6267
|
+
variable: valueNode.type === "identifier" ? valueText : null,
|
|
6268
|
+
literal: CSHARP_LITERAL_NODE_TYPES.has(valueNode.type) ? valueText : null,
|
|
6269
|
+
value: null
|
|
6270
|
+
}],
|
|
6271
|
+
location: { line: cast.startPosition.row + 1, column: cast.startPosition.column },
|
|
6272
|
+
in_method: findEnclosingMethod(cast),
|
|
6273
|
+
is_constructor: true
|
|
6274
|
+
});
|
|
6275
|
+
}
|
|
6198
6276
|
return calls;
|
|
6199
6277
|
}
|
|
6278
|
+
var CSHARP_RAW_MARKUP_CAST_TYPES = /* @__PURE__ */ new Set(["MarkupString"]);
|
|
6200
6279
|
function extractCSharpArguments(argsNode) {
|
|
6201
6280
|
const args2 = [];
|
|
6202
6281
|
let position = 0;
|
|
@@ -6216,12 +6295,22 @@ function extractCSharpArguments(argsNode) {
|
|
|
6216
6295
|
position: position++,
|
|
6217
6296
|
expression: text,
|
|
6218
6297
|
variable: expr?.type === "identifier" ? text : null,
|
|
6219
|
-
literal: expr
|
|
6298
|
+
literal: expr && CSHARP_LITERAL_NODE_TYPES.has(expr.type) ? text : null,
|
|
6220
6299
|
value: null
|
|
6221
6300
|
});
|
|
6222
6301
|
}
|
|
6223
6302
|
return args2;
|
|
6224
6303
|
}
|
|
6304
|
+
var CSHARP_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
6305
|
+
"string_literal",
|
|
6306
|
+
"verbatim_string_literal",
|
|
6307
|
+
"raw_string_literal",
|
|
6308
|
+
"character_literal",
|
|
6309
|
+
"integer_literal",
|
|
6310
|
+
"real_literal",
|
|
6311
|
+
"boolean_literal",
|
|
6312
|
+
"null_literal"
|
|
6313
|
+
]);
|
|
6225
6314
|
function extractJavaScriptCalls(tree, cache) {
|
|
6226
6315
|
const calls = [];
|
|
6227
6316
|
const context = buildJSResolutionContext(tree, cache);
|
|
@@ -11005,6 +11094,23 @@ var DEFAULT_SOURCES = [
|
|
|
11005
11094
|
{ method: "next", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11006
11095
|
{ method: "nextInt", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11007
11096
|
// Database result sources
|
|
11097
|
+
// C# I/O, network and database reads (cognium-dev#502). Mirrors the Java
|
|
11098
|
+
// rows above one-for-one, including the severity split: console/file I/O is
|
|
11099
|
+
// `io_input` at high, a database row read is `db_input` at medium. There is
|
|
11100
|
+
// no separate confidence tier — `type` + `severity` IS the tier, exactly as
|
|
11101
|
+
// for `BufferedReader.readLine` vs `ResultSet.getString`.
|
|
11102
|
+
{ method: "ReadLine", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11103
|
+
{ method: "ReadLineAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11104
|
+
{ method: "ReadToEnd", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11105
|
+
{ method: "ReadToEndAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11106
|
+
// Socket and HTTP-client reads. `GetStream` / `AcceptTcpClient` /
|
|
11107
|
+
// `OpenRead` return the channel; the read off it is covered by the
|
|
11108
|
+
// StreamReader rows above, and seeding the channel too lets a flow form when
|
|
11109
|
+
// the read is inlined.
|
|
11110
|
+
{ method: "GetStream", class: "TcpClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11111
|
+
{ method: "AcceptTcpClient", class: "TcpListener", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11112
|
+
{ method: "OpenRead", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11113
|
+
{ method: "DownloadString", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11008
11114
|
{ method: "getString", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11009
11115
|
{ method: "getObject", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11010
11116
|
{ method: "getInt", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
@@ -13245,9 +13351,14 @@ var DEFAULT_SINKS = [
|
|
|
13245
13351
|
{ method: "deserialize", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13246
13352
|
{ method: "from_str", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13247
13353
|
{ method: "from_str", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13248
|
-
//
|
|
13249
|
-
|
|
13250
|
-
|
|
13354
|
+
// #294/#484: no classless `from_str` / `from_slice` deserialization sink.
|
|
13355
|
+
// Those rows matched EVERY `T::from_str` / `from_slice` — `u32::from_str`,
|
|
13356
|
+
// `Url::from_str`, `PublicKey::from_slice` — i.e. ordinary `FromStr` / byte
|
|
13357
|
+
// parsing, which cannot instantiate an attacker-chosen type. Removing them
|
|
13358
|
+
// was previously blocked because the class-scoped rows below could not match
|
|
13359
|
+
// the IMPORTED call form (`use serde_json::from_str; from_str(&body)`); the
|
|
13360
|
+
// `::` path-separator fix in taint-matcher.ts makes them match, so the
|
|
13361
|
+
// classless rows are no longer load-bearing.
|
|
13251
13362
|
// Rust XSS (actix-web, rocket, axum response body)
|
|
13252
13363
|
{ method: "body", class: "HttpResponseBuilder", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
13253
13364
|
{ method: "body", class: "HttpResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -13564,8 +13675,12 @@ var DEFAULT_SINKS = [
|
|
|
13564
13675
|
{ method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13565
13676
|
{ method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13566
13677
|
{ method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13567
|
-
|
|
13568
|
-
|
|
13678
|
+
// #502: the `(Stream)` overload takes no path — `new StreamReader(tcp.GetStream())`
|
|
13679
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13680
|
+
{ method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13681
|
+
// #502: the `(Stream)` overload takes no path — `new StreamWriter(tcp.GetStream())`
|
|
13682
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
13683
|
+
{ method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
13569
13684
|
// Remaining System.IO.File path APIs — class-scoped so they don't collide
|
|
13570
13685
|
// with unrelated methods. Copy/Move take a source AND destination path (both
|
|
13571
13686
|
// attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
|
|
@@ -13609,6 +13724,23 @@ var DEFAULT_SINKS = [
|
|
|
13609
13724
|
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
13610
13725
|
// infrastructure. Ctor arg 0, taint-gated.
|
|
13611
13726
|
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13727
|
+
// C# code injection — CodeDOM runtime compilation (CWE-94), cognium-dev#503
|
|
13728
|
+
// part 2. `provider.CompileAssemblyFromSource(parameters, source)` compiles
|
|
13729
|
+
// and can then execute attacker-supplied C#, so the source argument is the
|
|
13730
|
+
// injection point. The signature is
|
|
13731
|
+
// `CompileAssemblyFromSource(CompilerParameters, params string[] sources)`,
|
|
13732
|
+
// so the sources begin at position 1 — position 0 is the options object.
|
|
13733
|
+
//
|
|
13734
|
+
// These rows are only useful together with the #530 StringBuilder
|
|
13735
|
+
// propagation fix: the Juliet CWE-94 corpus accumulates the compiled source
|
|
13736
|
+
// through `sb.Append(...)` and reads it back with `ToString()`, so before
|
|
13737
|
+
// that fix the sink registered but no taint ever reached it.
|
|
13738
|
+
{ method: "CompileAssemblyFromSource", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13739
|
+
{ method: "CompileAssemblyFromSourceBatch", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
13740
|
+
// Roslyn's equivalent pair. `ParseText` turns text into a syntax tree and
|
|
13741
|
+
// `Create` compiles it; either is the point where attacker text becomes code.
|
|
13742
|
+
{ method: "ParseText", class: "CSharpSyntaxTree", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13743
|
+
{ method: "ParseText", class: "SyntaxFactory", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13612
13744
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13613
13745
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13614
13746
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13653,6 +13785,10 @@ var DEFAULT_SINKS = [
|
|
|
13653
13785
|
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13654
13786
|
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13655
13787
|
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13788
|
+
// #503 (part 1): `Response.StatusDescription = "Bad " + data` writes
|
|
13789
|
+
// attacker text into the HTTP status line (CWE-81). Surfaced by the
|
|
13790
|
+
// property-assignment synthesiser in the C# extractor, receiver-gated there.
|
|
13791
|
+
{ method: "StatusDescription", class: "Response", type: "xss", cwe: "CWE-81", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13656
13792
|
// `return Content(html, "text/html")` on a controller writes arg 0 to the
|
|
13657
13793
|
// response body unescaped (cognium-dev#275). Taint-gated on arg 0; the common
|
|
13658
13794
|
// `Content(constantString)` and the JSON/plain-text content types are
|
|
@@ -13667,6 +13803,12 @@ var DEFAULT_SINKS = [
|
|
|
13667
13803
|
// filter is the constructor argument.
|
|
13668
13804
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13669
13805
|
{ method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13806
|
+
// #336: a tainted `HttpClient.BaseAddress` gives the attacker the scheme,
|
|
13807
|
+
// host and port of every RELATIVE request made on that client, so the
|
|
13808
|
+
// request arguments can all be constant and the destination still be
|
|
13809
|
+
// attacker-chosen. Surfaced by the property-assignment synthesiser, which
|
|
13810
|
+
// class-gates it to a resolved `HttpClient`.
|
|
13811
|
+
{ method: "BaseAddress", class: "HttpClient", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13670
13812
|
// C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
|
|
13671
13813
|
// cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
|
|
13672
13814
|
// an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
|
|
@@ -15380,6 +15522,20 @@ function escapeRe(s) {
|
|
|
15380
15522
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15381
15523
|
}
|
|
15382
15524
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
15525
|
+
function argIsStreamExpression(call, position) {
|
|
15526
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
15527
|
+
if (!arg) return false;
|
|
15528
|
+
const expr = (arg.expression ?? "").trim();
|
|
15529
|
+
if (!expr) return false;
|
|
15530
|
+
return (
|
|
15531
|
+
// `new MemoryStream(...)`, `new FileStream(...)`, `new GZipStream(...)`
|
|
15532
|
+
/^new\s+[A-Za-z_][\w.]*Stream\s*\(/.test(expr) || // `tcp.GetStream()`, `resp.GetResponseStream()`, `file.OpenReadStream()`
|
|
15533
|
+
/\.\s*Get(?:Response)?Stream\s*\(/.test(expr) || /\.\s*OpenReadStream\s*\(/.test(expr) || // `wc.OpenRead(url)` returns a Stream (WebClient), as does `File.OpenRead`
|
|
15534
|
+
/\.\s*OpenRead\s*\(/.test(expr) || // `Request.Body`, `x.BaseStream`
|
|
15535
|
+
/\.\s*(?:Body|BaseStream)\s*$/.test(expr) || // a local whose name is stream-ish and is not a path string
|
|
15536
|
+
/^(?:[A-Za-z_]\w*\.)*[A-Za-z_]*[sS]tream\d*$/.test(expr)
|
|
15537
|
+
);
|
|
15538
|
+
}
|
|
15383
15539
|
function argIsStringLiteral(call, position) {
|
|
15384
15540
|
const arg = call.arguments.find((a) => a.position === position);
|
|
15385
15541
|
if (!arg) return false;
|
|
@@ -15617,6 +15773,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15617
15773
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
15618
15774
|
continue;
|
|
15619
15775
|
}
|
|
15776
|
+
if (pattern.safe_if_stream_arg_at !== void 0 && argIsStreamExpression(call, pattern.safe_if_stream_arg_at)) {
|
|
15777
|
+
continue;
|
|
15778
|
+
}
|
|
15620
15779
|
if (pattern.type === "command_injection") {
|
|
15621
15780
|
if (call.is_constructor) {
|
|
15622
15781
|
if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
|
|
@@ -16011,8 +16170,9 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
16011
16170
|
return false;
|
|
16012
16171
|
} else if (!call.receiver && !call.receiver_type) {
|
|
16013
16172
|
const target = call.resolution?.target;
|
|
16173
|
+
const normalisedTarget = language === "rust" ? target?.replace(/::/g, ".") : target;
|
|
16014
16174
|
const expectedTail = `${pattern.class}.${pattern.method}`;
|
|
16015
|
-
if (
|
|
16175
|
+
if (normalisedTarget && (normalisedTarget === expectedTail || normalisedTarget.endsWith("." + expectedTail))) {
|
|
16016
16176
|
} else {
|
|
16017
16177
|
return false;
|
|
16018
16178
|
}
|