circle-ir 3.46.0 → 3.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/constant-propagation/propagator.d.ts +7 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +81 -41
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/html/html-attribute-security-pass.js +14 -9
- package/dist/analysis/html/html-attribute-security-pass.js.map +1 -1
- package/dist/analysis/html/html-extractor.d.ts.map +1 -1
- package/dist/analysis/html/html-extractor.js +16 -11
- package/dist/analysis/html/html-extractor.js.map +1 -1
- package/dist/analysis/passes/spring4shell-pass.d.ts +58 -0
- package/dist/analysis/passes/spring4shell-pass.d.ts.map +1 -0
- package/dist/analysis/passes/spring4shell-pass.js +252 -0
- package/dist/analysis/passes/spring4shell-pass.js.map +1 -0
- package/dist/analyzer.d.ts +1 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +4 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +315 -73
- package/dist/core/circle-ir-core.cjs +62 -41
- package/dist/core/circle-ir-core.js +62 -41
- package/dist/core/parser.d.ts +10 -0
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +20 -5
- package/dist/core/parser.js.map +1 -1
- package/dist/languages/plugins/base.d.ts.map +1 -1
- package/dist/languages/plugins/base.js +15 -11
- package/dist/languages/plugins/base.js.map +1 -1
- package/dist/languages/plugins/java.d.ts.map +1 -1
- package/dist/languages/plugins/java.js +8 -4
- package/dist/languages/plugins/java.js.map +1 -1
- package/package.json +1 -1
|
@@ -4127,11 +4127,13 @@ function disposeTree(tree) {
|
|
|
4127
4127
|
}
|
|
4128
4128
|
}
|
|
4129
4129
|
function walkTree(node, visitor) {
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
const
|
|
4133
|
-
|
|
4134
|
-
|
|
4130
|
+
const stack = [node];
|
|
4131
|
+
while (stack.length > 0) {
|
|
4132
|
+
const current = stack.pop();
|
|
4133
|
+
visitor(current);
|
|
4134
|
+
for (let i2 = current.childCount - 1; i2 >= 0; i2--) {
|
|
4135
|
+
const child = current.child(i2);
|
|
4136
|
+
if (child) stack.push(child);
|
|
4135
4137
|
}
|
|
4136
4138
|
}
|
|
4137
4139
|
}
|
|
@@ -16032,8 +16034,10 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16032
16034
|
* These are variables declared directly in the class body, not inside methods.
|
|
16033
16035
|
*/
|
|
16034
16036
|
collectClassFields(root) {
|
|
16035
|
-
const
|
|
16036
|
-
|
|
16037
|
+
const stack = [root];
|
|
16038
|
+
while (stack.length > 0) {
|
|
16039
|
+
const n = stack.pop();
|
|
16040
|
+
if (!n) continue;
|
|
16037
16041
|
if (n.type === "class_body") {
|
|
16038
16042
|
for (const child of n.children) {
|
|
16039
16043
|
if (child.type === "field_declaration") {
|
|
@@ -16047,32 +16051,28 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16047
16051
|
}
|
|
16048
16052
|
}
|
|
16049
16053
|
}
|
|
16050
|
-
|
|
16051
|
-
traverse(child, true, true);
|
|
16052
|
-
} else {
|
|
16053
|
-
traverse(child, true, false);
|
|
16054
|
-
}
|
|
16054
|
+
stack.push(child);
|
|
16055
16055
|
}
|
|
16056
|
-
|
|
16056
|
+
continue;
|
|
16057
16057
|
}
|
|
16058
16058
|
for (const child of n.children) {
|
|
16059
|
-
|
|
16059
|
+
stack.push(child);
|
|
16060
16060
|
}
|
|
16061
|
-
}
|
|
16062
|
-
traverse(root, false, false);
|
|
16061
|
+
}
|
|
16063
16062
|
}
|
|
16064
16063
|
findAllMethods(node) {
|
|
16065
16064
|
const methods = [];
|
|
16066
|
-
const
|
|
16067
|
-
|
|
16065
|
+
const stack = [node];
|
|
16066
|
+
while (stack.length > 0) {
|
|
16067
|
+
const n = stack.pop();
|
|
16068
|
+
if (!n) continue;
|
|
16068
16069
|
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
16069
16070
|
methods.push(n);
|
|
16070
16071
|
}
|
|
16071
16072
|
for (const child of n.children) {
|
|
16072
|
-
if (child)
|
|
16073
|
+
if (child) stack.push(child);
|
|
16073
16074
|
}
|
|
16074
|
-
}
|
|
16075
|
-
traverse(node);
|
|
16075
|
+
}
|
|
16076
16076
|
return methods;
|
|
16077
16077
|
}
|
|
16078
16078
|
getMethodName(method) {
|
|
@@ -16123,9 +16123,24 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16123
16123
|
// AST Visitor
|
|
16124
16124
|
// ===========================================================================
|
|
16125
16125
|
visit(node) {
|
|
16126
|
+
const stack = [node];
|
|
16127
|
+
while (stack.length > 0) {
|
|
16128
|
+
const current = stack.pop();
|
|
16129
|
+
if (this.visitOne(current)) continue;
|
|
16130
|
+
for (let i2 = current.children.length - 1; i2 >= 0; i2--) {
|
|
16131
|
+
stack.push(current.children[i2]);
|
|
16132
|
+
}
|
|
16133
|
+
}
|
|
16134
|
+
}
|
|
16135
|
+
/**
|
|
16136
|
+
* Visit a single node. Returns true if the handler already descended into
|
|
16137
|
+
* children (and the caller should NOT push them), false to fall through to
|
|
16138
|
+
* the default pre-order descent.
|
|
16139
|
+
*/
|
|
16140
|
+
visitOne(node) {
|
|
16126
16141
|
const line = getNodeLine(node);
|
|
16127
16142
|
if (this.unreachableLines.has(line)) {
|
|
16128
|
-
return;
|
|
16143
|
+
return true;
|
|
16129
16144
|
}
|
|
16130
16145
|
if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
|
|
16131
16146
|
this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
|
|
@@ -16134,43 +16149,41 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16134
16149
|
case "method_declaration":
|
|
16135
16150
|
case "constructor_declaration":
|
|
16136
16151
|
this.handleMethodDeclaration(node);
|
|
16137
|
-
return;
|
|
16152
|
+
return true;
|
|
16138
16153
|
// Don't visit children directly, handleMethodDeclaration does it
|
|
16139
16154
|
case "local_variable_declaration":
|
|
16140
16155
|
this.handleVariableDeclaration(node);
|
|
16141
|
-
|
|
16156
|
+
return false;
|
|
16142
16157
|
case "assignment_expression":
|
|
16143
16158
|
this.handleAssignment(node);
|
|
16144
|
-
|
|
16159
|
+
return false;
|
|
16145
16160
|
case "update_expression":
|
|
16146
16161
|
this.handleUpdateExpression(node);
|
|
16147
|
-
|
|
16162
|
+
return false;
|
|
16148
16163
|
case "if_statement":
|
|
16149
16164
|
this.handleIfStatement(node);
|
|
16150
|
-
return;
|
|
16165
|
+
return true;
|
|
16151
16166
|
case "switch_expression":
|
|
16152
16167
|
case "switch_statement":
|
|
16153
16168
|
this.handleSwitch(node);
|
|
16154
|
-
return;
|
|
16169
|
+
return true;
|
|
16155
16170
|
case "ternary_expression":
|
|
16156
16171
|
this.handleTernary(node);
|
|
16157
|
-
|
|
16172
|
+
return false;
|
|
16158
16173
|
case "expression_statement":
|
|
16159
16174
|
this.handleExpressionStatement(node);
|
|
16160
|
-
|
|
16175
|
+
return false;
|
|
16161
16176
|
case "for_statement":
|
|
16162
16177
|
case "enhanced_for_statement":
|
|
16163
16178
|
case "while_statement":
|
|
16164
16179
|
case "do_statement":
|
|
16165
16180
|
this.handleLoopStatement(node);
|
|
16166
|
-
return;
|
|
16181
|
+
return true;
|
|
16167
16182
|
case "synchronized_statement":
|
|
16168
16183
|
this.handleSynchronizedStatement(node);
|
|
16169
|
-
return;
|
|
16184
|
+
return true;
|
|
16170
16185
|
default:
|
|
16171
|
-
|
|
16172
|
-
this.visit(child);
|
|
16173
|
-
}
|
|
16186
|
+
return false;
|
|
16174
16187
|
}
|
|
16175
16188
|
}
|
|
16176
16189
|
/**
|
|
@@ -16938,6 +16951,19 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16938
16951
|
return null;
|
|
16939
16952
|
}
|
|
16940
16953
|
isTaintedExpression(node) {
|
|
16954
|
+
const stack = [node];
|
|
16955
|
+
while (stack.length > 0) {
|
|
16956
|
+
const current = stack.pop();
|
|
16957
|
+
const result = this.isTaintedExpressionStep(current);
|
|
16958
|
+
if (result === true) return true;
|
|
16959
|
+
if (result === false) continue;
|
|
16960
|
+
for (let i2 = current.children.length - 1; i2 >= 0; i2--) {
|
|
16961
|
+
stack.push(current.children[i2]);
|
|
16962
|
+
}
|
|
16963
|
+
}
|
|
16964
|
+
return false;
|
|
16965
|
+
}
|
|
16966
|
+
isTaintedExpressionStep(node) {
|
|
16941
16967
|
const text = getNodeText2(node, this.source);
|
|
16942
16968
|
if (node.type === "method_invocation") {
|
|
16943
16969
|
const nameNode = node.childForFieldName("name");
|
|
@@ -17197,12 +17223,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
17197
17223
|
}
|
|
17198
17224
|
return isTainted;
|
|
17199
17225
|
}
|
|
17200
|
-
|
|
17201
|
-
if (this.isTaintedExpression(child)) {
|
|
17202
|
-
return true;
|
|
17203
|
-
}
|
|
17204
|
-
}
|
|
17205
|
-
return false;
|
|
17226
|
+
return void 0;
|
|
17206
17227
|
}
|
|
17207
17228
|
checkCollectionTaint(node) {
|
|
17208
17229
|
const objectNode = node.childForFieldName("object");
|
|
@@ -17526,19 +17547,17 @@ var BaseLanguagePlugin = class {
|
|
|
17526
17547
|
*/
|
|
17527
17548
|
findNodes(root, type) {
|
|
17528
17549
|
const nodes = [];
|
|
17529
|
-
const
|
|
17530
|
-
|
|
17531
|
-
|
|
17532
|
-
|
|
17550
|
+
const stack = [root];
|
|
17551
|
+
while (stack.length > 0) {
|
|
17552
|
+
const node = stack.pop();
|
|
17553
|
+
if (node.type === type) {
|
|
17554
|
+
nodes.push(node);
|
|
17533
17555
|
}
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
} while (cursor.gotoNextSibling());
|
|
17538
|
-
cursor.gotoParent();
|
|
17556
|
+
for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
|
|
17557
|
+
const child = node.child(i2);
|
|
17558
|
+
if (child) stack.push(child);
|
|
17539
17559
|
}
|
|
17540
|
-
}
|
|
17541
|
-
visit();
|
|
17560
|
+
}
|
|
17542
17561
|
return nodes;
|
|
17543
17562
|
}
|
|
17544
17563
|
/**
|
|
@@ -17894,16 +17913,17 @@ var JavaPlugin = class extends BaseLanguagePlugin {
|
|
|
17894
17913
|
}
|
|
17895
17914
|
}
|
|
17896
17915
|
};
|
|
17897
|
-
const
|
|
17916
|
+
const stack = [tree.rootNode];
|
|
17917
|
+
while (stack.length > 0) {
|
|
17918
|
+
const node = stack.pop();
|
|
17898
17919
|
if (node.type === "field_declaration" || node.type === "local_variable_declaration") {
|
|
17899
17920
|
collectDecl(node);
|
|
17900
17921
|
}
|
|
17901
17922
|
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
17902
17923
|
const child = node.child(i2);
|
|
17903
|
-
if (child)
|
|
17924
|
+
if (child) stack.push(child);
|
|
17904
17925
|
}
|
|
17905
|
-
}
|
|
17906
|
-
walk(tree.rootNode);
|
|
17926
|
+
}
|
|
17907
17927
|
this._typeMapCache.set(tree, map);
|
|
17908
17928
|
return map;
|
|
17909
17929
|
}
|
|
@@ -20700,16 +20720,18 @@ function extractHtmlContent(rootNode) {
|
|
|
20700
20720
|
return { scriptBlocks, eventHandlers };
|
|
20701
20721
|
}
|
|
20702
20722
|
function walkNode(node, scriptBlocks, eventHandlers) {
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20723
|
+
const stack = [node];
|
|
20724
|
+
while (stack.length > 0) {
|
|
20725
|
+
const current = stack.pop();
|
|
20726
|
+
if (current.type === "script_element") {
|
|
20727
|
+
extractScriptBlock(current, scriptBlocks);
|
|
20728
|
+
}
|
|
20729
|
+
if (current.type === "element" || current.type === "self_closing_tag") {
|
|
20730
|
+
extractEventHandlers(current, eventHandlers);
|
|
20731
|
+
}
|
|
20732
|
+
for (let i2 = current.childCount - 1; i2 >= 0; i2--) {
|
|
20733
|
+
const child = current.child(i2);
|
|
20734
|
+
if (child) stack.push(child);
|
|
20713
20735
|
}
|
|
20714
20736
|
}
|
|
20715
20737
|
}
|
|
@@ -20806,13 +20828,15 @@ function runHtmlAttributeSecurityChecks(rootNode, filePath) {
|
|
|
20806
20828
|
return findings;
|
|
20807
20829
|
}
|
|
20808
20830
|
function walkForSecurityChecks(node, filePath, findings) {
|
|
20809
|
-
|
|
20810
|
-
|
|
20811
|
-
|
|
20812
|
-
|
|
20813
|
-
|
|
20814
|
-
|
|
20815
|
-
|
|
20831
|
+
const stack = [node];
|
|
20832
|
+
while (stack.length > 0) {
|
|
20833
|
+
const current = stack.pop();
|
|
20834
|
+
if (current.type === "element" || current.type === "self_closing_tag" || current.type === "script_element" || current.type === "style_element") {
|
|
20835
|
+
checkElement(current, filePath, findings);
|
|
20836
|
+
}
|
|
20837
|
+
for (let i2 = current.childCount - 1; i2 >= 0; i2--) {
|
|
20838
|
+
const child = current.child(i2);
|
|
20839
|
+
if (child) stack.push(child);
|
|
20816
20840
|
}
|
|
20817
20841
|
}
|
|
20818
20842
|
}
|
|
@@ -26462,6 +26486,223 @@ var ScanSecretsPass = class {
|
|
|
26462
26486
|
}
|
|
26463
26487
|
};
|
|
26464
26488
|
|
|
26489
|
+
// src/analysis/passes/spring4shell-pass.ts
|
|
26490
|
+
var CONTROLLER_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
26491
|
+
"Controller",
|
|
26492
|
+
"RestController",
|
|
26493
|
+
"ControllerAdvice",
|
|
26494
|
+
"RestControllerAdvice"
|
|
26495
|
+
]);
|
|
26496
|
+
var ROUTE_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
26497
|
+
"RequestMapping",
|
|
26498
|
+
"GetMapping",
|
|
26499
|
+
"PostMapping",
|
|
26500
|
+
"PutMapping",
|
|
26501
|
+
"DeleteMapping",
|
|
26502
|
+
"PatchMapping"
|
|
26503
|
+
]);
|
|
26504
|
+
var BINDING_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
26505
|
+
"RequestBody",
|
|
26506
|
+
// JSON via Jackson (no DataBinder)
|
|
26507
|
+
"RequestParam",
|
|
26508
|
+
// scalar binding
|
|
26509
|
+
"PathVariable",
|
|
26510
|
+
// scalar binding
|
|
26511
|
+
"RequestHeader",
|
|
26512
|
+
// scalar binding
|
|
26513
|
+
"CookieValue",
|
|
26514
|
+
// scalar binding
|
|
26515
|
+
"MatrixVariable",
|
|
26516
|
+
// scalar binding
|
|
26517
|
+
"ModelAttribute",
|
|
26518
|
+
// explicit form binding — user opted in
|
|
26519
|
+
"Valid",
|
|
26520
|
+
// typically paired with @RequestBody / @ModelAttribute
|
|
26521
|
+
"Validated",
|
|
26522
|
+
"RequestPart",
|
|
26523
|
+
// multipart, explicit
|
|
26524
|
+
"SessionAttribute",
|
|
26525
|
+
"RequestAttribute"
|
|
26526
|
+
]);
|
|
26527
|
+
var FRAMEWORK_PARAM_TYPES = /* @__PURE__ */ new Set([
|
|
26528
|
+
// Servlet / HTTP
|
|
26529
|
+
"HttpServletRequest",
|
|
26530
|
+
"HttpServletResponse",
|
|
26531
|
+
"ServletRequest",
|
|
26532
|
+
"ServletResponse",
|
|
26533
|
+
"HttpSession",
|
|
26534
|
+
"ServletContext",
|
|
26535
|
+
"Cookie",
|
|
26536
|
+
// Spring MVC plumbing
|
|
26537
|
+
"Model",
|
|
26538
|
+
"ModelMap",
|
|
26539
|
+
"ModelAndView",
|
|
26540
|
+
"Map",
|
|
26541
|
+
"BindingResult",
|
|
26542
|
+
"Errors",
|
|
26543
|
+
"RedirectAttributes",
|
|
26544
|
+
"SessionStatus",
|
|
26545
|
+
"WebRequest",
|
|
26546
|
+
"NativeWebRequest",
|
|
26547
|
+
"ServletWebRequest",
|
|
26548
|
+
"UriComponentsBuilder",
|
|
26549
|
+
"UriBuilder",
|
|
26550
|
+
"HttpEntity",
|
|
26551
|
+
"RequestEntity",
|
|
26552
|
+
"ResponseEntity",
|
|
26553
|
+
"HttpHeaders",
|
|
26554
|
+
"InputStream",
|
|
26555
|
+
"OutputStream",
|
|
26556
|
+
"Reader",
|
|
26557
|
+
"Writer",
|
|
26558
|
+
// Reactive
|
|
26559
|
+
"ServerHttpRequest",
|
|
26560
|
+
"ServerHttpResponse",
|
|
26561
|
+
"ServerWebExchange",
|
|
26562
|
+
// Security / locale
|
|
26563
|
+
"Principal",
|
|
26564
|
+
"Authentication",
|
|
26565
|
+
"Locale",
|
|
26566
|
+
"TimeZone",
|
|
26567
|
+
"ZoneId",
|
|
26568
|
+
// Multipart
|
|
26569
|
+
"MultipartFile",
|
|
26570
|
+
"Part",
|
|
26571
|
+
// Misc
|
|
26572
|
+
"TimeZone"
|
|
26573
|
+
]);
|
|
26574
|
+
var SIMPLE_JAVA_TYPES = /* @__PURE__ */ new Set([
|
|
26575
|
+
// Primitives
|
|
26576
|
+
"boolean",
|
|
26577
|
+
"byte",
|
|
26578
|
+
"char",
|
|
26579
|
+
"short",
|
|
26580
|
+
"int",
|
|
26581
|
+
"long",
|
|
26582
|
+
"float",
|
|
26583
|
+
"double",
|
|
26584
|
+
"void",
|
|
26585
|
+
// Boxed primitives
|
|
26586
|
+
"Boolean",
|
|
26587
|
+
"Byte",
|
|
26588
|
+
"Character",
|
|
26589
|
+
"Short",
|
|
26590
|
+
"Integer",
|
|
26591
|
+
"Long",
|
|
26592
|
+
"Float",
|
|
26593
|
+
"Double",
|
|
26594
|
+
// Standard scalar-ish types
|
|
26595
|
+
"String",
|
|
26596
|
+
"CharSequence",
|
|
26597
|
+
"BigInteger",
|
|
26598
|
+
"BigDecimal",
|
|
26599
|
+
"UUID",
|
|
26600
|
+
// Date/time (Spring binds these via ConversionService, not DataBinder)
|
|
26601
|
+
"Date",
|
|
26602
|
+
"Calendar",
|
|
26603
|
+
"Instant",
|
|
26604
|
+
"LocalDate",
|
|
26605
|
+
"LocalTime",
|
|
26606
|
+
"LocalDateTime",
|
|
26607
|
+
"OffsetDateTime",
|
|
26608
|
+
"OffsetTime",
|
|
26609
|
+
"ZonedDateTime",
|
|
26610
|
+
"Duration",
|
|
26611
|
+
"Period",
|
|
26612
|
+
// Collections — first-class scalar list binding (?names=a&names=b)
|
|
26613
|
+
"List",
|
|
26614
|
+
"Set",
|
|
26615
|
+
"Collection",
|
|
26616
|
+
"Iterable",
|
|
26617
|
+
"Optional"
|
|
26618
|
+
// Arrays (handled by suffix check below)
|
|
26619
|
+
]);
|
|
26620
|
+
var Spring4ShellPass = class {
|
|
26621
|
+
name = "spring4shell";
|
|
26622
|
+
category = "security";
|
|
26623
|
+
run(ctx) {
|
|
26624
|
+
const { graph, language } = ctx;
|
|
26625
|
+
if (language !== "java") {
|
|
26626
|
+
return { controllerMethodsScanned: 0, findingsEmitted: 0 };
|
|
26627
|
+
}
|
|
26628
|
+
const file = graph.ir.meta.file;
|
|
26629
|
+
let scanned = 0;
|
|
26630
|
+
let emitted = 0;
|
|
26631
|
+
for (const type of graph.ir.types) {
|
|
26632
|
+
if (!isController(type)) continue;
|
|
26633
|
+
for (const method of type.methods) {
|
|
26634
|
+
if (!isRouteHandler(method)) continue;
|
|
26635
|
+
scanned++;
|
|
26636
|
+
for (const param of method.parameters) {
|
|
26637
|
+
if (!isVulnerableParameter(param)) continue;
|
|
26638
|
+
ctx.addFinding({
|
|
26639
|
+
id: `${this.name}-${file}-${method.start_line}-${param.name}`,
|
|
26640
|
+
pass: this.name,
|
|
26641
|
+
category: this.category,
|
|
26642
|
+
rule_id: this.name,
|
|
26643
|
+
cwe: "CWE-94",
|
|
26644
|
+
severity: "high",
|
|
26645
|
+
level: "error",
|
|
26646
|
+
message: `Spring MVC controller method '${type.name}.${method.name}' binds parameter '${param.name}' of type '${param.type ?? "?"}' via implicit form-data binding (no @RequestBody / @RequestParam / @ModelAttribute) \u2014 vulnerable to Spring4Shell (CVE-2022-22965) class-graph RCE on Spring < 5.3.18 / 5.2.20`,
|
|
26647
|
+
file,
|
|
26648
|
+
line: param.line ?? method.start_line,
|
|
26649
|
+
fix: "Annotate the parameter with @RequestBody (JSON) or @ModelAttribute + @InitBinder/setAllowedFields whitelisting, upgrade Spring to \u2265 5.3.18 / 5.2.20, and ensure JDK is patched.",
|
|
26650
|
+
evidence: {
|
|
26651
|
+
controller_class: type.name,
|
|
26652
|
+
controller_annotations: type.annotations,
|
|
26653
|
+
method: method.name,
|
|
26654
|
+
method_annotations: method.annotations,
|
|
26655
|
+
parameter_name: param.name,
|
|
26656
|
+
parameter_type: param.type
|
|
26657
|
+
}
|
|
26658
|
+
});
|
|
26659
|
+
emitted++;
|
|
26660
|
+
}
|
|
26661
|
+
}
|
|
26662
|
+
}
|
|
26663
|
+
return { controllerMethodsScanned: scanned, findingsEmitted: emitted };
|
|
26664
|
+
}
|
|
26665
|
+
};
|
|
26666
|
+
function annotationHead(annotation) {
|
|
26667
|
+
const parenIdx = annotation.indexOf("(");
|
|
26668
|
+
return parenIdx >= 0 ? annotation.slice(0, parenIdx) : annotation;
|
|
26669
|
+
}
|
|
26670
|
+
function hasAnnotation(annotations, names) {
|
|
26671
|
+
for (const a of annotations) {
|
|
26672
|
+
if (names.has(annotationHead(a))) return true;
|
|
26673
|
+
}
|
|
26674
|
+
return false;
|
|
26675
|
+
}
|
|
26676
|
+
function isController(type) {
|
|
26677
|
+
return hasAnnotation(type.annotations, CONTROLLER_ANNOTATIONS);
|
|
26678
|
+
}
|
|
26679
|
+
function isRouteHandler(method) {
|
|
26680
|
+
return hasAnnotation(method.annotations, ROUTE_ANNOTATIONS);
|
|
26681
|
+
}
|
|
26682
|
+
function isVulnerableParameter(param) {
|
|
26683
|
+
if (hasAnnotation(param.annotations, BINDING_ANNOTATIONS)) return false;
|
|
26684
|
+
if (!param.type) return false;
|
|
26685
|
+
const type = stripGenerics2(param.type).trim();
|
|
26686
|
+
if (!type) return false;
|
|
26687
|
+
if (type.endsWith("[]")) {
|
|
26688
|
+
const elem = type.slice(0, -2).trim();
|
|
26689
|
+
return !SIMPLE_JAVA_TYPES.has(elem) && isPotentialPojo(elem);
|
|
26690
|
+
}
|
|
26691
|
+
if (SIMPLE_JAVA_TYPES.has(type)) return false;
|
|
26692
|
+
if (FRAMEWORK_PARAM_TYPES.has(type)) return false;
|
|
26693
|
+
if (!isPotentialPojo(type)) return false;
|
|
26694
|
+
return true;
|
|
26695
|
+
}
|
|
26696
|
+
function stripGenerics2(type) {
|
|
26697
|
+
const ltIdx = type.indexOf("<");
|
|
26698
|
+
return ltIdx >= 0 ? type.slice(0, ltIdx) : type;
|
|
26699
|
+
}
|
|
26700
|
+
function isPotentialPojo(type) {
|
|
26701
|
+
if (type.length === 0) return false;
|
|
26702
|
+
const first = type.charCodeAt(0);
|
|
26703
|
+
return first >= 65 && first <= 90;
|
|
26704
|
+
}
|
|
26705
|
+
|
|
26465
26706
|
// src/analysis/metrics/passes/size-metrics-pass.ts
|
|
26466
26707
|
var SizeMetricsPass = class {
|
|
26467
26708
|
name = "size-metrics";
|
|
@@ -27357,6 +27598,7 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
27357
27598
|
if (!disabledPasses.has("god-class")) pipeline.add(new GodClassPass());
|
|
27358
27599
|
if (!disabledPasses.has("naming-convention")) pipeline.add(new NamingConventionPass(passOpts.namingConvention));
|
|
27359
27600
|
if (!disabledPasses.has("security-headers")) pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
|
|
27601
|
+
if (!disabledPasses.has("spring4shell")) pipeline.add(new Spring4ShellPass());
|
|
27360
27602
|
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
27361
27603
|
const sinkFilter = results.get("sink-filter");
|
|
27362
27604
|
const interProc = results.get("interprocedural");
|