circle-ir 4.7.2 → 4.9.7
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 +34 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +1 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +37 -3
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts +2 -0
- package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-deserialization-config-pass.js +45 -2
- package/dist/analysis/passes/insecure-deserialization-config-pass.js.map +1 -1
- package/dist/analysis/passes/jwt-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/jwt-verify-disabled-pass.js +48 -21
- package/dist/analysis/passes/jwt-verify-disabled-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 +131 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.js +34 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.js +32 -19
- package/dist/analysis/passes/weak-crypto-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +293 -25
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +14 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +660 -109
- package/dist/core/circle-ir-core.cjs +411 -68
- package/dist/core/circle-ir-core.js +411 -68
- package/dist/core/extractors/calls.js +17 -3
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/cfg.d.ts.map +1 -1
- package/dist/core/extractors/cfg.js +140 -40
- package/dist/core/extractors/cfg.js.map +1 -1
- package/dist/core/extractors/types.js +77 -2
- package/dist/core/extractors/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -4375,6 +4375,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4375
4375
|
const nameNode = node.childForFieldName("name");
|
|
4376
4376
|
const body2 = node.childForFieldName("body");
|
|
4377
4377
|
const methods = [];
|
|
4378
|
+
const fields = extractCSharpFields(body2);
|
|
4378
4379
|
if (body2) {
|
|
4379
4380
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4380
4381
|
const m = body2.child(i2);
|
|
@@ -4392,7 +4393,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4392
4393
|
parameters.push({
|
|
4393
4394
|
name: getNodeText(pName),
|
|
4394
4395
|
type: pType ? getNodeText(pType) : null,
|
|
4395
|
-
annotations:
|
|
4396
|
+
annotations: extractCSharpParamAnnotations(pnode),
|
|
4396
4397
|
line: pnode.startPosition.row + 1
|
|
4397
4398
|
});
|
|
4398
4399
|
}
|
|
@@ -4418,7 +4419,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4418
4419
|
implements: [],
|
|
4419
4420
|
annotations: [],
|
|
4420
4421
|
methods,
|
|
4421
|
-
fields
|
|
4422
|
+
fields,
|
|
4422
4423
|
start_line: node.startPosition.row + 1,
|
|
4423
4424
|
end_line: node.endPosition.row + 1
|
|
4424
4425
|
});
|
|
@@ -4426,6 +4427,59 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4426
4427
|
}
|
|
4427
4428
|
return types;
|
|
4428
4429
|
}
|
|
4430
|
+
function extractCSharpFields(body2) {
|
|
4431
|
+
const fields = [];
|
|
4432
|
+
if (!body2) return fields;
|
|
4433
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4434
|
+
const c = body2.child(i2);
|
|
4435
|
+
if (!c) continue;
|
|
4436
|
+
if (c.type === "field_declaration") {
|
|
4437
|
+
let varDecl = null;
|
|
4438
|
+
for (let k = 0; k < c.childCount; k++) {
|
|
4439
|
+
const cc = c.child(k);
|
|
4440
|
+
if (cc?.type === "variable_declaration") {
|
|
4441
|
+
varDecl = cc;
|
|
4442
|
+
break;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
if (!varDecl) continue;
|
|
4446
|
+
const typeNode = varDecl.childForFieldName("type");
|
|
4447
|
+
const type = typeNode ? getNodeText(typeNode) : null;
|
|
4448
|
+
const modifiers = extractCSharpModifiers(c);
|
|
4449
|
+
for (let k = 0; k < varDecl.childCount; k++) {
|
|
4450
|
+
const decl = varDecl.child(k);
|
|
4451
|
+
if (decl?.type !== "variable_declarator") continue;
|
|
4452
|
+
const nameNode = decl.childForFieldName("name");
|
|
4453
|
+
fields.push({ name: nameNode ? getNodeText(nameNode) : "unknown", type, modifiers, annotations: [] });
|
|
4454
|
+
}
|
|
4455
|
+
} else if (c.type === "property_declaration") {
|
|
4456
|
+
const nameNode = c.childForFieldName("name");
|
|
4457
|
+
if (!nameNode) continue;
|
|
4458
|
+
const typeNode = c.childForFieldName("type");
|
|
4459
|
+
fields.push({
|
|
4460
|
+
name: getNodeText(nameNode),
|
|
4461
|
+
type: typeNode ? getNodeText(typeNode) : null,
|
|
4462
|
+
modifiers: extractCSharpModifiers(c),
|
|
4463
|
+
annotations: []
|
|
4464
|
+
});
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
return fields;
|
|
4468
|
+
}
|
|
4469
|
+
function extractCSharpParamAnnotations(param) {
|
|
4470
|
+
const out2 = [];
|
|
4471
|
+
for (let i2 = 0; i2 < param.childCount; i2++) {
|
|
4472
|
+
const list = param.child(i2);
|
|
4473
|
+
if (list?.type !== "attribute_list") continue;
|
|
4474
|
+
for (let j = 0; j < list.childCount; j++) {
|
|
4475
|
+
const attr = list.child(j);
|
|
4476
|
+
if (attr?.type !== "attribute") continue;
|
|
4477
|
+
const name2 = attr.childForFieldName("name");
|
|
4478
|
+
if (name2) out2.push(getNodeText(name2));
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
return out2;
|
|
4482
|
+
}
|
|
4429
4483
|
function extractCSharpModifiers(node) {
|
|
4430
4484
|
const mods = [];
|
|
4431
4485
|
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
@@ -6000,13 +6054,21 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6000
6054
|
const left = asn.childForFieldName("left");
|
|
6001
6055
|
if (left?.type !== "member_access_expression") continue;
|
|
6002
6056
|
const nameNode = left.childForFieldName("name");
|
|
6003
|
-
if (!nameNode
|
|
6057
|
+
if (!nameNode) continue;
|
|
6058
|
+
const propName = getNodeText(nameNode);
|
|
6059
|
+
const exprNode = left.childForFieldName("expression");
|
|
6060
|
+
if (propName === "Filter") {
|
|
6061
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6062
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6063
|
+
if (recvType !== "DirectorySearcher") continue;
|
|
6064
|
+
} else if (propName !== "CommandText") {
|
|
6065
|
+
continue;
|
|
6066
|
+
}
|
|
6004
6067
|
const right = asn.childForFieldName("right");
|
|
6005
6068
|
if (!right) continue;
|
|
6006
6069
|
const rhsText = getNodeText(right);
|
|
6007
|
-
const exprNode = left.childForFieldName("expression");
|
|
6008
6070
|
calls.push({
|
|
6009
|
-
method_name:
|
|
6071
|
+
method_name: propName,
|
|
6010
6072
|
receiver: exprNode ? getNodeText(exprNode) : null,
|
|
6011
6073
|
receiver_type: null,
|
|
6012
6074
|
receiver_type_fqn: null,
|
|
@@ -8572,6 +8634,9 @@ function buildCFG(tree, language, cache) {
|
|
|
8572
8634
|
if (effectiveLanguage === "go") {
|
|
8573
8635
|
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8574
8636
|
}
|
|
8637
|
+
if (effectiveLanguage === "csharp") {
|
|
8638
|
+
return buildCSharpCFG(tree, blockIdCounter, cache);
|
|
8639
|
+
}
|
|
8575
8640
|
if (isJavaScript) {
|
|
8576
8641
|
const functions = [
|
|
8577
8642
|
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
@@ -8584,7 +8649,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8584
8649
|
const body2 = func2.childForFieldName("body");
|
|
8585
8650
|
if (!body2) continue;
|
|
8586
8651
|
if (body2.type === "statement_block") {
|
|
8587
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8652
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "js");
|
|
8588
8653
|
allBlocks.push(...blocks);
|
|
8589
8654
|
allEdges.push(...edges);
|
|
8590
8655
|
blockIdCounter = nextId;
|
|
@@ -8606,7 +8671,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8606
8671
|
for (const method of methods) {
|
|
8607
8672
|
const body2 = method.childForFieldName("body");
|
|
8608
8673
|
if (!body2) continue;
|
|
8609
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8674
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8610
8675
|
allBlocks.push(...blocks);
|
|
8611
8676
|
allEdges.push(...edges);
|
|
8612
8677
|
blockIdCounter = nextId;
|
|
@@ -8614,7 +8679,28 @@ function buildCFG(tree, language, cache) {
|
|
|
8614
8679
|
}
|
|
8615
8680
|
return { blocks: allBlocks, edges: allEdges };
|
|
8616
8681
|
}
|
|
8617
|
-
function
|
|
8682
|
+
function buildCSharpCFG(tree, blockIdCounter, cache) {
|
|
8683
|
+
const allBlocks = [];
|
|
8684
|
+
const allEdges = [];
|
|
8685
|
+
const containers = [
|
|
8686
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8687
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
|
|
8688
|
+
...getNodesFromCache(tree.rootNode, "destructor_declaration", cache),
|
|
8689
|
+
...getNodesFromCache(tree.rootNode, "operator_declaration", cache),
|
|
8690
|
+
...getNodesFromCache(tree.rootNode, "local_function_statement", cache),
|
|
8691
|
+
...getNodesFromCache(tree.rootNode, "accessor_declaration", cache)
|
|
8692
|
+
];
|
|
8693
|
+
for (const container of containers) {
|
|
8694
|
+
const body2 = container.childForFieldName("body");
|
|
8695
|
+
if (!body2 || body2.type !== "block") continue;
|
|
8696
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "csharp");
|
|
8697
|
+
allBlocks.push(...blocks);
|
|
8698
|
+
allEdges.push(...edges);
|
|
8699
|
+
blockIdCounter = nextId;
|
|
8700
|
+
}
|
|
8701
|
+
return { blocks: allBlocks, edges: allEdges };
|
|
8702
|
+
}
|
|
8703
|
+
function buildMethodCFG(body2, startId, dialect) {
|
|
8618
8704
|
const blocks = [];
|
|
8619
8705
|
const edges = [];
|
|
8620
8706
|
let currentId = startId;
|
|
@@ -8625,7 +8711,7 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8625
8711
|
end_line: body2.startPosition.row + 1
|
|
8626
8712
|
};
|
|
8627
8713
|
blocks.push(entryBlock);
|
|
8628
|
-
const result = processStatements(body2, currentId, blocks, edges,
|
|
8714
|
+
const result = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8629
8715
|
currentId = result.nextId;
|
|
8630
8716
|
if (result.entryId !== -1) {
|
|
8631
8717
|
edges.push({
|
|
@@ -8657,15 +8743,15 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8657
8743
|
}
|
|
8658
8744
|
return { blocks, edges, nextId: currentId };
|
|
8659
8745
|
}
|
|
8660
|
-
function processStatements(container, startId, blocks, edges,
|
|
8746
|
+
function processStatements(container, startId, blocks, edges, dialect) {
|
|
8661
8747
|
let currentId = startId;
|
|
8662
8748
|
let firstBlockId = -1;
|
|
8663
8749
|
let lastExitIds = [];
|
|
8664
8750
|
for (let i2 = 0; i2 < container.childCount; i2++) {
|
|
8665
8751
|
const stmt = container.child(i2);
|
|
8666
8752
|
if (!stmt) continue;
|
|
8667
|
-
if (!isStatement(stmt,
|
|
8668
|
-
const result = processStatement(stmt, currentId, blocks, edges,
|
|
8753
|
+
if (!isStatement(stmt, dialect)) continue;
|
|
8754
|
+
const result = processStatement(stmt, currentId, blocks, edges, dialect);
|
|
8669
8755
|
currentId = result.nextId;
|
|
8670
8756
|
if (firstBlockId === -1) {
|
|
8671
8757
|
firstBlockId = result.entryId;
|
|
@@ -8686,31 +8772,49 @@ function processStatements(container, startId, blocks, edges, isJavaScript) {
|
|
|
8686
8772
|
nextId: currentId
|
|
8687
8773
|
};
|
|
8688
8774
|
}
|
|
8689
|
-
function processStatement(stmt, startId, blocks, edges,
|
|
8775
|
+
function processStatement(stmt, startId, blocks, edges, dialect) {
|
|
8690
8776
|
switch (stmt.type) {
|
|
8691
8777
|
case "if_statement":
|
|
8692
|
-
return processIfStatement(stmt, startId, blocks, edges,
|
|
8778
|
+
return processIfStatement(stmt, startId, blocks, edges, dialect);
|
|
8693
8779
|
case "for_statement":
|
|
8694
8780
|
case "enhanced_for_statement":
|
|
8695
8781
|
case "for_in_statement":
|
|
8696
8782
|
case "for_of_statement":
|
|
8697
|
-
|
|
8783
|
+
case "foreach_statement":
|
|
8784
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8698
8785
|
case "while_statement":
|
|
8699
|
-
return processWhileStatement(stmt, startId, blocks, edges,
|
|
8786
|
+
return processWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8700
8787
|
case "do_statement":
|
|
8701
|
-
return processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8788
|
+
return processDoWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8702
8789
|
case "try_statement":
|
|
8703
|
-
return processTryStatement(stmt, startId, blocks, edges,
|
|
8790
|
+
return processTryStatement(stmt, startId, blocks, edges, dialect);
|
|
8704
8791
|
case "switch_expression":
|
|
8705
8792
|
case "switch_statement":
|
|
8706
|
-
return processSwitchStatement(stmt, startId, blocks, edges,
|
|
8793
|
+
return processSwitchStatement(stmt, startId, blocks, edges, dialect);
|
|
8794
|
+
// C# scoped blocks add no branching — flow through the inner block so its
|
|
8795
|
+
// nested statements are still captured.
|
|
8796
|
+
case "using_statement":
|
|
8797
|
+
case "lock_statement":
|
|
8798
|
+
case "checked_statement":
|
|
8799
|
+
case "unsafe_statement": {
|
|
8800
|
+
const inner = stmt.childForFieldName("body") ?? lastBlockChild(stmt);
|
|
8801
|
+
if (inner) return processStatement(inner, startId, blocks, edges, dialect);
|
|
8802
|
+
return processSimpleStatement(stmt, startId, blocks);
|
|
8803
|
+
}
|
|
8707
8804
|
case "block":
|
|
8708
8805
|
case "statement_block":
|
|
8709
|
-
return processStatements(stmt, startId, blocks, edges,
|
|
8806
|
+
return processStatements(stmt, startId, blocks, edges, dialect);
|
|
8710
8807
|
default:
|
|
8711
8808
|
return processSimpleStatement(stmt, startId, blocks);
|
|
8712
8809
|
}
|
|
8713
8810
|
}
|
|
8811
|
+
function lastBlockChild(node) {
|
|
8812
|
+
for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
|
|
8813
|
+
const c = node.child(i2);
|
|
8814
|
+
if (c && c.type === "block") return c;
|
|
8815
|
+
}
|
|
8816
|
+
return null;
|
|
8817
|
+
}
|
|
8714
8818
|
function processSimpleStatement(stmt, startId, blocks) {
|
|
8715
8819
|
const block = {
|
|
8716
8820
|
id: startId,
|
|
@@ -8725,7 +8829,7 @@ function processSimpleStatement(stmt, startId, blocks) {
|
|
|
8725
8829
|
nextId: startId + 1
|
|
8726
8830
|
};
|
|
8727
8831
|
}
|
|
8728
|
-
function processIfStatement(stmt, startId, blocks, edges,
|
|
8832
|
+
function processIfStatement(stmt, startId, blocks, edges, dialect) {
|
|
8729
8833
|
let currentId = startId;
|
|
8730
8834
|
const condBlock = {
|
|
8731
8835
|
id: currentId++,
|
|
@@ -8737,7 +8841,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8737
8841
|
const exitIds = [];
|
|
8738
8842
|
const consequence = stmt.childForFieldName("consequence");
|
|
8739
8843
|
if (consequence) {
|
|
8740
|
-
const thenResult = processStatement(consequence, currentId, blocks, edges,
|
|
8844
|
+
const thenResult = processStatement(consequence, currentId, blocks, edges, dialect);
|
|
8741
8845
|
currentId = thenResult.nextId;
|
|
8742
8846
|
edges.push({
|
|
8743
8847
|
from: condBlock.id,
|
|
@@ -8748,7 +8852,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8748
8852
|
}
|
|
8749
8853
|
const alternative = stmt.childForFieldName("alternative");
|
|
8750
8854
|
if (alternative) {
|
|
8751
|
-
const elseResult = processStatement(alternative, currentId, blocks, edges,
|
|
8855
|
+
const elseResult = processStatement(alternative, currentId, blocks, edges, dialect);
|
|
8752
8856
|
currentId = elseResult.nextId;
|
|
8753
8857
|
edges.push({
|
|
8754
8858
|
from: condBlock.id,
|
|
@@ -8765,7 +8869,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8765
8869
|
nextId: currentId
|
|
8766
8870
|
};
|
|
8767
8871
|
}
|
|
8768
|
-
function processForStatement(stmt, startId, blocks, edges,
|
|
8872
|
+
function processForStatement(stmt, startId, blocks, edges, dialect) {
|
|
8769
8873
|
let currentId = startId;
|
|
8770
8874
|
const loopBlock = {
|
|
8771
8875
|
id: currentId++,
|
|
@@ -8776,7 +8880,7 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8776
8880
|
blocks.push(loopBlock);
|
|
8777
8881
|
const body2 = stmt.childForFieldName("body");
|
|
8778
8882
|
if (body2) {
|
|
8779
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8883
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8780
8884
|
currentId = bodyResult.nextId;
|
|
8781
8885
|
edges.push({
|
|
8782
8886
|
from: loopBlock.id,
|
|
@@ -8798,16 +8902,16 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8798
8902
|
nextId: currentId
|
|
8799
8903
|
};
|
|
8800
8904
|
}
|
|
8801
|
-
function processWhileStatement(stmt, startId, blocks, edges,
|
|
8802
|
-
return processForStatement(stmt, startId, blocks, edges,
|
|
8905
|
+
function processWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8906
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8803
8907
|
}
|
|
8804
|
-
function processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8908
|
+
function processDoWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8805
8909
|
let currentId = startId;
|
|
8806
8910
|
const body2 = stmt.childForFieldName("body");
|
|
8807
8911
|
let bodyEntryId = currentId;
|
|
8808
8912
|
let bodyExitIds = [];
|
|
8809
8913
|
if (body2) {
|
|
8810
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8914
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8811
8915
|
currentId = bodyResult.nextId;
|
|
8812
8916
|
bodyEntryId = bodyResult.entryId;
|
|
8813
8917
|
bodyExitIds = bodyResult.exitIds;
|
|
@@ -8837,13 +8941,13 @@ function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8837
8941
|
nextId: currentId
|
|
8838
8942
|
};
|
|
8839
8943
|
}
|
|
8840
|
-
function processTryStatement(stmt, startId, blocks, edges,
|
|
8944
|
+
function processTryStatement(stmt, startId, blocks, edges, dialect) {
|
|
8841
8945
|
let currentId = startId;
|
|
8842
8946
|
const exitIds = [];
|
|
8843
8947
|
const body2 = stmt.childForFieldName("body");
|
|
8844
8948
|
let tryEntryId = -1;
|
|
8845
8949
|
if (body2) {
|
|
8846
|
-
const bodyResult = processStatements(body2, currentId, blocks, edges,
|
|
8950
|
+
const bodyResult = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8847
8951
|
currentId = bodyResult.nextId;
|
|
8848
8952
|
tryEntryId = bodyResult.entryId;
|
|
8849
8953
|
exitIds.push(...bodyResult.exitIds);
|
|
@@ -8853,7 +8957,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8853
8957
|
if (child?.type === "catch_clause") {
|
|
8854
8958
|
const catchBody = child.childForFieldName("body");
|
|
8855
8959
|
if (catchBody) {
|
|
8856
|
-
const catchResult = processStatements(catchBody, currentId, blocks, edges,
|
|
8960
|
+
const catchResult = processStatements(catchBody, currentId, blocks, edges, dialect);
|
|
8857
8961
|
currentId = catchResult.nextId;
|
|
8858
8962
|
if (tryEntryId !== -1) {
|
|
8859
8963
|
edges.push({
|
|
@@ -8866,9 +8970,18 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8866
8970
|
}
|
|
8867
8971
|
}
|
|
8868
8972
|
}
|
|
8869
|
-
|
|
8973
|
+
let finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
|
|
8974
|
+
if (!finallyClause && dialect === "csharp") {
|
|
8975
|
+
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
8976
|
+
const child = stmt.child(i2);
|
|
8977
|
+
if (child?.type === "finally_clause") {
|
|
8978
|
+
finallyClause = lastBlockChild(child);
|
|
8979
|
+
break;
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8870
8983
|
if (finallyClause) {
|
|
8871
|
-
const finallyResult = processStatements(finallyClause, currentId, blocks, edges,
|
|
8984
|
+
const finallyResult = processStatements(finallyClause, currentId, blocks, edges, dialect);
|
|
8872
8985
|
currentId = finallyResult.nextId;
|
|
8873
8986
|
for (const exitId of exitIds) {
|
|
8874
8987
|
edges.push({
|
|
@@ -8889,7 +9002,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8889
9002
|
nextId: currentId
|
|
8890
9003
|
};
|
|
8891
9004
|
}
|
|
8892
|
-
function processSwitchStatement(stmt, startId, blocks, edges,
|
|
9005
|
+
function processSwitchStatement(stmt, startId, blocks, edges, dialect) {
|
|
8893
9006
|
let currentId = startId;
|
|
8894
9007
|
const switchBlock = {
|
|
8895
9008
|
id: currentId++,
|
|
@@ -8901,11 +9014,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8901
9014
|
const exitIds = [];
|
|
8902
9015
|
const body2 = stmt.childForFieldName("body");
|
|
8903
9016
|
if (body2) {
|
|
8904
|
-
const caseTypes =
|
|
9017
|
+
const caseTypes = dialect === "js" ? ["switch_case", "switch_default"] : dialect === "csharp" ? ["switch_section"] : ["switch_block_statement_group", "switch_rule"];
|
|
8905
9018
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
8906
9019
|
const child = body2.child(i2);
|
|
8907
9020
|
if (child && caseTypes.includes(child.type)) {
|
|
8908
|
-
const caseResult = processStatements(child, currentId, blocks, edges,
|
|
9021
|
+
const caseResult = processStatements(child, currentId, blocks, edges, dialect);
|
|
8909
9022
|
currentId = caseResult.nextId;
|
|
8910
9023
|
if (caseResult.entryId !== -1) {
|
|
8911
9024
|
edges.push({
|
|
@@ -8935,7 +9048,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8935
9048
|
for (const func2 of functions) {
|
|
8936
9049
|
const body2 = func2.childForFieldName("body");
|
|
8937
9050
|
if (!body2) continue;
|
|
8938
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9051
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8939
9052
|
allBlocks.push(...blocks);
|
|
8940
9053
|
allEdges.push(...edges);
|
|
8941
9054
|
blockIdCounter = nextId;
|
|
@@ -8958,7 +9071,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8958
9071
|
let lastExitIds = [];
|
|
8959
9072
|
let firstBlockId = -1;
|
|
8960
9073
|
for (const stmt of topLevelStatements) {
|
|
8961
|
-
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges,
|
|
9074
|
+
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, "java");
|
|
8962
9075
|
blockIdCounter = result.nextId;
|
|
8963
9076
|
if (firstBlockId === -1) {
|
|
8964
9077
|
firstBlockId = result.entryId;
|
|
@@ -9002,7 +9115,8 @@ function isBashStatement(node) {
|
|
|
9002
9115
|
]);
|
|
9003
9116
|
return bashStatementTypes.has(node.type);
|
|
9004
9117
|
}
|
|
9005
|
-
function isStatement(node,
|
|
9118
|
+
function isStatement(node, dialect) {
|
|
9119
|
+
if (dialect === "csharp") return csharpStatementTypes.has(node.type);
|
|
9006
9120
|
const javaStatementTypes = /* @__PURE__ */ new Set([
|
|
9007
9121
|
"local_variable_declaration",
|
|
9008
9122
|
"expression_statement",
|
|
@@ -9046,8 +9160,30 @@ function isStatement(node, isJavaScript) {
|
|
|
9046
9160
|
"export_statement",
|
|
9047
9161
|
"import_statement"
|
|
9048
9162
|
]);
|
|
9049
|
-
return
|
|
9050
|
-
}
|
|
9163
|
+
return dialect === "js" ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
9164
|
+
}
|
|
9165
|
+
var csharpStatementTypes = /* @__PURE__ */ new Set([
|
|
9166
|
+
"local_declaration_statement",
|
|
9167
|
+
"expression_statement",
|
|
9168
|
+
"if_statement",
|
|
9169
|
+
"for_statement",
|
|
9170
|
+
"foreach_statement",
|
|
9171
|
+
"while_statement",
|
|
9172
|
+
"do_statement",
|
|
9173
|
+
"try_statement",
|
|
9174
|
+
"switch_statement",
|
|
9175
|
+
"return_statement",
|
|
9176
|
+
"throw_statement",
|
|
9177
|
+
"break_statement",
|
|
9178
|
+
"continue_statement",
|
|
9179
|
+
"using_statement",
|
|
9180
|
+
"lock_statement",
|
|
9181
|
+
"checked_statement",
|
|
9182
|
+
"unsafe_statement",
|
|
9183
|
+
"yield_statement",
|
|
9184
|
+
"goto_statement",
|
|
9185
|
+
"block"
|
|
9186
|
+
]);
|
|
9051
9187
|
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
9052
9188
|
const allBlocks = [];
|
|
9053
9189
|
const allEdges = [];
|
|
@@ -9058,7 +9194,7 @@ function buildGoCFG(tree, blockIdCounter, cache) {
|
|
|
9058
9194
|
for (const func2 of functions) {
|
|
9059
9195
|
const body2 = func2.childForFieldName("body");
|
|
9060
9196
|
if (!body2 || body2.type !== "block") continue;
|
|
9061
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9197
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
9062
9198
|
allBlocks.push(...blocks);
|
|
9063
9199
|
allEdges.push(...edges);
|
|
9064
9200
|
blockIdCounter = nextId;
|
|
@@ -13789,6 +13925,11 @@ var DEFAULT_SINKS = [
|
|
|
13789
13925
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13790
13926
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13791
13927
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13928
|
+
// `DirectorySearcher.Filter = "(uid=" + x + ")"` — LDAP injection (cognium-ai#272).
|
|
13929
|
+
// Emitted as a synthetic call by extractCSharpCalls ONLY when the receiver
|
|
13930
|
+
// resolves to a DirectorySearcher, so this classless entry never over-matches
|
|
13931
|
+
// other `.Filter =` assignments (DataView/BindingSource/collection filters).
|
|
13932
|
+
{ method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13792
13933
|
// ADO.NET `cmd.Execute*()` — object-carried sink (cognium-dev#271). The taint
|
|
13793
13934
|
// rides the SqlCommand receiver (CommandText set from tainted data); the
|
|
13794
13935
|
// receiver is surfaced as arg[0] by extractCSharpCalls, and the command object
|
|
@@ -13805,6 +13946,9 @@ var DEFAULT_SINKS = [
|
|
|
13805
13946
|
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13806
13947
|
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13807
13948
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13949
|
+
// P/Invoke of libc `system(cmd)` — lowercase `system` in C# is virtually
|
|
13950
|
+
// always the imported shell entry point (cognium-ai#275 interop/IlPinvoke).
|
|
13951
|
+
{ method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13808
13952
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13809
13953
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13810
13954
|
{ method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13833,6 +13977,9 @@ var DEFAULT_SINKS = [
|
|
|
13833
13977
|
{ method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13834
13978
|
{ method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13835
13979
|
{ method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13980
|
+
// ASP.NET `ControllerBase.PhysicalFile(path, contentType)` serves a file from
|
|
13981
|
+
// an absolute disk path — attacker-controllable path is CWE-22 (cognium-ai#326/#275).
|
|
13982
|
+
{ method: "PhysicalFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13836
13983
|
// C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
|
|
13837
13984
|
{ method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13838
13985
|
{ method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13857,6 +14004,17 @@ var DEFAULT_SINKS = [
|
|
|
13857
14004
|
{ method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13858
14005
|
{ method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13859
14006
|
{ method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
14007
|
+
// C# server-side template injection (SSTI). Compiling an attacker-controlled
|
|
14008
|
+
// template string is arbitrary code execution — classified as code_injection
|
|
14009
|
+
// (CWE-94), matching how Python (Jinja2/Mako) and Node SSTI are modelled.
|
|
14010
|
+
// Restricted to DISTINCTIVE template-compile APIs so the generic
|
|
14011
|
+
// `Template.Parse` / `.Compile` names (int.Parse, Regex.Compile, …) are not
|
|
14012
|
+
// over-matched. Taint-gated: a constant template never fires. `RunCompile`
|
|
14013
|
+
// = RazorEngine; `CompileRenderStringAsync` = RazorLight; `Compile` is
|
|
14014
|
+
// class-scoped to Handlebars. (cognium-dev#273)
|
|
14015
|
+
{ method: "RunCompile", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
14016
|
+
{ method: "CompileRenderStringAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
14017
|
+
{ method: "Compile", class: "Handlebars", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13860
14018
|
// C# insecure deserialization — the polymorphic BCL formatters that can
|
|
13861
14019
|
// instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
|
|
13862
14020
|
// Each of these classes exists only to deserialize and is unsafe on untrusted
|
|
@@ -13871,6 +14029,14 @@ var DEFAULT_SINKS = [
|
|
|
13871
14029
|
{ method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13872
14030
|
{ method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13873
14031
|
{ method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14032
|
+
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
14033
|
+
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
14034
|
+
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14035
|
+
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
14036
|
+
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
14037
|
+
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
14038
|
+
// memory, so medium (FormatException DoS / unintended arg access), unlike C printf.
|
|
14039
|
+
{ method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13874
14040
|
// C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
|
|
13875
14041
|
// filter is the constructor argument.
|
|
13876
14042
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13909,6 +14075,10 @@ var DEFAULT_SINKS = [
|
|
|
13909
14075
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13910
14076
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13911
14077
|
{ method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14078
|
+
// XPathNavigator.Select/Evaluate — class-scoped (both names collide with LINQ
|
|
14079
|
+
// `.Select`/`.Evaluate`, so they must resolve to an XPathNavigator receiver).
|
|
14080
|
+
{ method: "Select", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14081
|
+
{ method: "Evaluate", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13912
14082
|
// C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
|
|
13913
14083
|
{ method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13914
14084
|
{ method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -14972,7 +15142,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14972
15142
|
const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
|
|
14973
15143
|
if (skipMethods.includes(method.name)) continue;
|
|
14974
15144
|
for (const param of method.parameters) {
|
|
14975
|
-
const
|
|
15145
|
+
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
15146
|
+
const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
14976
15147
|
if (isTaintable) {
|
|
14977
15148
|
const paramLine = param.line ?? method.start_line;
|
|
14978
15149
|
sources.push({
|
|
@@ -15092,6 +15263,13 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
15092
15263
|
}
|
|
15093
15264
|
return result;
|
|
15094
15265
|
}
|
|
15266
|
+
var CSHARP_BINDING_ATTRS = /* @__PURE__ */ new Set([
|
|
15267
|
+
"FromBody",
|
|
15268
|
+
"FromQuery",
|
|
15269
|
+
"FromRoute",
|
|
15270
|
+
"FromForm",
|
|
15271
|
+
"FromHeader"
|
|
15272
|
+
]);
|
|
15095
15273
|
function isInterproceduralTaintableType(typeName, language) {
|
|
15096
15274
|
const baseType = typeName.split("<")[0].trim();
|
|
15097
15275
|
const excludedTypes = [
|
|
@@ -15330,33 +15508,195 @@ function isSafeJSChildProcessCall(call, pattern, language) {
|
|
|
15330
15508
|
if (SHELL_PROGRAMS.has(program)) return false;
|
|
15331
15509
|
return true;
|
|
15332
15510
|
}
|
|
15333
|
-
|
|
15511
|
+
var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
15512
|
+
"sh",
|
|
15513
|
+
"bash",
|
|
15514
|
+
"zsh",
|
|
15515
|
+
"dash",
|
|
15516
|
+
"ash",
|
|
15517
|
+
"ksh",
|
|
15518
|
+
"cmd",
|
|
15519
|
+
"powershell",
|
|
15520
|
+
"pwsh"
|
|
15521
|
+
]);
|
|
15522
|
+
function isConstNonShellExe(raw) {
|
|
15523
|
+
if (!raw) return false;
|
|
15524
|
+
const t = raw.trim();
|
|
15525
|
+
if (!/^@?"[^"]*"$/.test(t)) return false;
|
|
15526
|
+
const program = (t.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
15527
|
+
return !CSHARP_SHELL_PROGRAMS.has(program);
|
|
15528
|
+
}
|
|
15529
|
+
var PSI_CTOR_EXE_RE = /\bnew\s+ProcessStartInfo\s*(?:<[^>]*>)?\s*\(\s*(@?"[^"]*")/;
|
|
15530
|
+
function processStartInfoExe(expr, sourceLines) {
|
|
15531
|
+
const inline = PSI_CTOR_EXE_RE.exec(expr);
|
|
15532
|
+
if (inline) return inline[1];
|
|
15533
|
+
if (sourceLines && /^[A-Za-z_]\w*$/.test(expr.trim())) {
|
|
15534
|
+
const varName = expr.trim();
|
|
15535
|
+
const assignRe = new RegExp(
|
|
15536
|
+
`\\b${varName}\\s*=\\s*new\\s+ProcessStartInfo\\s*(?:<[^>]*>)?\\s*\\(\\s*(@?"[^"]*")`
|
|
15537
|
+
);
|
|
15538
|
+
for (const line of sourceLines) {
|
|
15539
|
+
const m = assignRe.exec(line);
|
|
15540
|
+
if (m) return m[1];
|
|
15541
|
+
}
|
|
15542
|
+
}
|
|
15543
|
+
return null;
|
|
15544
|
+
}
|
|
15545
|
+
function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
15334
15546
|
if (language !== "csharp") return false;
|
|
15335
15547
|
if (pattern.type !== "command_injection") return false;
|
|
15336
|
-
|
|
15337
|
-
if (
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15548
|
+
const method = call.method_name;
|
|
15549
|
+
if (method !== "Start" && method !== "ProcessStartInfo") return false;
|
|
15550
|
+
if (call.arguments.length >= 2) {
|
|
15551
|
+
const fileArg = call.arguments.find((a) => a.position === 0);
|
|
15552
|
+
const raw = fileArg?.literal != null ? String(fileArg.literal) : fileArg?.expression;
|
|
15553
|
+
return isConstNonShellExe(raw);
|
|
15554
|
+
}
|
|
15555
|
+
if (method === "Start" && call.arguments.length === 1) {
|
|
15556
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
15557
|
+
const expr = (arg0?.expression ?? "").trim();
|
|
15558
|
+
return isConstNonShellExe(processStartInfoExe(expr, sourceLines));
|
|
15559
|
+
}
|
|
15560
|
+
return false;
|
|
15561
|
+
}
|
|
15562
|
+
function stripCsLiterals(line) {
|
|
15563
|
+
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
15564
|
+
}
|
|
15565
|
+
function csBraceDepthBefore(lines, idx) {
|
|
15566
|
+
let depth = 0;
|
|
15567
|
+
for (let i2 = 0; i2 < idx && i2 < lines.length; i2++) {
|
|
15568
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
15569
|
+
if (ch === "{") depth++;
|
|
15570
|
+
else if (ch === "}") depth--;
|
|
15571
|
+
}
|
|
15572
|
+
}
|
|
15573
|
+
return depth;
|
|
15574
|
+
}
|
|
15575
|
+
function netBraces(fragment) {
|
|
15576
|
+
let n = 0;
|
|
15577
|
+
for (const ch of fragment) {
|
|
15578
|
+
if (ch === "{") n++;
|
|
15579
|
+
else if (ch === "}") n--;
|
|
15580
|
+
}
|
|
15581
|
+
return n;
|
|
15582
|
+
}
|
|
15583
|
+
function splitCsIf(line) {
|
|
15584
|
+
const m = /\bif\s*\(/.exec(line);
|
|
15585
|
+
if (!m) return null;
|
|
15586
|
+
const ifCol = m.index;
|
|
15587
|
+
let i2 = m.index + m[0].length;
|
|
15588
|
+
let depth = 1;
|
|
15589
|
+
let inStr = false;
|
|
15590
|
+
let strCh = "";
|
|
15591
|
+
for (; i2 < line.length; i2++) {
|
|
15592
|
+
const c = line[i2];
|
|
15593
|
+
if (inStr) {
|
|
15594
|
+
if (c === "\\") {
|
|
15595
|
+
i2++;
|
|
15596
|
+
continue;
|
|
15597
|
+
}
|
|
15598
|
+
if (c === strCh) inStr = false;
|
|
15599
|
+
continue;
|
|
15600
|
+
}
|
|
15601
|
+
if (c === '"' || c === "'") {
|
|
15602
|
+
inStr = true;
|
|
15603
|
+
strCh = c;
|
|
15604
|
+
continue;
|
|
15605
|
+
}
|
|
15606
|
+
if (c === "(") depth++;
|
|
15607
|
+
else if (c === ")") {
|
|
15608
|
+
depth--;
|
|
15609
|
+
if (depth === 0) break;
|
|
15610
|
+
}
|
|
15611
|
+
}
|
|
15612
|
+
if (depth !== 0) return null;
|
|
15613
|
+
return { cond: line.slice(m.index + m[0].length, i2), rest: line.slice(i2 + 1), ifCol };
|
|
15614
|
+
}
|
|
15615
|
+
function csEqualityGuard(cond) {
|
|
15616
|
+
const c = cond.trim();
|
|
15617
|
+
let m = /^(.*?)\s*(==|!=)\s*@?"[^"]*"\s*$/.exec(c);
|
|
15618
|
+
if (m) return { op: m[2], exprSide: m[1].trim() };
|
|
15619
|
+
m = /^@?"[^"]*"\s*(==|!=)\s*(.*)$/.exec(c);
|
|
15620
|
+
if (m) return { op: m[1], exprSide: m[2].trim() };
|
|
15621
|
+
return null;
|
|
15622
|
+
}
|
|
15623
|
+
var CS_IDENT_RE = /[A-Za-z_]\w*/g;
|
|
15624
|
+
function csIdentifiers(expr) {
|
|
15625
|
+
return new Set(expr.match(CS_IDENT_RE) ?? []);
|
|
15626
|
+
}
|
|
15627
|
+
function csThenBlock(lines, ifIdx, rest) {
|
|
15628
|
+
const isExit = (s) => /^\s*(?:return|throw|continue|break)\b/.test(s);
|
|
15629
|
+
let firstIdx;
|
|
15630
|
+
let firstText;
|
|
15631
|
+
if (rest.trim()) {
|
|
15632
|
+
firstIdx = ifIdx;
|
|
15633
|
+
firstText = rest;
|
|
15343
15634
|
} else {
|
|
15344
|
-
|
|
15635
|
+
let j = ifIdx + 1;
|
|
15636
|
+
while (j < lines.length && stripCsLiterals(lines[j]).trim() === "") j++;
|
|
15637
|
+
firstIdx = j;
|
|
15638
|
+
firstText = lines[j] ?? "";
|
|
15345
15639
|
}
|
|
15346
|
-
if (
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
"
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15640
|
+
if (firstText.trim().startsWith("{")) {
|
|
15641
|
+
let depth = 0;
|
|
15642
|
+
let started = false;
|
|
15643
|
+
let endLine = lines.length - 1;
|
|
15644
|
+
let body2 = "";
|
|
15645
|
+
for (let i2 = firstIdx; i2 < lines.length; i2++) {
|
|
15646
|
+
const stripped = stripCsLiterals(i2 === firstIdx ? firstText : lines[i2]);
|
|
15647
|
+
let broke = false;
|
|
15648
|
+
for (const ch of stripped) {
|
|
15649
|
+
if (ch === "{") {
|
|
15650
|
+
depth++;
|
|
15651
|
+
started = true;
|
|
15652
|
+
if (depth === 1) continue;
|
|
15653
|
+
} else if (ch === "}") {
|
|
15654
|
+
depth--;
|
|
15655
|
+
if (depth === 0) {
|
|
15656
|
+
endLine = i2;
|
|
15657
|
+
broke = true;
|
|
15658
|
+
break;
|
|
15659
|
+
}
|
|
15660
|
+
}
|
|
15661
|
+
if (started && depth >= 1) body2 += ch;
|
|
15662
|
+
}
|
|
15663
|
+
if (broke) break;
|
|
15664
|
+
if (started) body2 += " ";
|
|
15665
|
+
}
|
|
15666
|
+
return { start: ifIdx, end: endLine, earlyExit: isExit(body2.trim()) };
|
|
15667
|
+
}
|
|
15668
|
+
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
15669
|
+
}
|
|
15670
|
+
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
15671
|
+
if (language !== "csharp") return false;
|
|
15672
|
+
if (pattern.type !== "ssrf") return false;
|
|
15673
|
+
if (!sourceLines || sourceLines.length === 0) return false;
|
|
15674
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
15675
|
+
for (const a of call.arguments) {
|
|
15676
|
+
if (a.variable) candidates.add(a.variable);
|
|
15677
|
+
for (const id of csIdentifiers(a.expression ?? "")) candidates.add(id);
|
|
15678
|
+
}
|
|
15679
|
+
if (candidates.size === 0) return false;
|
|
15680
|
+
const sinkIdx = call.location.line - 1;
|
|
15681
|
+
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
15682
|
+
for (let i2 = 0; i2 < sinkIdx; i2++) {
|
|
15683
|
+
const parts2 = splitCsIf(sourceLines[i2]);
|
|
15684
|
+
if (!parts2) continue;
|
|
15685
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
15686
|
+
if (!guard) continue;
|
|
15687
|
+
const guardIds = csIdentifiers(guard.exprSide);
|
|
15688
|
+
if (![...guardIds].some((id) => candidates.has(id))) continue;
|
|
15689
|
+
const block = csThenBlock(sourceLines, i2, parts2.rest);
|
|
15690
|
+
if (guard.op === "==") {
|
|
15691
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end) return true;
|
|
15692
|
+
} else {
|
|
15693
|
+
const guardDepth = csBraceDepthBefore(sourceLines, i2) + netBraces(stripCsLiterals(sourceLines[i2]).slice(0, parts2.ifCol));
|
|
15694
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
15695
|
+
return true;
|
|
15696
|
+
}
|
|
15697
|
+
}
|
|
15698
|
+
}
|
|
15699
|
+
return false;
|
|
15360
15700
|
}
|
|
15361
15701
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
15362
15702
|
if (language !== "rust") return false;
|
|
@@ -15613,7 +15953,10 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15613
15953
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
15614
15954
|
continue;
|
|
15615
15955
|
}
|
|
15616
|
-
if (isSafeCSharpProcessStartCall(call, pattern, language)) {
|
|
15956
|
+
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
15957
|
+
continue;
|
|
15958
|
+
}
|
|
15959
|
+
if (isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)) {
|
|
15617
15960
|
continue;
|
|
15618
15961
|
}
|
|
15619
15962
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
@@ -26502,6 +26845,8 @@ var LanguageSourcesPass = class {
|
|
|
26502
26845
|
additionalSources.push(...findSetterChainSources(types, code, language));
|
|
26503
26846
|
additionalSources.push(...findJavaScriptAssignmentSources(code, language));
|
|
26504
26847
|
additionalSources.push(...findCSharpRequestSources(code, language));
|
|
26848
|
+
additionalSources.push(...findCSharpBindingAttributeSources(code, language));
|
|
26849
|
+
additionalSources.push(...findCSharpMinimalApiSources(code, language));
|
|
26505
26850
|
additionalSources.push(...findGoArgvSources(code, language));
|
|
26506
26851
|
const jsDOMSinks = findJavaScriptDOMSinks(code, language);
|
|
26507
26852
|
for (const s of jsDOMSinks) {
|
|
@@ -27209,6 +27554,89 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
27209
27554
|
}
|
|
27210
27555
|
return sources;
|
|
27211
27556
|
}
|
|
27557
|
+
var CSHARP_MINIMAL_API_BINDING = /* @__PURE__ */ new Map([
|
|
27558
|
+
["FromBody", "http_body"],
|
|
27559
|
+
["FromForm", "http_body"],
|
|
27560
|
+
["FromQuery", "http_param"],
|
|
27561
|
+
["FromRoute", "http_path"],
|
|
27562
|
+
["FromHeader", "http_param"]
|
|
27563
|
+
]);
|
|
27564
|
+
var CSHARP_NON_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
27565
|
+
"HttpContext",
|
|
27566
|
+
"HttpRequest",
|
|
27567
|
+
"HttpResponse",
|
|
27568
|
+
"CancellationToken",
|
|
27569
|
+
"ClaimsPrincipal",
|
|
27570
|
+
"ILogger",
|
|
27571
|
+
"IFormFileCollection"
|
|
27572
|
+
]);
|
|
27573
|
+
function findCSharpBindingAttributeSources(sourceCode, language) {
|
|
27574
|
+
if (language !== "csharp") return [];
|
|
27575
|
+
const sources = [];
|
|
27576
|
+
const lines = sourceCode.split("\n");
|
|
27577
|
+
const re = /\[\s*(FromQuery|FromBody|FromForm|FromRoute|FromHeader)(?:\s*\([^)]*\))?\s*\]\s*(?:\[[^\]]*\]\s*)*[\w.<>[\]?]+\s+([A-Za-z_]\w*)/g;
|
|
27578
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
27579
|
+
const lineRe = new RegExp(re.source, "g");
|
|
27580
|
+
let m;
|
|
27581
|
+
while ((m = lineRe.exec(lines[i2])) !== null) {
|
|
27582
|
+
const attr = m[1];
|
|
27583
|
+
const name2 = m[2];
|
|
27584
|
+
const type = attr === "FromBody" || attr === "FromForm" ? "http_body" : attr === "FromRoute" ? "http_path" : "http_param";
|
|
27585
|
+
if (sources.some((s) => s.line === i2 + 1 && s.variable === name2)) continue;
|
|
27586
|
+
sources.push({
|
|
27587
|
+
type,
|
|
27588
|
+
location: `[${attr}] ${name2}`,
|
|
27589
|
+
severity: "high",
|
|
27590
|
+
line: i2 + 1,
|
|
27591
|
+
confidence: 1,
|
|
27592
|
+
variable: name2
|
|
27593
|
+
});
|
|
27594
|
+
}
|
|
27595
|
+
}
|
|
27596
|
+
return sources;
|
|
27597
|
+
}
|
|
27598
|
+
function findCSharpMinimalApiSources(sourceCode, language) {
|
|
27599
|
+
if (language !== "csharp") return [];
|
|
27600
|
+
const sources = [];
|
|
27601
|
+
const lines = sourceCode.split("\n");
|
|
27602
|
+
const mapRe = /\bMap(?:Get|Post|Put|Delete|Patch)\s*\(\s*(?:@?"[^"]*"|[\w.]+)\s*,\s*(?:\[[^\]]*\]\s*)?(?:async\s*)?\(([^)]*)\)\s*=>/;
|
|
27603
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
27604
|
+
const m = mapRe.exec(lines[i2]);
|
|
27605
|
+
if (!m || !m[1].trim()) continue;
|
|
27606
|
+
for (const rawParam of m[1].split(",")) {
|
|
27607
|
+
const seed = classifyCSharpLambdaParam(rawParam);
|
|
27608
|
+
if (!seed) continue;
|
|
27609
|
+
if (sources.some((s) => s.line === i2 + 1 && s.variable === seed.name)) continue;
|
|
27610
|
+
sources.push({
|
|
27611
|
+
type: seed.type,
|
|
27612
|
+
location: `${seed.name} (Minimal API ${seed.via})`,
|
|
27613
|
+
severity: "high",
|
|
27614
|
+
line: i2 + 1,
|
|
27615
|
+
confidence: 1,
|
|
27616
|
+
variable: seed.name
|
|
27617
|
+
});
|
|
27618
|
+
}
|
|
27619
|
+
}
|
|
27620
|
+
return sources;
|
|
27621
|
+
}
|
|
27622
|
+
function classifyCSharpLambdaParam(raw) {
|
|
27623
|
+
const attrs = [...raw.matchAll(/\[([^\]]*)\]/g)].map((a) => a[1].split("(")[0].trim());
|
|
27624
|
+
if (attrs.includes("FromServices")) return null;
|
|
27625
|
+
if (attrs.some((a) => CSHARP_MINIMAL_API_BINDING.has(a))) return null;
|
|
27626
|
+
const noAttr = raw.replace(/\[[^\]]*\]/g, "").trim();
|
|
27627
|
+
const parts2 = noAttr.split(/\s+/).filter(Boolean);
|
|
27628
|
+
if (parts2.length < 2) return null;
|
|
27629
|
+
const name2 = parts2[parts2.length - 1];
|
|
27630
|
+
if (!/^[A-Za-z_]\w*$/.test(name2)) return null;
|
|
27631
|
+
const baseType = (parts2[parts2.length - 2] ?? "").replace(/[?\[\]]/g, "").split("<")[0].split(".").pop() ?? "";
|
|
27632
|
+
if (CSHARP_NON_INPUT_TYPES.has(baseType)) return null;
|
|
27633
|
+
for (const a of attrs) {
|
|
27634
|
+
const t = CSHARP_MINIMAL_API_BINDING.get(a);
|
|
27635
|
+
if (t) return { type: t, name: name2, via: `[${a}]` };
|
|
27636
|
+
}
|
|
27637
|
+
if (baseType === "string") return { type: "http_param", name: name2, via: "string param" };
|
|
27638
|
+
return null;
|
|
27639
|
+
}
|
|
27212
27640
|
function findJavaScriptAssignmentSources(sourceCode, language) {
|
|
27213
27641
|
if (!["javascript", "typescript"].includes(language)) return [];
|
|
27214
27642
|
const sources = [];
|
|
@@ -41534,6 +41962,9 @@ var JAVA_SET_HTTPONLY_TRUE_RE = /\.setHttpOnly\s*\(\s*true\s*\)/;
|
|
|
41534
41962
|
var GO_SECURE_TRUE_RE = /\bSecure\s*:\s*true\b/;
|
|
41535
41963
|
var GO_HTTPONLY_TRUE_RE = /\bHttpOnly\s*:\s*true\b/;
|
|
41536
41964
|
var RUST_SET_COOKIE_MACRO_RE = /(format!|write!|writeln!)\s*\(([^()]*Set-Cookie[^()]*)\)/gis;
|
|
41965
|
+
var CS_COOKIE_OPTIONS_RE = /\bnew\s+CookieOptions\s*\{([^{}]*)\}/gs;
|
|
41966
|
+
var CS_SECURE_FALSE_RE = /\bSecure\s*=\s*false\b/;
|
|
41967
|
+
var CS_HTTPONLY_FALSE_RE = /\bHttpOnly\s*=\s*false\b/;
|
|
41537
41968
|
var InsecureCookiePass = class {
|
|
41538
41969
|
name = "insecure-cookie";
|
|
41539
41970
|
category = "security";
|
|
@@ -41577,9 +42008,29 @@ var InsecureCookiePass = class {
|
|
|
41577
42008
|
insecureCookies.push(det);
|
|
41578
42009
|
this.emit(ctx, file, det, "rust");
|
|
41579
42010
|
}
|
|
42011
|
+
} else if (language === "csharp") {
|
|
42012
|
+
for (const det of this.detectCSharpCookieOptions(code)) {
|
|
42013
|
+
insecureCookies.push(det);
|
|
42014
|
+
this.emit(ctx, file, det, "csharp");
|
|
42015
|
+
}
|
|
41580
42016
|
}
|
|
41581
42017
|
return { insecureCookies };
|
|
41582
42018
|
}
|
|
42019
|
+
// ---------------- C# ----------------
|
|
42020
|
+
detectCSharpCookieOptions(code) {
|
|
42021
|
+
const out2 = [];
|
|
42022
|
+
const re = new RegExp(CS_COOKIE_OPTIONS_RE.source, CS_COOKIE_OPTIONS_RE.flags);
|
|
42023
|
+
let m;
|
|
42024
|
+
while ((m = re.exec(code)) !== null) {
|
|
42025
|
+
const body2 = m[1] ?? "";
|
|
42026
|
+
const missingSecure = CS_SECURE_FALSE_RE.test(body2);
|
|
42027
|
+
const missingHttpOnly = CS_HTTPONLY_FALSE_RE.test(body2);
|
|
42028
|
+
if (!missingSecure && !missingHttpOnly) continue;
|
|
42029
|
+
const line = code.slice(0, m.index).split("\n").length;
|
|
42030
|
+
out2.push({ line, receiver: "CookieOptions", missingSecure, missingHttpOnly, optionsPresent: true });
|
|
42031
|
+
}
|
|
42032
|
+
return out2;
|
|
42033
|
+
}
|
|
41583
42034
|
// ---------------- JS / TS ----------------
|
|
41584
42035
|
detectJs(call) {
|
|
41585
42036
|
if (call.method_name !== "cookie") return null;
|
|
@@ -41681,15 +42132,15 @@ var InsecureCookiePass = class {
|
|
|
41681
42132
|
const missing = [];
|
|
41682
42133
|
if (det.missingSecure) {
|
|
41683
42134
|
missing.push(
|
|
41684
|
-
flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : flavor === "java" ? "`setSecure(true)`" : flavor === "go" ? "`Secure: true`" : "`Secure` attribute"
|
|
42135
|
+
flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : flavor === "java" ? "`setSecure(true)`" : flavor === "go" ? "`Secure: true`" : flavor === "csharp" ? "`Secure = true`" : "`Secure` attribute"
|
|
41685
42136
|
);
|
|
41686
42137
|
}
|
|
41687
42138
|
if (det.missingHttpOnly) {
|
|
41688
42139
|
missing.push(
|
|
41689
|
-
flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : flavor === "java" ? "`setHttpOnly(true)`" : flavor === "go" ? "`HttpOnly: true`" : "`HttpOnly` attribute"
|
|
42140
|
+
flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : flavor === "java" ? "`setHttpOnly(true)`" : flavor === "go" ? "`HttpOnly: true`" : flavor === "csharp" ? "`HttpOnly = true`" : "`HttpOnly` attribute"
|
|
41690
42141
|
);
|
|
41691
42142
|
}
|
|
41692
|
-
const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : flavor === "java" ? "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response." : flavor === "go" ? "Set `Secure: true` and `HttpOnly: true` on the `http.Cookie` struct literal passed to `http.SetCookie`." : "Append `; Secure; HttpOnly` to the `Set-Cookie` header string.";
|
|
42143
|
+
const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : flavor === "java" ? "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response." : flavor === "go" ? "Set `Secure: true` and `HttpOnly: true` on the `http.Cookie` struct literal passed to `http.SetCookie`." : flavor === "csharp" ? "Set `Secure = true` and `HttpOnly = true` on the `CookieOptions` (or enforce them globally via `CookiePolicyOptions`)." : "Append `; Secure; HttpOnly` to the `Set-Cookie` header string.";
|
|
41693
42144
|
ctx.addFinding({
|
|
41694
42145
|
id: `${this.name}-${file}-${det.line}`,
|
|
41695
42146
|
pass: this.name,
|
|
@@ -42160,6 +42611,7 @@ var ISSUE_CWE = {
|
|
|
42160
42611
|
"hardcoded-key": "CWE-321",
|
|
42161
42612
|
"weak-rsa-key": "CWE-326"
|
|
42162
42613
|
};
|
|
42614
|
+
var CS_CIPHER_MODE_ECB_RE = /\bCipherMode\s*\.\s*ECB\b/;
|
|
42163
42615
|
var WeakCryptoPass = class {
|
|
42164
42616
|
name = "weak-crypto";
|
|
42165
42617
|
category = "security";
|
|
@@ -42175,26 +42627,34 @@ var WeakCryptoPass = class {
|
|
|
42175
42627
|
const findings = [];
|
|
42176
42628
|
const constProp = ctx.hasResult("constant-propagation") ? ctx.getResult("constant-propagation") : null;
|
|
42177
42629
|
const literalBindings = scanLiteralBindings(code, language);
|
|
42630
|
+
const emit = (line, det) => {
|
|
42631
|
+
findings.push({ line, language, ...det });
|
|
42632
|
+
ctx.addFinding({
|
|
42633
|
+
id: `${this.name}-${file}-${line}-${det.issue}`,
|
|
42634
|
+
pass: this.name,
|
|
42635
|
+
category: this.category,
|
|
42636
|
+
rule_id: this.name,
|
|
42637
|
+
cwe: ISSUE_CWE[det.issue],
|
|
42638
|
+
severity: "high",
|
|
42639
|
+
level: "error",
|
|
42640
|
+
message: this.buildMessage(det),
|
|
42641
|
+
file,
|
|
42642
|
+
line,
|
|
42643
|
+
fix: this.buildFix(det.issue),
|
|
42644
|
+
evidence: { ...det, language }
|
|
42645
|
+
});
|
|
42646
|
+
};
|
|
42178
42647
|
for (const call of graph.ir.calls) {
|
|
42179
|
-
const
|
|
42180
|
-
|
|
42181
|
-
|
|
42182
|
-
|
|
42183
|
-
|
|
42184
|
-
|
|
42185
|
-
|
|
42186
|
-
|
|
42187
|
-
|
|
42188
|
-
|
|
42189
|
-
cwe: ISSUE_CWE[det.issue],
|
|
42190
|
-
severity: "high",
|
|
42191
|
-
level: "error",
|
|
42192
|
-
message,
|
|
42193
|
-
file,
|
|
42194
|
-
line,
|
|
42195
|
-
fix: this.buildFix(det.issue),
|
|
42196
|
-
evidence: { ...det, language }
|
|
42197
|
-
});
|
|
42648
|
+
for (const det of this.detect(call, language, constProp, literalBindings)) {
|
|
42649
|
+
emit(call.location.line, det);
|
|
42650
|
+
}
|
|
42651
|
+
}
|
|
42652
|
+
if (language === "csharp") {
|
|
42653
|
+
const lines = code.split("\n");
|
|
42654
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
42655
|
+
if (CS_CIPHER_MODE_ECB_RE.test(lines[i2])) {
|
|
42656
|
+
emit(i2 + 1, { issue: "ecb-mode", detail: "CipherMode.ECB", api: "SymmetricAlgorithm.Mode" });
|
|
42657
|
+
}
|
|
42198
42658
|
}
|
|
42199
42659
|
}
|
|
42200
42660
|
return { findings };
|
|
@@ -43434,8 +43894,12 @@ var InsecureDeserializationConfigPass = class {
|
|
|
43434
43894
|
name = "insecure-deserialization-config";
|
|
43435
43895
|
category = "security";
|
|
43436
43896
|
run(ctx) {
|
|
43897
|
+
if (ctx.language === "java") return this.runJava(ctx);
|
|
43898
|
+
if (ctx.language === "csharp") return this.runCSharp(ctx);
|
|
43899
|
+
return { findings: [] };
|
|
43900
|
+
}
|
|
43901
|
+
runJava(ctx) {
|
|
43437
43902
|
const { graph, language } = ctx;
|
|
43438
|
-
if (language !== "java") return { findings: [] };
|
|
43439
43903
|
const file = graph.ir.meta.file;
|
|
43440
43904
|
const findings = [];
|
|
43441
43905
|
for (const call of graph.ir.calls) {
|
|
@@ -43460,12 +43924,46 @@ var InsecureDeserializationConfigPass = class {
|
|
|
43460
43924
|
}
|
|
43461
43925
|
return { findings };
|
|
43462
43926
|
}
|
|
43927
|
+
// C# — Json.NET `TypeNameHandling` set to a value other than `None` (ca#318).
|
|
43928
|
+
// `TypeNameHandling.All/Auto/Objects/Arrays` makes Json.NET honour a `$type`
|
|
43929
|
+
// field in the payload and instantiate the named .NET type, so
|
|
43930
|
+
// `JsonConvert.DeserializeObject(untrusted, settings)` becomes a gadget-chain
|
|
43931
|
+
// RCE (the .NET analogue of XStream's grant-all). The vulnerability is the
|
|
43932
|
+
// constant setting, independent of flow — the secure value `None` never matches.
|
|
43933
|
+
runCSharp(ctx) {
|
|
43934
|
+
const file = ctx.graph.ir.meta.file;
|
|
43935
|
+
const findings = [];
|
|
43936
|
+
const lines = ctx.code.split("\n");
|
|
43937
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
43938
|
+
const m = INSECURE_TYPE_NAME_HANDLING_RE.exec(lines[i2]);
|
|
43939
|
+
if (!m) continue;
|
|
43940
|
+
const line = i2 + 1;
|
|
43941
|
+
const api = `TypeNameHandling = TypeNameHandling.${m[1]}`;
|
|
43942
|
+
findings.push({ line, api });
|
|
43943
|
+
ctx.addFinding({
|
|
43944
|
+
id: `${this.name}-${file}-${line}`,
|
|
43945
|
+
pass: this.name,
|
|
43946
|
+
category: this.category,
|
|
43947
|
+
rule_id: this.name,
|
|
43948
|
+
cwe: "CWE-502",
|
|
43949
|
+
severity: "high",
|
|
43950
|
+
level: "error",
|
|
43951
|
+
message: `Json.NET configured with TypeNameHandling.${m[1]}: a $type field in untrusted JSON can instantiate arbitrary .NET types (deserialization RCE)`,
|
|
43952
|
+
file,
|
|
43953
|
+
line,
|
|
43954
|
+
fix: "Use TypeNameHandling.None (the default), or bind a SerializationBinder that allow-lists the exact types you deserialize.",
|
|
43955
|
+
evidence: { api, language: "csharp" }
|
|
43956
|
+
});
|
|
43957
|
+
}
|
|
43958
|
+
return { findings };
|
|
43959
|
+
}
|
|
43463
43960
|
isPermissiveXStreamConfig(call) {
|
|
43464
43961
|
if (call.method_name !== "addPermission") return false;
|
|
43465
43962
|
const arg0 = call.arguments[0]?.expression;
|
|
43466
43963
|
return typeof arg0 === "string" && ANY_TYPE_PERMISSION_RE.test(arg0);
|
|
43467
43964
|
}
|
|
43468
43965
|
};
|
|
43966
|
+
var INSECURE_TYPE_NAME_HANDLING_RE = /\bTypeNameHandling\s*=\s*(?:Newtonsoft\.Json\.)?TypeNameHandling\.(All|Auto|Objects|Arrays)\b/;
|
|
43469
43967
|
|
|
43470
43968
|
// src/analysis/passes/plaintext-password-storage-pass.ts
|
|
43471
43969
|
function isWriteStorageCall(call, language) {
|
|
@@ -43708,6 +44206,8 @@ var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
|
43708
44206
|
var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
|
|
43709
44207
|
var INSECURE_SKIP_VERIFY_TRUE_RE = /\bInsecureSkipVerify\s*:\s*true\b/;
|
|
43710
44208
|
var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
|
|
44209
|
+
var CS_CERT_CALLBACK_TRUE_RE = /\b(ServerCertificateValidationCallback|ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback)\s*(?:\+?=|\()\s*(?:\([^)]*\)|\w+)\s*=>\s*(?:true\b|\{\s*return\s+true\b)/;
|
|
44210
|
+
var CS_DANGEROUS_ACCEPT_RE = /\bDangerousAcceptAnyServerCertificateValidator\b/;
|
|
43711
44211
|
var ALLOW_ALL_HOSTNAME_VERIFIERS = /* @__PURE__ */ new Set([
|
|
43712
44212
|
"NoopHostnameVerifier.INSTANCE",
|
|
43713
44213
|
"new AllowAllHostnameVerifier()",
|
|
@@ -43852,6 +44352,21 @@ var TlsVerifyDisabledPass = class {
|
|
|
43852
44352
|
}
|
|
43853
44353
|
}
|
|
43854
44354
|
}
|
|
44355
|
+
if (language === "csharp") {
|
|
44356
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
44357
|
+
const l = lines[i2];
|
|
44358
|
+
const m = CS_CERT_CALLBACK_TRUE_RE.exec(l);
|
|
44359
|
+
if (m) {
|
|
44360
|
+
out2.push({ line: i2 + 1, pattern: `${m[1]} => true`, api: m[1] });
|
|
44361
|
+
} else if (CS_DANGEROUS_ACCEPT_RE.test(l)) {
|
|
44362
|
+
out2.push({
|
|
44363
|
+
line: i2 + 1,
|
|
44364
|
+
pattern: "DangerousAcceptAnyServerCertificateValidator",
|
|
44365
|
+
api: "HttpClientHandler"
|
|
44366
|
+
});
|
|
44367
|
+
}
|
|
44368
|
+
}
|
|
44369
|
+
}
|
|
43855
44370
|
return out2;
|
|
43856
44371
|
}
|
|
43857
44372
|
fixFor(language, pattern) {
|
|
@@ -43873,6 +44388,9 @@ var TlsVerifyDisabledPass = class {
|
|
|
43873
44388
|
if (pattern.includes("ssl._create_unverified_context")) {
|
|
43874
44389
|
return "Do not use `_create_unverified_context()`. Use `ssl.create_default_context()`.";
|
|
43875
44390
|
}
|
|
44391
|
+
if (language === "csharp") {
|
|
44392
|
+
return "Do not accept every certificate. Remove the always-true validation callback (and `DangerousAcceptAnyServerCertificateValidator`); rely on the platform default. To trust a private CA, validate the chain against it in the callback instead of returning true.";
|
|
44393
|
+
}
|
|
43876
44394
|
return "Restore TLS certificate and hostname verification.";
|
|
43877
44395
|
}
|
|
43878
44396
|
};
|
|
@@ -44376,6 +44894,8 @@ var PY_VERIFY_SIGNATURE_FALSE_RE = /["']verify_signature["']\s*:\s*False\b/;
|
|
|
44376
44894
|
var PY_VERIFY_KW_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
44377
44895
|
var PY_ALG_NONE_RE = /\balgorithms\s*=\s*[\[\(]\s*["']none["']/i;
|
|
44378
44896
|
var JS_ALG_NONE_RE = /\balgorithms\s*:\s*\[\s*["']none["']/i;
|
|
44897
|
+
var CS_REQUIRE_SIGNED_FALSE_RE = /\bRequireSignedTokens\s*=\s*false\b/;
|
|
44898
|
+
var CS_SIGNATURE_VALIDATOR_BYPASS_RE = /\bSignatureValidator\s*=\s*[^;]*=>\s*new\s+JwtSecurityToken\b/;
|
|
44379
44899
|
var JwtVerifyDisabledPass = class {
|
|
44380
44900
|
name = "jwt-verify-disabled";
|
|
44381
44901
|
category = "security";
|
|
@@ -44383,25 +44903,34 @@ var JwtVerifyDisabledPass = class {
|
|
|
44383
44903
|
const { graph, language } = ctx;
|
|
44384
44904
|
const file = graph.ir.meta.file;
|
|
44385
44905
|
const findings = [];
|
|
44906
|
+
const emit = (line, det) => {
|
|
44907
|
+
findings.push({ line, language, ...det });
|
|
44908
|
+
ctx.addFinding({
|
|
44909
|
+
id: `${this.name}-${file}-${line}-${det.pattern}`,
|
|
44910
|
+
pass: this.name,
|
|
44911
|
+
category: this.category,
|
|
44912
|
+
rule_id: this.name,
|
|
44913
|
+
cwe: "CWE-347",
|
|
44914
|
+
severity: "critical",
|
|
44915
|
+
level: "error",
|
|
44916
|
+
message: `JWT signature verification disabled via \`${det.pattern}\` in \`${det.api}\`. Any attacker can forge a token with arbitrary claims (user id, roles, expiry) since the signature is not checked.`,
|
|
44917
|
+
file,
|
|
44918
|
+
line,
|
|
44919
|
+
fix: this.fixFor(language),
|
|
44920
|
+
evidence: { ...det, language }
|
|
44921
|
+
});
|
|
44922
|
+
};
|
|
44386
44923
|
for (const call of graph.ir.calls) {
|
|
44387
|
-
const
|
|
44388
|
-
|
|
44389
|
-
|
|
44390
|
-
|
|
44391
|
-
|
|
44392
|
-
|
|
44393
|
-
|
|
44394
|
-
|
|
44395
|
-
|
|
44396
|
-
|
|
44397
|
-
severity: "critical",
|
|
44398
|
-
level: "error",
|
|
44399
|
-
message: `JWT signature verification disabled via \`${det.pattern}\` in \`${det.api}\`. Any attacker can forge a token with arbitrary claims (user id, roles, expiry) since the signature is not checked.`,
|
|
44400
|
-
file,
|
|
44401
|
-
line,
|
|
44402
|
-
fix: this.fixFor(language),
|
|
44403
|
-
evidence: { ...det, language }
|
|
44404
|
-
});
|
|
44924
|
+
for (const det of this.detect(call, language)) emit(call.location.line, det);
|
|
44925
|
+
}
|
|
44926
|
+
if (language === "csharp") {
|
|
44927
|
+
const lines = ctx.code.split("\n");
|
|
44928
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
44929
|
+
if (CS_REQUIRE_SIGNED_FALSE_RE.test(lines[i2])) {
|
|
44930
|
+
emit(i2 + 1, { pattern: "RequireSignedTokens = false", api: "TokenValidationParameters" });
|
|
44931
|
+
} else if (CS_SIGNATURE_VALIDATOR_BYPASS_RE.test(lines[i2])) {
|
|
44932
|
+
emit(i2 + 1, { pattern: "SignatureValidator returns an unvalidated token", api: "TokenValidationParameters" });
|
|
44933
|
+
}
|
|
44405
44934
|
}
|
|
44406
44935
|
}
|
|
44407
44936
|
return { findings };
|
|
@@ -44473,6 +45002,9 @@ var JwtVerifyDisabledPass = class {
|
|
|
44473
45002
|
if (language === "java") {
|
|
44474
45003
|
return "For auth0/java-jwt: use `JWT.require(Algorithm.HMAC256(secret))` or an RSA algorithm. For jjwt: call `parseClaimsJws(token)` (signature enforced) rather than `parse(token)` (signature ignored).";
|
|
44475
45004
|
}
|
|
45005
|
+
if (language === "csharp") {
|
|
45006
|
+
return "Leave `RequireSignedTokens = true` and do not install a custom `SignatureValidator` that returns the token unverified. Configure `IssuerSigningKey`/`IssuerSigningKeys` and let the handler validate the signature.";
|
|
45007
|
+
}
|
|
44476
45008
|
return "Enforce JWT signature verification with a concrete algorithm (HS256/RS256/ES256). Never accept `alg: none`.";
|
|
44477
45009
|
}
|
|
44478
45010
|
};
|
|
@@ -45629,6 +46161,25 @@ function getNodeTypesForLanguage(language) {
|
|
|
45629
46161
|
"selector_expression",
|
|
45630
46162
|
"identifier"
|
|
45631
46163
|
]);
|
|
46164
|
+
case "csharp":
|
|
46165
|
+
return /* @__PURE__ */ new Set([
|
|
46166
|
+
"method_invocation",
|
|
46167
|
+
"object_creation_expression",
|
|
46168
|
+
"class_declaration",
|
|
46169
|
+
"method_declaration",
|
|
46170
|
+
"constructor_declaration",
|
|
46171
|
+
"field_declaration",
|
|
46172
|
+
"import_declaration",
|
|
46173
|
+
"interface_declaration",
|
|
46174
|
+
"enum_declaration",
|
|
46175
|
+
"package_declaration",
|
|
46176
|
+
"local_variable_declaration",
|
|
46177
|
+
// buildCSharpCFG method-like containers
|
|
46178
|
+
"destructor_declaration",
|
|
46179
|
+
"operator_declaration",
|
|
46180
|
+
"local_function_statement",
|
|
46181
|
+
"accessor_declaration"
|
|
46182
|
+
]);
|
|
45632
46183
|
default:
|
|
45633
46184
|
return /* @__PURE__ */ new Set([
|
|
45634
46185
|
"method_invocation",
|