circle-ir 4.7.1 → 4.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +79 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.js +1 -1
- package/dist/analysis/findings.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/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +11 -1
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.js +10 -0
- package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +80 -0
- 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 +320 -47
- package/dist/core/circle-ir-core.cjs +256 -43
- package/dist/core/circle-ir-core.js +256 -43
- 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/package.json +1 -1
|
@@ -6000,13 +6000,21 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6000
6000
|
const left = asn.childForFieldName("left");
|
|
6001
6001
|
if (left?.type !== "member_access_expression") continue;
|
|
6002
6002
|
const nameNode = left.childForFieldName("name");
|
|
6003
|
-
if (!nameNode
|
|
6003
|
+
if (!nameNode) continue;
|
|
6004
|
+
const propName = getNodeText(nameNode);
|
|
6005
|
+
const exprNode = left.childForFieldName("expression");
|
|
6006
|
+
if (propName === "Filter") {
|
|
6007
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6008
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6009
|
+
if (recvType !== "DirectorySearcher") continue;
|
|
6010
|
+
} else if (propName !== "CommandText") {
|
|
6011
|
+
continue;
|
|
6012
|
+
}
|
|
6004
6013
|
const right = asn.childForFieldName("right");
|
|
6005
6014
|
if (!right) continue;
|
|
6006
6015
|
const rhsText = getNodeText(right);
|
|
6007
|
-
const exprNode = left.childForFieldName("expression");
|
|
6008
6016
|
calls.push({
|
|
6009
|
-
method_name:
|
|
6017
|
+
method_name: propName,
|
|
6010
6018
|
receiver: exprNode ? getNodeText(exprNode) : null,
|
|
6011
6019
|
receiver_type: null,
|
|
6012
6020
|
receiver_type_fqn: null,
|
|
@@ -8572,6 +8580,9 @@ function buildCFG(tree, language, cache) {
|
|
|
8572
8580
|
if (effectiveLanguage === "go") {
|
|
8573
8581
|
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8574
8582
|
}
|
|
8583
|
+
if (effectiveLanguage === "csharp") {
|
|
8584
|
+
return buildCSharpCFG(tree, blockIdCounter, cache);
|
|
8585
|
+
}
|
|
8575
8586
|
if (isJavaScript) {
|
|
8576
8587
|
const functions = [
|
|
8577
8588
|
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
@@ -8584,7 +8595,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8584
8595
|
const body2 = func2.childForFieldName("body");
|
|
8585
8596
|
if (!body2) continue;
|
|
8586
8597
|
if (body2.type === "statement_block") {
|
|
8587
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8598
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "js");
|
|
8588
8599
|
allBlocks.push(...blocks);
|
|
8589
8600
|
allEdges.push(...edges);
|
|
8590
8601
|
blockIdCounter = nextId;
|
|
@@ -8606,7 +8617,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8606
8617
|
for (const method of methods) {
|
|
8607
8618
|
const body2 = method.childForFieldName("body");
|
|
8608
8619
|
if (!body2) continue;
|
|
8609
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8620
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8610
8621
|
allBlocks.push(...blocks);
|
|
8611
8622
|
allEdges.push(...edges);
|
|
8612
8623
|
blockIdCounter = nextId;
|
|
@@ -8614,7 +8625,28 @@ function buildCFG(tree, language, cache) {
|
|
|
8614
8625
|
}
|
|
8615
8626
|
return { blocks: allBlocks, edges: allEdges };
|
|
8616
8627
|
}
|
|
8617
|
-
function
|
|
8628
|
+
function buildCSharpCFG(tree, blockIdCounter, cache) {
|
|
8629
|
+
const allBlocks = [];
|
|
8630
|
+
const allEdges = [];
|
|
8631
|
+
const containers = [
|
|
8632
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8633
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
|
|
8634
|
+
...getNodesFromCache(tree.rootNode, "destructor_declaration", cache),
|
|
8635
|
+
...getNodesFromCache(tree.rootNode, "operator_declaration", cache),
|
|
8636
|
+
...getNodesFromCache(tree.rootNode, "local_function_statement", cache),
|
|
8637
|
+
...getNodesFromCache(tree.rootNode, "accessor_declaration", cache)
|
|
8638
|
+
];
|
|
8639
|
+
for (const container of containers) {
|
|
8640
|
+
const body2 = container.childForFieldName("body");
|
|
8641
|
+
if (!body2 || body2.type !== "block") continue;
|
|
8642
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "csharp");
|
|
8643
|
+
allBlocks.push(...blocks);
|
|
8644
|
+
allEdges.push(...edges);
|
|
8645
|
+
blockIdCounter = nextId;
|
|
8646
|
+
}
|
|
8647
|
+
return { blocks: allBlocks, edges: allEdges };
|
|
8648
|
+
}
|
|
8649
|
+
function buildMethodCFG(body2, startId, dialect) {
|
|
8618
8650
|
const blocks = [];
|
|
8619
8651
|
const edges = [];
|
|
8620
8652
|
let currentId = startId;
|
|
@@ -8625,7 +8657,7 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8625
8657
|
end_line: body2.startPosition.row + 1
|
|
8626
8658
|
};
|
|
8627
8659
|
blocks.push(entryBlock);
|
|
8628
|
-
const result = processStatements(body2, currentId, blocks, edges,
|
|
8660
|
+
const result = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8629
8661
|
currentId = result.nextId;
|
|
8630
8662
|
if (result.entryId !== -1) {
|
|
8631
8663
|
edges.push({
|
|
@@ -8657,15 +8689,15 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8657
8689
|
}
|
|
8658
8690
|
return { blocks, edges, nextId: currentId };
|
|
8659
8691
|
}
|
|
8660
|
-
function processStatements(container, startId, blocks, edges,
|
|
8692
|
+
function processStatements(container, startId, blocks, edges, dialect) {
|
|
8661
8693
|
let currentId = startId;
|
|
8662
8694
|
let firstBlockId = -1;
|
|
8663
8695
|
let lastExitIds = [];
|
|
8664
8696
|
for (let i2 = 0; i2 < container.childCount; i2++) {
|
|
8665
8697
|
const stmt = container.child(i2);
|
|
8666
8698
|
if (!stmt) continue;
|
|
8667
|
-
if (!isStatement(stmt,
|
|
8668
|
-
const result = processStatement(stmt, currentId, blocks, edges,
|
|
8699
|
+
if (!isStatement(stmt, dialect)) continue;
|
|
8700
|
+
const result = processStatement(stmt, currentId, blocks, edges, dialect);
|
|
8669
8701
|
currentId = result.nextId;
|
|
8670
8702
|
if (firstBlockId === -1) {
|
|
8671
8703
|
firstBlockId = result.entryId;
|
|
@@ -8686,31 +8718,49 @@ function processStatements(container, startId, blocks, edges, isJavaScript) {
|
|
|
8686
8718
|
nextId: currentId
|
|
8687
8719
|
};
|
|
8688
8720
|
}
|
|
8689
|
-
function processStatement(stmt, startId, blocks, edges,
|
|
8721
|
+
function processStatement(stmt, startId, blocks, edges, dialect) {
|
|
8690
8722
|
switch (stmt.type) {
|
|
8691
8723
|
case "if_statement":
|
|
8692
|
-
return processIfStatement(stmt, startId, blocks, edges,
|
|
8724
|
+
return processIfStatement(stmt, startId, blocks, edges, dialect);
|
|
8693
8725
|
case "for_statement":
|
|
8694
8726
|
case "enhanced_for_statement":
|
|
8695
8727
|
case "for_in_statement":
|
|
8696
8728
|
case "for_of_statement":
|
|
8697
|
-
|
|
8729
|
+
case "foreach_statement":
|
|
8730
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8698
8731
|
case "while_statement":
|
|
8699
|
-
return processWhileStatement(stmt, startId, blocks, edges,
|
|
8732
|
+
return processWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8700
8733
|
case "do_statement":
|
|
8701
|
-
return processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8734
|
+
return processDoWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8702
8735
|
case "try_statement":
|
|
8703
|
-
return processTryStatement(stmt, startId, blocks, edges,
|
|
8736
|
+
return processTryStatement(stmt, startId, blocks, edges, dialect);
|
|
8704
8737
|
case "switch_expression":
|
|
8705
8738
|
case "switch_statement":
|
|
8706
|
-
return processSwitchStatement(stmt, startId, blocks, edges,
|
|
8739
|
+
return processSwitchStatement(stmt, startId, blocks, edges, dialect);
|
|
8740
|
+
// C# scoped blocks add no branching — flow through the inner block so its
|
|
8741
|
+
// nested statements are still captured.
|
|
8742
|
+
case "using_statement":
|
|
8743
|
+
case "lock_statement":
|
|
8744
|
+
case "checked_statement":
|
|
8745
|
+
case "unsafe_statement": {
|
|
8746
|
+
const inner = stmt.childForFieldName("body") ?? lastBlockChild(stmt);
|
|
8747
|
+
if (inner) return processStatement(inner, startId, blocks, edges, dialect);
|
|
8748
|
+
return processSimpleStatement(stmt, startId, blocks);
|
|
8749
|
+
}
|
|
8707
8750
|
case "block":
|
|
8708
8751
|
case "statement_block":
|
|
8709
|
-
return processStatements(stmt, startId, blocks, edges,
|
|
8752
|
+
return processStatements(stmt, startId, blocks, edges, dialect);
|
|
8710
8753
|
default:
|
|
8711
8754
|
return processSimpleStatement(stmt, startId, blocks);
|
|
8712
8755
|
}
|
|
8713
8756
|
}
|
|
8757
|
+
function lastBlockChild(node) {
|
|
8758
|
+
for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
|
|
8759
|
+
const c = node.child(i2);
|
|
8760
|
+
if (c && c.type === "block") return c;
|
|
8761
|
+
}
|
|
8762
|
+
return null;
|
|
8763
|
+
}
|
|
8714
8764
|
function processSimpleStatement(stmt, startId, blocks) {
|
|
8715
8765
|
const block = {
|
|
8716
8766
|
id: startId,
|
|
@@ -8725,7 +8775,7 @@ function processSimpleStatement(stmt, startId, blocks) {
|
|
|
8725
8775
|
nextId: startId + 1
|
|
8726
8776
|
};
|
|
8727
8777
|
}
|
|
8728
|
-
function processIfStatement(stmt, startId, blocks, edges,
|
|
8778
|
+
function processIfStatement(stmt, startId, blocks, edges, dialect) {
|
|
8729
8779
|
let currentId = startId;
|
|
8730
8780
|
const condBlock = {
|
|
8731
8781
|
id: currentId++,
|
|
@@ -8737,7 +8787,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8737
8787
|
const exitIds = [];
|
|
8738
8788
|
const consequence = stmt.childForFieldName("consequence");
|
|
8739
8789
|
if (consequence) {
|
|
8740
|
-
const thenResult = processStatement(consequence, currentId, blocks, edges,
|
|
8790
|
+
const thenResult = processStatement(consequence, currentId, blocks, edges, dialect);
|
|
8741
8791
|
currentId = thenResult.nextId;
|
|
8742
8792
|
edges.push({
|
|
8743
8793
|
from: condBlock.id,
|
|
@@ -8748,7 +8798,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8748
8798
|
}
|
|
8749
8799
|
const alternative = stmt.childForFieldName("alternative");
|
|
8750
8800
|
if (alternative) {
|
|
8751
|
-
const elseResult = processStatement(alternative, currentId, blocks, edges,
|
|
8801
|
+
const elseResult = processStatement(alternative, currentId, blocks, edges, dialect);
|
|
8752
8802
|
currentId = elseResult.nextId;
|
|
8753
8803
|
edges.push({
|
|
8754
8804
|
from: condBlock.id,
|
|
@@ -8765,7 +8815,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8765
8815
|
nextId: currentId
|
|
8766
8816
|
};
|
|
8767
8817
|
}
|
|
8768
|
-
function processForStatement(stmt, startId, blocks, edges,
|
|
8818
|
+
function processForStatement(stmt, startId, blocks, edges, dialect) {
|
|
8769
8819
|
let currentId = startId;
|
|
8770
8820
|
const loopBlock = {
|
|
8771
8821
|
id: currentId++,
|
|
@@ -8776,7 +8826,7 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8776
8826
|
blocks.push(loopBlock);
|
|
8777
8827
|
const body2 = stmt.childForFieldName("body");
|
|
8778
8828
|
if (body2) {
|
|
8779
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8829
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8780
8830
|
currentId = bodyResult.nextId;
|
|
8781
8831
|
edges.push({
|
|
8782
8832
|
from: loopBlock.id,
|
|
@@ -8798,16 +8848,16 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8798
8848
|
nextId: currentId
|
|
8799
8849
|
};
|
|
8800
8850
|
}
|
|
8801
|
-
function processWhileStatement(stmt, startId, blocks, edges,
|
|
8802
|
-
return processForStatement(stmt, startId, blocks, edges,
|
|
8851
|
+
function processWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8852
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8803
8853
|
}
|
|
8804
|
-
function processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8854
|
+
function processDoWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8805
8855
|
let currentId = startId;
|
|
8806
8856
|
const body2 = stmt.childForFieldName("body");
|
|
8807
8857
|
let bodyEntryId = currentId;
|
|
8808
8858
|
let bodyExitIds = [];
|
|
8809
8859
|
if (body2) {
|
|
8810
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8860
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8811
8861
|
currentId = bodyResult.nextId;
|
|
8812
8862
|
bodyEntryId = bodyResult.entryId;
|
|
8813
8863
|
bodyExitIds = bodyResult.exitIds;
|
|
@@ -8837,13 +8887,13 @@ function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8837
8887
|
nextId: currentId
|
|
8838
8888
|
};
|
|
8839
8889
|
}
|
|
8840
|
-
function processTryStatement(stmt, startId, blocks, edges,
|
|
8890
|
+
function processTryStatement(stmt, startId, blocks, edges, dialect) {
|
|
8841
8891
|
let currentId = startId;
|
|
8842
8892
|
const exitIds = [];
|
|
8843
8893
|
const body2 = stmt.childForFieldName("body");
|
|
8844
8894
|
let tryEntryId = -1;
|
|
8845
8895
|
if (body2) {
|
|
8846
|
-
const bodyResult = processStatements(body2, currentId, blocks, edges,
|
|
8896
|
+
const bodyResult = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8847
8897
|
currentId = bodyResult.nextId;
|
|
8848
8898
|
tryEntryId = bodyResult.entryId;
|
|
8849
8899
|
exitIds.push(...bodyResult.exitIds);
|
|
@@ -8853,7 +8903,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8853
8903
|
if (child?.type === "catch_clause") {
|
|
8854
8904
|
const catchBody = child.childForFieldName("body");
|
|
8855
8905
|
if (catchBody) {
|
|
8856
|
-
const catchResult = processStatements(catchBody, currentId, blocks, edges,
|
|
8906
|
+
const catchResult = processStatements(catchBody, currentId, blocks, edges, dialect);
|
|
8857
8907
|
currentId = catchResult.nextId;
|
|
8858
8908
|
if (tryEntryId !== -1) {
|
|
8859
8909
|
edges.push({
|
|
@@ -8866,9 +8916,18 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8866
8916
|
}
|
|
8867
8917
|
}
|
|
8868
8918
|
}
|
|
8869
|
-
|
|
8919
|
+
let finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
|
|
8920
|
+
if (!finallyClause && dialect === "csharp") {
|
|
8921
|
+
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
8922
|
+
const child = stmt.child(i2);
|
|
8923
|
+
if (child?.type === "finally_clause") {
|
|
8924
|
+
finallyClause = lastBlockChild(child);
|
|
8925
|
+
break;
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8928
|
+
}
|
|
8870
8929
|
if (finallyClause) {
|
|
8871
|
-
const finallyResult = processStatements(finallyClause, currentId, blocks, edges,
|
|
8930
|
+
const finallyResult = processStatements(finallyClause, currentId, blocks, edges, dialect);
|
|
8872
8931
|
currentId = finallyResult.nextId;
|
|
8873
8932
|
for (const exitId of exitIds) {
|
|
8874
8933
|
edges.push({
|
|
@@ -8889,7 +8948,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8889
8948
|
nextId: currentId
|
|
8890
8949
|
};
|
|
8891
8950
|
}
|
|
8892
|
-
function processSwitchStatement(stmt, startId, blocks, edges,
|
|
8951
|
+
function processSwitchStatement(stmt, startId, blocks, edges, dialect) {
|
|
8893
8952
|
let currentId = startId;
|
|
8894
8953
|
const switchBlock = {
|
|
8895
8954
|
id: currentId++,
|
|
@@ -8901,11 +8960,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8901
8960
|
const exitIds = [];
|
|
8902
8961
|
const body2 = stmt.childForFieldName("body");
|
|
8903
8962
|
if (body2) {
|
|
8904
|
-
const caseTypes =
|
|
8963
|
+
const caseTypes = dialect === "js" ? ["switch_case", "switch_default"] : dialect === "csharp" ? ["switch_section"] : ["switch_block_statement_group", "switch_rule"];
|
|
8905
8964
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
8906
8965
|
const child = body2.child(i2);
|
|
8907
8966
|
if (child && caseTypes.includes(child.type)) {
|
|
8908
|
-
const caseResult = processStatements(child, currentId, blocks, edges,
|
|
8967
|
+
const caseResult = processStatements(child, currentId, blocks, edges, dialect);
|
|
8909
8968
|
currentId = caseResult.nextId;
|
|
8910
8969
|
if (caseResult.entryId !== -1) {
|
|
8911
8970
|
edges.push({
|
|
@@ -8935,7 +8994,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8935
8994
|
for (const func2 of functions) {
|
|
8936
8995
|
const body2 = func2.childForFieldName("body");
|
|
8937
8996
|
if (!body2) continue;
|
|
8938
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8997
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8939
8998
|
allBlocks.push(...blocks);
|
|
8940
8999
|
allEdges.push(...edges);
|
|
8941
9000
|
blockIdCounter = nextId;
|
|
@@ -8958,7 +9017,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8958
9017
|
let lastExitIds = [];
|
|
8959
9018
|
let firstBlockId = -1;
|
|
8960
9019
|
for (const stmt of topLevelStatements) {
|
|
8961
|
-
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges,
|
|
9020
|
+
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, "java");
|
|
8962
9021
|
blockIdCounter = result.nextId;
|
|
8963
9022
|
if (firstBlockId === -1) {
|
|
8964
9023
|
firstBlockId = result.entryId;
|
|
@@ -9002,7 +9061,8 @@ function isBashStatement(node) {
|
|
|
9002
9061
|
]);
|
|
9003
9062
|
return bashStatementTypes.has(node.type);
|
|
9004
9063
|
}
|
|
9005
|
-
function isStatement(node,
|
|
9064
|
+
function isStatement(node, dialect) {
|
|
9065
|
+
if (dialect === "csharp") return csharpStatementTypes.has(node.type);
|
|
9006
9066
|
const javaStatementTypes = /* @__PURE__ */ new Set([
|
|
9007
9067
|
"local_variable_declaration",
|
|
9008
9068
|
"expression_statement",
|
|
@@ -9046,8 +9106,30 @@ function isStatement(node, isJavaScript) {
|
|
|
9046
9106
|
"export_statement",
|
|
9047
9107
|
"import_statement"
|
|
9048
9108
|
]);
|
|
9049
|
-
return
|
|
9050
|
-
}
|
|
9109
|
+
return dialect === "js" ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
9110
|
+
}
|
|
9111
|
+
var csharpStatementTypes = /* @__PURE__ */ new Set([
|
|
9112
|
+
"local_declaration_statement",
|
|
9113
|
+
"expression_statement",
|
|
9114
|
+
"if_statement",
|
|
9115
|
+
"for_statement",
|
|
9116
|
+
"foreach_statement",
|
|
9117
|
+
"while_statement",
|
|
9118
|
+
"do_statement",
|
|
9119
|
+
"try_statement",
|
|
9120
|
+
"switch_statement",
|
|
9121
|
+
"return_statement",
|
|
9122
|
+
"throw_statement",
|
|
9123
|
+
"break_statement",
|
|
9124
|
+
"continue_statement",
|
|
9125
|
+
"using_statement",
|
|
9126
|
+
"lock_statement",
|
|
9127
|
+
"checked_statement",
|
|
9128
|
+
"unsafe_statement",
|
|
9129
|
+
"yield_statement",
|
|
9130
|
+
"goto_statement",
|
|
9131
|
+
"block"
|
|
9132
|
+
]);
|
|
9051
9133
|
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
9052
9134
|
const allBlocks = [];
|
|
9053
9135
|
const allEdges = [];
|
|
@@ -9058,7 +9140,7 @@ function buildGoCFG(tree, blockIdCounter, cache) {
|
|
|
9058
9140
|
for (const func2 of functions) {
|
|
9059
9141
|
const body2 = func2.childForFieldName("body");
|
|
9060
9142
|
if (!body2 || body2.type !== "block") continue;
|
|
9061
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9143
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
9062
9144
|
allBlocks.push(...blocks);
|
|
9063
9145
|
allEdges.push(...edges);
|
|
9064
9146
|
blockIdCounter = nextId;
|
|
@@ -12258,7 +12340,11 @@ var DEFAULT_SINKS = [
|
|
|
12258
12340
|
// File: covers both File(String pathname) and File(parent, child). The 2-arg
|
|
12259
12341
|
// overload's child argument carries CVE-2018-8041 (Camel mail Content-Disposition
|
|
12260
12342
|
// filename written to disk).
|
|
12261
|
-
|
|
12343
|
+
// Java `new File(path)` idiom. Excluded for C#: there is no `new File()` in
|
|
12344
|
+
// .NET (it's `System.IO.File` statics + `FileStream`, covered separately), and
|
|
12345
|
+
// this over-matched ASP.NET `ControllerBase.File(stream|bytes, contentType)` —
|
|
12346
|
+
// a result helper that returns a file, not a path sink (cognium-ai#326 B).
|
|
12347
|
+
{ method: "File", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], exclude_languages: ["csharp"] },
|
|
12262
12348
|
{ method: "FileInputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12263
12349
|
{ method: "FileOutputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12264
12350
|
{ method: "FileReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -13785,6 +13871,11 @@ var DEFAULT_SINKS = [
|
|
|
13785
13871
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13786
13872
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13787
13873
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13874
|
+
// `DirectorySearcher.Filter = "(uid=" + x + ")"` — LDAP injection (cognium-ai#272).
|
|
13875
|
+
// Emitted as a synthetic call by extractCSharpCalls ONLY when the receiver
|
|
13876
|
+
// resolves to a DirectorySearcher, so this classless entry never over-matches
|
|
13877
|
+
// other `.Filter =` assignments (DataView/BindingSource/collection filters).
|
|
13878
|
+
{ method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13788
13879
|
// ADO.NET `cmd.Execute*()` — object-carried sink (cognium-dev#271). The taint
|
|
13789
13880
|
// rides the SqlCommand receiver (CommandText set from tainted data); the
|
|
13790
13881
|
// receiver is surfaced as arg[0] by extractCSharpCalls, and the command object
|
|
@@ -13801,6 +13892,9 @@ var DEFAULT_SINKS = [
|
|
|
13801
13892
|
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13802
13893
|
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13803
13894
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13895
|
+
// P/Invoke of libc `system(cmd)` — lowercase `system` in C# is virtually
|
|
13896
|
+
// always the imported shell entry point (cognium-ai#275 interop/IlPinvoke).
|
|
13897
|
+
{ method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13804
13898
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13805
13899
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13806
13900
|
{ method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13813,6 +13907,25 @@ var DEFAULT_SINKS = [
|
|
|
13813
13907
|
{ method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13814
13908
|
{ method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13815
13909
|
{ method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13910
|
+
// Remaining System.IO.File path APIs — class-scoped so they don't collide
|
|
13911
|
+
// with unrelated methods. Copy/Move take a source AND destination path (both
|
|
13912
|
+
// attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
|
|
13913
|
+
{ method: "Copy", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13914
|
+
{ method: "Move", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13915
|
+
{ method: "Delete", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13916
|
+
{ method: "Open", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13917
|
+
{ method: "OpenText", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13918
|
+
{ method: "Create", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13919
|
+
{ method: "WriteAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13920
|
+
{ method: "AppendAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13921
|
+
// System.IO.Directory path APIs.
|
|
13922
|
+
{ method: "CreateDirectory", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13923
|
+
{ method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13924
|
+
{ method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13925
|
+
{ method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13926
|
+
// ASP.NET `ControllerBase.PhysicalFile(path, contentType)` serves a file from
|
|
13927
|
+
// an absolute disk path — attacker-controllable path is CWE-22 (cognium-ai#326/#275).
|
|
13928
|
+
{ method: "PhysicalFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13816
13929
|
// C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
|
|
13817
13930
|
{ method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13818
13931
|
{ method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13829,13 +13942,36 @@ var DEFAULT_SINKS = [
|
|
|
13829
13942
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13830
13943
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13831
13944
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13832
|
-
//
|
|
13945
|
+
// Loading an assembly from an attacker-controlled name/path is arbitrary code
|
|
13946
|
+
// execution. Class-scoped to `Assembly` (low FP — a tainted arg here is rare
|
|
13947
|
+
// in benign code). `Type.GetType`/`Activator.CreateInstance` are intentionally
|
|
13948
|
+
// NOT added — reflection with config-driven type names is common in DI, an FP
|
|
13949
|
+
// trap without the reflection-suppress machinery.
|
|
13950
|
+
{ method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13951
|
+
{ method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13952
|
+
{ method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13953
|
+
// C# insecure deserialization — the polymorphic BCL formatters that can
|
|
13954
|
+
// instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
|
|
13955
|
+
// Each of these classes exists only to deserialize and is unsafe on untrusted
|
|
13956
|
+
// input; class-scoped, so no collision with safe JSON APIs (System.Text.Json /
|
|
13957
|
+
// Newtonsoft-default, which are intentionally NOT sinks).
|
|
13833
13958
|
{ method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13834
13959
|
{ method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13960
|
+
{ method: "Deserialize", class: "SoapFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13961
|
+
{ method: "Deserialize", class: "LosFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13962
|
+
{ method: "Deserialize", class: "ObjectStateFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13835
13963
|
// C# XSS — raw HTML output (CWE-79).
|
|
13836
13964
|
{ method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13837
13965
|
{ method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13838
13966
|
{ method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13967
|
+
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13968
|
+
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13969
|
+
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13970
|
+
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
13971
|
+
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
13972
|
+
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
13973
|
+
// memory, so medium (FormatException DoS / unintended arg access), unlike C printf.
|
|
13974
|
+
{ method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13839
13975
|
// C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
|
|
13840
13976
|
// filter is the constructor argument.
|
|
13841
13977
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13856,14 +13992,37 @@ var DEFAULT_SINKS = [
|
|
|
13856
13992
|
// cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
|
|
13857
13993
|
// attacker-controlled query document. Class-scoped (static receiver resolves).
|
|
13858
13994
|
{ method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13995
|
+
// C# log injection — Microsoft.Extensions.Logging `ILogger` (CWE-117,
|
|
13996
|
+
// cognium-ai#318). Only the message TEMPLATE (arg[0]) is the injectable
|
|
13997
|
+
// position; the structured form `LogInformation("… {Id}", id)` keeps taint in
|
|
13998
|
+
// later args (the logger renders them as data), so arg_positions:[0] does not
|
|
13999
|
+
// flag it. Mirrors the Java `Logger` sinks; severity low.
|
|
14000
|
+
{ method: "LogInformation", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14001
|
+
{ method: "LogWarning", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14002
|
+
{ method: "LogError", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14003
|
+
{ method: "LogDebug", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14004
|
+
{ method: "LogCritical", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14005
|
+
{ method: "LogTrace", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14006
|
+
// Generic `ILogger.Log(logLevel, message, …)` — class-scoped (`Log` alone is
|
|
14007
|
+
// too generic); the message is arg[1].
|
|
14008
|
+
{ method: "Log", class: "ILogger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1], languages: ["csharp"] },
|
|
13859
14009
|
// C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
|
|
13860
14010
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13861
14011
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13862
14012
|
{ method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14013
|
+
// XPathNavigator.Select/Evaluate — class-scoped (both names collide with LINQ
|
|
14014
|
+
// `.Select`/`.Evaluate`, so they must resolve to an XPathNavigator receiver).
|
|
14015
|
+
{ method: "Select", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14016
|
+
{ method: "Evaluate", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13863
14017
|
// C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
|
|
13864
14018
|
{ method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13865
14019
|
{ method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13866
14020
|
{ method: "Load", class: "XDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14021
|
+
// `new XmlTextReader(input)` — its DtdProcessing defaults to Parse (resolves
|
|
14022
|
+
// external entities), unlike `XmlReader.Create` (Prohibit). CWE-611,
|
|
14023
|
+
// cognium-ai#318. The 4.7.0 XmlResolver=null / DtdProcessing=Prohibit
|
|
14024
|
+
// hardening still suppresses the safe shape.
|
|
14025
|
+
{ method: "XmlTextReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13867
14026
|
// Python SQLi — asyncpg Connection.*
|
|
13868
14027
|
{ method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
13869
14028
|
{ method: "fetch", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
@@ -15276,6 +15435,57 @@ function isSafeJSChildProcessCall(call, pattern, language) {
|
|
|
15276
15435
|
if (SHELL_PROGRAMS.has(program)) return false;
|
|
15277
15436
|
return true;
|
|
15278
15437
|
}
|
|
15438
|
+
var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
15439
|
+
"sh",
|
|
15440
|
+
"bash",
|
|
15441
|
+
"zsh",
|
|
15442
|
+
"dash",
|
|
15443
|
+
"ash",
|
|
15444
|
+
"ksh",
|
|
15445
|
+
"cmd",
|
|
15446
|
+
"powershell",
|
|
15447
|
+
"pwsh"
|
|
15448
|
+
]);
|
|
15449
|
+
function isConstNonShellExe(raw) {
|
|
15450
|
+
if (!raw) return false;
|
|
15451
|
+
const t = raw.trim();
|
|
15452
|
+
if (!/^@?"[^"]*"$/.test(t)) return false;
|
|
15453
|
+
const program = (t.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
15454
|
+
return !CSHARP_SHELL_PROGRAMS.has(program);
|
|
15455
|
+
}
|
|
15456
|
+
var PSI_CTOR_EXE_RE = /\bnew\s+ProcessStartInfo\s*(?:<[^>]*>)?\s*\(\s*(@?"[^"]*")/;
|
|
15457
|
+
function processStartInfoExe(expr, sourceLines) {
|
|
15458
|
+
const inline = PSI_CTOR_EXE_RE.exec(expr);
|
|
15459
|
+
if (inline) return inline[1];
|
|
15460
|
+
if (sourceLines && /^[A-Za-z_]\w*$/.test(expr.trim())) {
|
|
15461
|
+
const varName = expr.trim();
|
|
15462
|
+
const assignRe = new RegExp(
|
|
15463
|
+
`\\b${varName}\\s*=\\s*new\\s+ProcessStartInfo\\s*(?:<[^>]*>)?\\s*\\(\\s*(@?"[^"]*")`
|
|
15464
|
+
);
|
|
15465
|
+
for (const line of sourceLines) {
|
|
15466
|
+
const m = assignRe.exec(line);
|
|
15467
|
+
if (m) return m[1];
|
|
15468
|
+
}
|
|
15469
|
+
}
|
|
15470
|
+
return null;
|
|
15471
|
+
}
|
|
15472
|
+
function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
15473
|
+
if (language !== "csharp") return false;
|
|
15474
|
+
if (pattern.type !== "command_injection") return false;
|
|
15475
|
+
const method = call.method_name;
|
|
15476
|
+
if (method !== "Start" && method !== "ProcessStartInfo") return false;
|
|
15477
|
+
if (call.arguments.length >= 2) {
|
|
15478
|
+
const fileArg = call.arguments.find((a) => a.position === 0);
|
|
15479
|
+
const raw = fileArg?.literal != null ? String(fileArg.literal) : fileArg?.expression;
|
|
15480
|
+
return isConstNonShellExe(raw);
|
|
15481
|
+
}
|
|
15482
|
+
if (method === "Start" && call.arguments.length === 1) {
|
|
15483
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
15484
|
+
const expr = (arg0?.expression ?? "").trim();
|
|
15485
|
+
return isConstNonShellExe(processStartInfoExe(expr, sourceLines));
|
|
15486
|
+
}
|
|
15487
|
+
return false;
|
|
15488
|
+
}
|
|
15279
15489
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
15280
15490
|
if (language !== "rust") return false;
|
|
15281
15491
|
if (pattern.type !== "command_injection") return false;
|
|
@@ -15531,6 +15741,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15531
15741
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
15532
15742
|
continue;
|
|
15533
15743
|
}
|
|
15744
|
+
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
15745
|
+
continue;
|
|
15746
|
+
}
|
|
15534
15747
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
15535
15748
|
continue;
|
|
15536
15749
|
}
|
|
@@ -16761,8 +16974,8 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
16761
16974
|
network_input: ["sql_injection", "command_injection", "xss", "ssrf", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
16762
16975
|
config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf", "log_injection", "format_string"],
|
|
16763
16976
|
// Servlet init params
|
|
16764
|
-
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
16765
|
-
// Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added; cognium-ai#281 — prompt_injection added
|
|
16977
|
+
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection", "xxe", "deserialization"],
|
|
16978
|
+
// Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added; cognium-ai#281 — prompt_injection added; cognium-ai#317 — xxe/deserialization added (C# xxe/deser sinks are reached via param-seeded sources and were silently dropped, 0% finding conversion)
|
|
16766
16979
|
plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection", "log_injection", "format_string"]
|
|
16767
16980
|
// Plugin/config parameters
|
|
16768
16981
|
};
|
|
@@ -27764,8 +27977,9 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
27764
27977
|
if (JAVA_KEYWORDS.has(lhs)) continue;
|
|
27765
27978
|
if (knownTainted.has(lhs)) continue;
|
|
27766
27979
|
const escaped = (v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27980
|
+
const rhsCode = rhs.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
27767
27981
|
const ref = [...knownTainted].some(
|
|
27768
|
-
(v) => new RegExp(`(?<![\\p{L}\\p{N}_])${escaped(v)}(?![\\p{L}\\p{N}_])`, "u").test(
|
|
27982
|
+
(v) => new RegExp(`(?<![\\p{L}\\p{N}_])${escaped(v)}(?![\\p{L}\\p{N}_])`, "u").test(rhsCode)
|
|
27769
27983
|
);
|
|
27770
27984
|
if (ref) {
|
|
27771
27985
|
derived.set(lhs, i2 + 1);
|
|
@@ -40790,6 +41004,8 @@ function isLikelyCredentialAssignment(line) {
|
|
|
40790
41004
|
if (CRED_DYNAMIC_VALUE_RE.test(value)) return null;
|
|
40791
41005
|
if (value.length < 3) return null;
|
|
40792
41006
|
if (isAllSameChar(value)) return null;
|
|
41007
|
+
if (value.toLowerCase() === name2.toLowerCase()) return null;
|
|
41008
|
+
if (/^(?:https?:\/\/|urn:|xmlns)/i.test(value)) return null;
|
|
40793
41009
|
if (value.length < 12) return null;
|
|
40794
41010
|
if (shannonEntropy(value) < 3.5) return null;
|
|
40795
41011
|
if (charClassDiversity(value) < 2) return null;
|
|
@@ -43346,8 +43562,12 @@ var InsecureDeserializationConfigPass = class {
|
|
|
43346
43562
|
name = "insecure-deserialization-config";
|
|
43347
43563
|
category = "security";
|
|
43348
43564
|
run(ctx) {
|
|
43565
|
+
if (ctx.language === "java") return this.runJava(ctx);
|
|
43566
|
+
if (ctx.language === "csharp") return this.runCSharp(ctx);
|
|
43567
|
+
return { findings: [] };
|
|
43568
|
+
}
|
|
43569
|
+
runJava(ctx) {
|
|
43349
43570
|
const { graph, language } = ctx;
|
|
43350
|
-
if (language !== "java") return { findings: [] };
|
|
43351
43571
|
const file = graph.ir.meta.file;
|
|
43352
43572
|
const findings = [];
|
|
43353
43573
|
for (const call of graph.ir.calls) {
|
|
@@ -43372,12 +43592,46 @@ var InsecureDeserializationConfigPass = class {
|
|
|
43372
43592
|
}
|
|
43373
43593
|
return { findings };
|
|
43374
43594
|
}
|
|
43595
|
+
// C# — Json.NET `TypeNameHandling` set to a value other than `None` (ca#318).
|
|
43596
|
+
// `TypeNameHandling.All/Auto/Objects/Arrays` makes Json.NET honour a `$type`
|
|
43597
|
+
// field in the payload and instantiate the named .NET type, so
|
|
43598
|
+
// `JsonConvert.DeserializeObject(untrusted, settings)` becomes a gadget-chain
|
|
43599
|
+
// RCE (the .NET analogue of XStream's grant-all). The vulnerability is the
|
|
43600
|
+
// constant setting, independent of flow — the secure value `None` never matches.
|
|
43601
|
+
runCSharp(ctx) {
|
|
43602
|
+
const file = ctx.graph.ir.meta.file;
|
|
43603
|
+
const findings = [];
|
|
43604
|
+
const lines = ctx.code.split("\n");
|
|
43605
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
43606
|
+
const m = INSECURE_TYPE_NAME_HANDLING_RE.exec(lines[i2]);
|
|
43607
|
+
if (!m) continue;
|
|
43608
|
+
const line = i2 + 1;
|
|
43609
|
+
const api = `TypeNameHandling = TypeNameHandling.${m[1]}`;
|
|
43610
|
+
findings.push({ line, api });
|
|
43611
|
+
ctx.addFinding({
|
|
43612
|
+
id: `${this.name}-${file}-${line}`,
|
|
43613
|
+
pass: this.name,
|
|
43614
|
+
category: this.category,
|
|
43615
|
+
rule_id: this.name,
|
|
43616
|
+
cwe: "CWE-502",
|
|
43617
|
+
severity: "high",
|
|
43618
|
+
level: "error",
|
|
43619
|
+
message: `Json.NET configured with TypeNameHandling.${m[1]}: a $type field in untrusted JSON can instantiate arbitrary .NET types (deserialization RCE)`,
|
|
43620
|
+
file,
|
|
43621
|
+
line,
|
|
43622
|
+
fix: "Use TypeNameHandling.None (the default), or bind a SerializationBinder that allow-lists the exact types you deserialize.",
|
|
43623
|
+
evidence: { api, language: "csharp" }
|
|
43624
|
+
});
|
|
43625
|
+
}
|
|
43626
|
+
return { findings };
|
|
43627
|
+
}
|
|
43375
43628
|
isPermissiveXStreamConfig(call) {
|
|
43376
43629
|
if (call.method_name !== "addPermission") return false;
|
|
43377
43630
|
const arg0 = call.arguments[0]?.expression;
|
|
43378
43631
|
return typeof arg0 === "string" && ANY_TYPE_PERMISSION_RE.test(arg0);
|
|
43379
43632
|
}
|
|
43380
43633
|
};
|
|
43634
|
+
var INSECURE_TYPE_NAME_HANDLING_RE = /\bTypeNameHandling\s*=\s*(?:Newtonsoft\.Json\.)?TypeNameHandling\.(All|Auto|Objects|Arrays)\b/;
|
|
43381
43635
|
|
|
43382
43636
|
// src/analysis/passes/plaintext-password-storage-pass.ts
|
|
43383
43637
|
function isWriteStorageCall(call, language) {
|
|
@@ -45541,6 +45795,25 @@ function getNodeTypesForLanguage(language) {
|
|
|
45541
45795
|
"selector_expression",
|
|
45542
45796
|
"identifier"
|
|
45543
45797
|
]);
|
|
45798
|
+
case "csharp":
|
|
45799
|
+
return /* @__PURE__ */ new Set([
|
|
45800
|
+
"method_invocation",
|
|
45801
|
+
"object_creation_expression",
|
|
45802
|
+
"class_declaration",
|
|
45803
|
+
"method_declaration",
|
|
45804
|
+
"constructor_declaration",
|
|
45805
|
+
"field_declaration",
|
|
45806
|
+
"import_declaration",
|
|
45807
|
+
"interface_declaration",
|
|
45808
|
+
"enum_declaration",
|
|
45809
|
+
"package_declaration",
|
|
45810
|
+
"local_variable_declaration",
|
|
45811
|
+
// buildCSharpCFG method-like containers
|
|
45812
|
+
"destructor_declaration",
|
|
45813
|
+
"operator_declaration",
|
|
45814
|
+
"local_function_statement",
|
|
45815
|
+
"accessor_declaration"
|
|
45816
|
+
]);
|
|
45544
45817
|
default:
|
|
45545
45818
|
return /* @__PURE__ */ new Set([
|
|
45546
45819
|
"method_invocation",
|