From 4f29e66d900ed311c668c36816b985ca8493fbcc Mon Sep 17 00:00:00 2001 From: CrackTC Date: Mon, 13 Oct 2025 19:36:22 +0800 Subject: [PATCH 01/14] feat: add go-restful entrypoint collection support --- resource/checker/checker-config.json | 5 ++ resource/checker/checker-pack-config.json | 1 + .../go/restful-entrypoint-collect-checker.js | 54 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/checker/taint/go/restful-entrypoint-collect-checker.js diff --git a/resource/checker/checker-config.json b/resource/checker/checker-config.json index bcdb4b57..30997c1a 100644 --- a/resource/checker/checker-config.json +++ b/resource/checker/checker-config.json @@ -93,5 +93,10 @@ "checkerPath": "src/checker/taint/test-taint-checker.js", "description": "taint_flow_test,回归测试使用,不推荐外部使用", "demoRuleConfigPath": "resource/example-rule-config/rule_config_test.json" + }, + { + "checkerId": "RestfulEntrypointCollectChecker", + "checkerPath": "src/checker/taint/go/restful-entrypoint-collect-checker.js", + "description": "go-restful entrypoint采集以及框架source添加" } ] diff --git a/resource/checker/checker-pack-config.json b/resource/checker/checker-pack-config.json index e46d7552..737426ae 100644 --- a/resource/checker/checker-pack-config.json +++ b/resource/checker/checker-pack-config.json @@ -5,6 +5,7 @@ "taint_flow_gin_input", "taint_flow_go_input", "cobra.Command-builtIn", + "RestfulEntrypointCollectChecker", "gorilla-mux-entrypoint-collect-checker", "gRpc-entryPoint-collect-checker", "go-main-entryPoints-collection", diff --git a/src/checker/taint/go/restful-entrypoint-collect-checker.js b/src/checker/taint/go/restful-entrypoint-collect-checker.js new file mode 100644 index 00000000..95848c26 --- /dev/null +++ b/src/checker/taint/go/restful-entrypoint-collect-checker.js @@ -0,0 +1,54 @@ +const config = require("../../../config"); + +const RouteRegistryProperty = ["Filter", "To", "If"] +const RouteRegistryObject = ["github.com/emicklei/go-restful/v3.WebService"] +const IntroduceTaint = require("../common-kit/source-util"); +const Checker = require("../../common/checker"); +const { completeEntryPoint } = require("./entry-points-util"); + +const processedRouteRegistry = new Set(); + +class RestfulEntrypointCollectChecker extends Checker { + constructor(resultManager) { + super(resultManager, "go-restful-entryPoints-collect-checker"); + } + + triggerAtFunctionCallBefore(analyzer, scope, node, state, info) { + const { fclos, argvalues } = info; + + this.collectRouteRegistry(node, fclos, argvalues, scope, info); + } + + triggerAtSymbolInterpretOfEntryPointAfter(analyzer, scope, node, state, info) { + if (info?.entryPoint.functionName === 'main') processedRouteRegistry.clear(); + } + + collectRouteRegistry(callExpNode, calleeFClos, argValues, scope, info) { + const { analyzer, state } = info; + if (config.entryPointMode === 'ONLY_CUSTOM') return; + if (!(calleeFClos && calleeFClos.object && calleeFClos.property)) return; + const { object, property } = calleeFClos; + if (!object._qid || !property.name) return; + const objectQid = object._qid; + const propertyName = property.name; + if ( + RouteRegistryObject.some((prefix) => objectQid.startsWith(prefix)) && + RouteRegistryProperty.includes(propertyName) + ) { + if (argValues.length < 1) return; + const arg0 = argValues[0]; + + if (arg0?.vtype === 'fclos' && arg0?.ast.loc) { + const hash = JSON.stringify(arg0.ast.loc) + if (!processedRouteRegistry.has(hash)) { + processedRouteRegistry.add(hash) + IntroduceTaint.introduceFuncArgTaintBySelfCollection(arg0, state, analyzer, '0', 'GO_INPUT') + const entryPoint = completeEntryPoint(arg0) + analyzer.entryPoints.push(entryPoint) + } + } + } + } +} + +module.exports = RestfulEntrypointCollectChecker; \ No newline at end of file From 95fa4b5e9b14766fc345825b505cd770d011b612 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Wed, 29 Oct 2025 11:18:00 +0800 Subject: [PATCH 02/14] feat: adapt go-restful v2 --- src/checker/taint/go/restful-entrypoint-collect-checker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checker/taint/go/restful-entrypoint-collect-checker.js b/src/checker/taint/go/restful-entrypoint-collect-checker.js index 95848c26..0c5908a7 100644 --- a/src/checker/taint/go/restful-entrypoint-collect-checker.js +++ b/src/checker/taint/go/restful-entrypoint-collect-checker.js @@ -1,7 +1,7 @@ const config = require("../../../config"); const RouteRegistryProperty = ["Filter", "To", "If"] -const RouteRegistryObject = ["github.com/emicklei/go-restful/v3.WebService"] +const RouteRegistryObject = ["github.com/emicklei/go-restful/v3.WebService", "github.com/emicklei/go-restful.WebService"] const IntroduceTaint = require("../common-kit/source-util"); const Checker = require("../../common/checker"); const { completeEntryPoint } = require("./entry-points-util"); From 5769b04fd0eadc053800f4afb092cf30520ac90e Mon Sep 17 00:00:00 2001 From: Soraccc Date: Wed, 29 Oct 2025 12:10:31 +0800 Subject: [PATCH 03/14] fix: checker naming --- resource/checker/checker-config.json | 2 +- resource/checker/checker-pack-config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resource/checker/checker-config.json b/resource/checker/checker-config.json index e5faa662..e15acdd7 100644 --- a/resource/checker/checker-config.json +++ b/resource/checker/checker-config.json @@ -137,7 +137,7 @@ "demoRuleConfigPath": "resource/example-rule-config/rule_config_test.json" }, { - "checkerId": "RestfulEntrypointCollectChecker", + "checkerId": "go-restful-entryPoints-collect-checker", "checkerPath": "src/checker/taint/go/restful-entrypoint-collect-checker.js", "description": "go-restful entrypoint采集以及框架source添加" }, diff --git a/resource/checker/checker-pack-config.json b/resource/checker/checker-pack-config.json index 9a04096d..95d42899 100644 --- a/resource/checker/checker-pack-config.json +++ b/resource/checker/checker-pack-config.json @@ -5,7 +5,7 @@ "taint_flow_gin_input", "taint_flow_go_input", "cobra.Command-builtIn", - "RestfulEntrypointCollectChecker", + "go-restful-entryPoints-collect-checker", "gorilla-mux-entrypoint-collect-checker", "gRpc-entryPoint-collect-checker", "go-main-entryPoints-collection", From cd9a8a42c28cc283f08746cc4473666b887b7845 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Thu, 30 Oct 2025 15:45:15 +0800 Subject: [PATCH 04/14] refactor: migrate to typescript --- resource/checker/checker-config.json | 2 +- ...t-checker.js => restful-entrypoint-collect-checker.ts} | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/checker/taint/go/{restful-entrypoint-collect-checker.js => restful-entrypoint-collect-checker.ts} (85%) diff --git a/resource/checker/checker-config.json b/resource/checker/checker-config.json index adaeedaa..f8bc8519 100644 --- a/resource/checker/checker-config.json +++ b/resource/checker/checker-config.json @@ -138,7 +138,7 @@ }, { "checkerId": "go-restful-entryPoints-collect-checker", - "checkerPath": "src/checker/taint/go/restful-entrypoint-collect-checker.js", + "checkerPath": "checker/taint/go/restful-entrypoint-collect-checker.js", "description": "go-restful entrypoint采集以及框架source添加" }, { diff --git a/src/checker/taint/go/restful-entrypoint-collect-checker.js b/src/checker/taint/go/restful-entrypoint-collect-checker.ts similarity index 85% rename from src/checker/taint/go/restful-entrypoint-collect-checker.js rename to src/checker/taint/go/restful-entrypoint-collect-checker.ts index 0c5908a7..c41fea17 100644 --- a/src/checker/taint/go/restful-entrypoint-collect-checker.js +++ b/src/checker/taint/go/restful-entrypoint-collect-checker.ts @@ -9,21 +9,21 @@ const { completeEntryPoint } = require("./entry-points-util"); const processedRouteRegistry = new Set(); class RestfulEntrypointCollectChecker extends Checker { - constructor(resultManager) { + constructor(resultManager: any) { super(resultManager, "go-restful-entryPoints-collect-checker"); } - triggerAtFunctionCallBefore(analyzer, scope, node, state, info) { + triggerAtFunctionCallBefore(analyzer: any, scope: any, node: any, state: any, info: any) { const { fclos, argvalues } = info; this.collectRouteRegistry(node, fclos, argvalues, scope, info); } - triggerAtSymbolInterpretOfEntryPointAfter(analyzer, scope, node, state, info) { + triggerAtSymbolInterpretOfEntryPointAfter(analyzer: any, scope: any, node: any, state: any, info: any) { if (info?.entryPoint.functionName === 'main') processedRouteRegistry.clear(); } - collectRouteRegistry(callExpNode, calleeFClos, argValues, scope, info) { + collectRouteRegistry(callExpNode: any, calleeFClos: any, argValues: any, scope: any, info: any) { const { analyzer, state } = info; if (config.entryPointMode === 'ONLY_CUSTOM') return; if (!(calleeFClos && calleeFClos.object && calleeFClos.property)) return; From d28832f3f2b551ee214d8438c69d1d849fd3b113 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Thu, 30 Oct 2025 20:47:44 +0800 Subject: [PATCH 05/14] fix: checker path suffix js -> ts --- resource/checker/checker-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/checker/checker-config.json b/resource/checker/checker-config.json index f8bc8519..4923d244 100644 --- a/resource/checker/checker-config.json +++ b/resource/checker/checker-config.json @@ -138,7 +138,7 @@ }, { "checkerId": "go-restful-entryPoints-collect-checker", - "checkerPath": "checker/taint/go/restful-entrypoint-collect-checker.js", + "checkerPath": "checker/taint/go/restful-entrypoint-collect-checker.ts", "description": "go-restful entrypoint采集以及框架source添加" }, { From 1e0617b6c183eaca91c3e640f255a2bd61ef1973 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Wed, 5 Nov 2025 15:29:53 +0800 Subject: [PATCH 06/14] feat: go echo checker --- resource/checker/checker-config.json | 5 + resource/checker/checker-pack-config.json | 1 + .../go/echo-entrypoint-collect-checker.ts | 144 ++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 src/checker/taint/go/echo-entrypoint-collect-checker.ts diff --git a/resource/checker/checker-config.json b/resource/checker/checker-config.json index 9bc685f5..bc64eeb4 100644 --- a/resource/checker/checker-config.json +++ b/resource/checker/checker-config.json @@ -141,6 +141,11 @@ "checkerPath": "checker/taint/go/restful-entrypoint-collect-checker.ts", "description": "go-restful entrypoint采集以及框架source添加" }, + { + "checkerId": "echo-entrypoint-collect-checker", + "checkerPath": "checker/taint/go/echo-entrypoint-collect-checker.ts", + "description": "echo entrypoint采集以及框架source添加" + }, { "checkerId": "get_file_ast", "checkerPath": "checker/sdk/get-file-ast-checker.ts", diff --git a/resource/checker/checker-pack-config.json b/resource/checker/checker-pack-config.json index 95d42899..ecd2578c 100644 --- a/resource/checker/checker-pack-config.json +++ b/resource/checker/checker-pack-config.json @@ -6,6 +6,7 @@ "taint_flow_go_input", "cobra.Command-builtIn", "go-restful-entryPoints-collect-checker", + "echo-entrypoint-collect-checker", "gorilla-mux-entrypoint-collect-checker", "gRpc-entryPoint-collect-checker", "go-main-entryPoints-collection", diff --git a/src/checker/taint/go/echo-entrypoint-collect-checker.ts b/src/checker/taint/go/echo-entrypoint-collect-checker.ts new file mode 100644 index 00000000..5573a116 --- /dev/null +++ b/src/checker/taint/go/echo-entrypoint-collect-checker.ts @@ -0,0 +1,144 @@ +const config = require("../../../config"); + +const RouteRegistryObject = ["github.com/labstack/echo/v4.New()"]; +const IntroduceTaint = require("../common-kit/source-util"); +const Checker = require("../../common/checker"); +const { completeEntryPoint } = require("./entry-points-util"); + +const processedRouteRegistry = new Set(); + +class EchoEntrypointCollectChecker extends Checker { + constructor(resultManager: any) { + super(resultManager, "echo-entrypoint-collect-checker"); + } + + triggerAtFunctionCallBefore(analyzer: any, scope: any, node: any, state: any, info: any) { + const { fclos, argvalues } = info; + this.collectRouteRegistry(node, fclos, argvalues, scope, info); + } + + triggerAtSymbolInterpretOfEntryPointAfter(analyzer: any, scope: any, node: any, state: any, info: any) { + if (info?.entryPoint.functionName === "main") processedRouteRegistry.clear(); + } + + collectRouteRegistry(callExpNode: any, calleeFClos: any, argValues: any, scope: any, info: any) { + const { analyzer, state } = info; + if (config.entryPointMode === "ONLY_CUSTOM") return; + if (!(calleeFClos && calleeFClos.object && calleeFClos.property)) return; + const { object, property } = calleeFClos; + if (!object._qid || !property.name) return; + const objectQid = object._qid; + if (!RouteRegistryObject.some((prefix) => objectQid.startsWith(prefix))) return; + switch (property.name) { + case "Use": + case "Pre": + this.handleUseOrPre(analyzer, scope, state, argValues); + break; + case "CONNECT": + case "DELETE": + case "GET": + case "HEAD": + case "OPTIONS": + case "PATCH": + case "POST": + case "PUT": + case "TRACE": + case "RouteNotFound": + case "Any": + this.handleMethodRegistration(analyzer, scope, state, argValues); + break; + case "Match": + case "Add": + this.handleMatchOrAdd(analyzer, scope, state, argValues); + break; + case "File": + this.handleFile(analyzer, scope, state, argValues); + break; + case "Host": + case "Group": + this.handleHostOrGroup(analyzer, scope, state, argValues); + break; + } + } + + processEntryPointAndTaintSource(analyzer: any, state: any, entryPointFuncValue: any, source: string) { + if (entryPointFuncValue?.vtype === "fclos" && entryPointFuncValue?.ast.loc) { + const hash = JSON.stringify(entryPointFuncValue.ast.loc) + if (!processedRouteRegistry.has(hash)) { + processedRouteRegistry.add(hash) + IntroduceTaint.introduceFuncArgTaintBySelfCollection(entryPointFuncValue, state, analyzer, source, 'GO_INPUT') + const entryPoint = completeEntryPoint(entryPointFuncValue) + analyzer.entryPoints.push(entryPoint) + } + } + } + + handleMiddlewareFunctionValue(analyzer: any, scope: any, state: any, middlewareFunctionValue: any) { + const retVal = analyzer.processAndCallFuncDef(scope, middlewareFunctionValue.fdef, middlewareFunctionValue, state) + let handlerFuncValues + switch (retVal.vtype) { + case "union": + handlerFuncValues = retVal.value + break; + default: // "fclos" + handlerFuncValues = [retVal] + break; + } + handlerFuncValues.forEach((handlerFuncValue: any) => { + this.processEntryPointAndTaintSource(analyzer, state, handlerFuncValue, '0') + }) + } + + handleUseOrPre(analyzer: any, scope: any, state: any, argValues: any) { + const middlewareFunctionValues = argValues.filter((argValue: any) => argValue.vtype === "fclos") + middlewareFunctionValues.forEach((middlewareFunctionValue: any) => { + this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) + }) + } + + handleMethodRegistration(analyzer: any, scope: any, state: any, argValues: any) { + this.processEntryPointAndTaintSource(analyzer, state, argValues[1], '0') + + for (let i = 2; i < argValues.length; i++) { + const argValue = argValues[i] + if (argValue.vtype === "fclos") { + const middlewareFunctionValue = argValue + this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) + } + } + } + + handleMatchOrAdd(analyzer: any, scope: any, state: any, argValues: any) { + this.processEntryPointAndTaintSource(analyzer, state, argValues[2], '0') + + for (let i = 3; i < argValues.length; i++) { + const argValue = argValues[i] + if (argValue.vtype === "fclos") { + const middlewareFunctionValue = argValue + this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) + } + } + } + + handleFile(analyzer: any, scope: any, state: any, argValues: any) { + for (let i = 2; i < argValues.length; i++) { + const argValue = argValues[i] + if (argValue.vtype === "fclos") { + const middlewareFunctionValue = argValue + this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) + } + } + } + + handleHostOrGroup(analyzer: any, scope: any, state: any, argValues: any) { + for (let i = 1; i < argValues.length; i++) { + const argValue = argValues[i] + if (argValue.vtype === "fclos") { + const middlewareFunctionValue = argValue + this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) + } + } + } +} + +module.exports = EchoEntrypointCollectChecker \ No newline at end of file From baa6b99a287ae36d8261b1a3467a22809f27004a Mon Sep 17 00:00:00 2001 From: Soraccc Date: Tue, 18 Nov 2025 13:34:37 +0800 Subject: [PATCH 07/14] feat: echo: refactor --- .../go/echo-entrypoint-collect-checker.ts | 532 ++++++++++++++---- src/checker/taint/go/util.ts | 49 ++ 2 files changed, 461 insertions(+), 120 deletions(-) create mode 100644 src/checker/taint/go/util.ts diff --git a/src/checker/taint/go/echo-entrypoint-collect-checker.ts b/src/checker/taint/go/echo-entrypoint-collect-checker.ts index 5573a116..e216ae13 100644 --- a/src/checker/taint/go/echo-entrypoint-collect-checker.ts +++ b/src/checker/taint/go/echo-entrypoint-collect-checker.ts @@ -1,144 +1,436 @@ +import Unit from "../../../engine/analyzer/common/value/unit"; +import { fixKnownPackageName, flattenUnionValues, processEntryPointAndTaintSource } from "./util"; + const config = require("../../../config"); +const KnownPackageName = new Map([ + ["github.com/labstack/echo/v4", "echo"], + ["github.com/labstack/echo-jwt/v4", "echojwt"] +]) + const RouteRegistryObject = ["github.com/labstack/echo/v4.New()"]; -const IntroduceTaint = require("../common-kit/source-util"); -const Checker = require("../../common/checker"); -const { completeEntryPoint } = require("./entry-points-util"); -const processedRouteRegistry = new Set(); +const MiddlewareHandlerRegistryObject = [ + "github.com/labstack/echo/v4/middleware", + "github.com/labstack/echo-contrib/casbin", + "github.com/labstack/echo-jwt/v4", + "github.com/labstack/echo-contrib/echoprometheus", + "github.com/labstack/echo-contrib/session" +]; -class EchoEntrypointCollectChecker extends Checker { - constructor(resultManager: any) { - super(resultManager, "echo-entrypoint-collect-checker"); - } - - triggerAtFunctionCallBefore(analyzer: any, scope: any, node: any, state: any, info: any) { - const { fclos, argvalues } = info; - this.collectRouteRegistry(node, fclos, argvalues, scope, info); - } +const ConfigObjectCollectionTable = new Map>([ + [ + "BasicAuthWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "Validator", source: "0, 1, 2" } + ] + ], + [ + "BodyDumpWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "Handler", source: "0, 1, 2" } + ] + ], + [ + "BodyLimitWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "MiddlewareWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "EnforceHandler", source: "0, 1" }, + { name: "UserGetter", source: "0" }, + { name: "ErrorHandler", source: "0, 1" } + ] + ], + [ + "ContextTimeoutWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "ErrorHandler", source: "0, 1" } + ] + ], + [ + "CORSWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "AllowOriginFunc", source: "0" } + ] + ], + [ + "CSRFWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "ErrorHandler", source: "0, 1" } + ] + ], + [ + "DecompressWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "GzipWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "WithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "BeforeFunc", source: "0" }, + { name: "SuccessHandler", source: "0" }, + { name: "ErrorHandler", source: "0, 1" }, + { name: "KeyFunc", source: "0" }, + { name: "ParseTokenFunc", source: "0" }, + { name: "NewClaimsFunc", source: "0" } + ] + ], + [ + "KeyAuthWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "Validator", source: "0, 1" }, + { name: "ErrorHandler", source: "0, 1" } + ] + ], + [ + "LoggerWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "CustomTagFunc", source: "0" } + ] + ], + [ + "RequestLoggerWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "BeforeNextFunc", source: "0" }, + { name: "LogValuesFunc", source: "0, 1" } + ] + ], + [ + "MethodOverrideWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "Getter", source: "0" } + ] + ], + [ + "NewMiddlewareWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "BeforeNext", source: "0" }, + { name: "AfterNext", source: "0, 1" }, + { name: "StatusCodeResolver", source: "0, 1" } + ] + ], + [ + "Proxy", + [ + { name: "Next", source: "0" } + ] + ], + [ + "ProxyWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "RetryFilter", source: "0, 1" }, + { name: "ErrorHandler", source: "0, 1" }, + { name: "ModifyResponse", source: "0" } + ] + ], + [ + "RateLimiter", + [ + { name: "Allow", source: "0" } + ] + ], + [ + "RateLimiterWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "BeforeFunc", source: "0" }, + { name: "IdentifierExtractor", source: "0" }, + { name: "ErrorHandler", source: "0, 1" }, + { name: "DenyHandler", source: "0, 1, 2" } + ] + ], + [ + "RecoverWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "LogErrorFunc", source: "0, 1" } + ] + ], + [ + "HTTPSRedirectWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "RequestIDWithConfig", + [ + { name: "Skipper", source: "0" }, + { name: "RequestIDHandler", source: "0, 1" } + ] + ], + [ + "RewriteWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "SecureWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "Middleware", + [ + { name: "Get", source: "0" }, + { name: "New", source: "0" }, + { name: "Save", source: "0" } + ] + ], + [ + "MiddlewareWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "StaticWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "AddTrailingSlashWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ], + [ + "RemoveTrailingSlashWithConfig", + [ + { name: "Skipper", source: "0" } + ] + ] +]) - triggerAtSymbolInterpretOfEntryPointAfter(analyzer: any, scope: any, node: any, state: any, info: any) { - if (info?.entryPoint.functionName === "main") processedRouteRegistry.clear(); - } +const Checker = require("../../common/checker"); - collectRouteRegistry(callExpNode: any, calleeFClos: any, argValues: any, scope: any, info: any) { - const { analyzer, state } = info; - if (config.entryPointMode === "ONLY_CUSTOM") return; - if (!(calleeFClos && calleeFClos.object && calleeFClos.property)) return; - const { object, property } = calleeFClos; - if (!object._qid || !property.name) return; - const objectQid = object._qid; - if (!RouteRegistryObject.some((prefix) => objectQid.startsWith(prefix))) return; - switch (property.name) { - case "Use": - case "Pre": - this.handleUseOrPre(analyzer, scope, state, argValues); - break; - case "CONNECT": - case "DELETE": - case "GET": - case "HEAD": - case "OPTIONS": - case "PATCH": - case "POST": - case "PUT": - case "TRACE": - case "RouteNotFound": - case "Any": - this.handleMethodRegistration(analyzer, scope, state, argValues); - break; - case "Match": - case "Add": - this.handleMatchOrAdd(analyzer, scope, state, argValues); - break; - case "File": - this.handleFile(analyzer, scope, state, argValues); - break; - case "Host": - case "Group": - this.handleHostOrGroup(analyzer, scope, state, argValues); - break; - } - } +const processedRouteRegistry = new Set(); - processEntryPointAndTaintSource(analyzer: any, state: any, entryPointFuncValue: any, source: string) { - if (entryPointFuncValue?.vtype === "fclos" && entryPointFuncValue?.ast.loc) { - const hash = JSON.stringify(entryPointFuncValue.ast.loc) - if (!processedRouteRegistry.has(hash)) { - processedRouteRegistry.add(hash) - IntroduceTaint.introduceFuncArgTaintBySelfCollection(entryPointFuncValue, state, analyzer, source, 'GO_INPUT') - const entryPoint = completeEntryPoint(entryPointFuncValue) - analyzer.entryPoints.push(entryPoint) - } - } - } +class EchoEntrypointCollectChecker extends Checker { + constructor(resultManager: any) { + super(resultManager, "echo-entrypoint-collect-checker"); + } - handleMiddlewareFunctionValue(analyzer: any, scope: any, state: any, middlewareFunctionValue: any) { - const retVal = analyzer.processAndCallFuncDef(scope, middlewareFunctionValue.fdef, middlewareFunctionValue, state) - let handlerFuncValues - switch (retVal.vtype) { - case "union": - handlerFuncValues = retVal.value - break; - default: // "fclos" - handlerFuncValues = [retVal] - break; - } - handlerFuncValues.forEach((handlerFuncValue: any) => { - this.processEntryPointAndTaintSource(analyzer, state, handlerFuncValue, '0') + triggerAtFunctionCallBefore(analyzer: any, scope: any, node: any, state: any, info: any) { + const { fclos, argvalues } = info; + if (config.entryPointMode === "ONLY_CUSTOM") return; + if (!(fclos && fclos.object && fclos.property)) return; + const { object, property } = fclos; + if (!object._qid || !property.name) return; + if (!RouteRegistryObject.some((obj) => object._qid.includes(obj))) return; + switch (property.name) { + case "Use": + case "Pre": + this.handleMiddlewareArgs(analyzer, scope, state, argvalues) + break; + case "CONNECT": + case "DELETE": + case "GET": + case "HEAD": + case "OPTIONS": + case "PATCH": + case "POST": + case "PUT": + case "TRACE": + case "RouteNotFound": + case "Any": + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, argvalues[1], '0'); + this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(2)) + break; + case "Match": + case "Add": + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, argvalues[2], '0'); + this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(3)) + break; + case "File": + this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(2)) + break; + case "FileFS": + flattenUnionValues([argvalues[2]]).forEach(fs => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, fs.field.Open, "0") }) + this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(3)) + break; + case "Host": + case "Group": + this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(1)) + break; } + } - handleUseOrPre(analyzer: any, scope: any, state: any, argValues: any) { - const middlewareFunctionValues = argValues.filter((argValue: any) => argValue.vtype === "fclos") - middlewareFunctionValues.forEach((middlewareFunctionValue: any) => { - this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) - }) - } + triggerAtSymbolInterpretOfEntryPointAfter(analyzer: any, scope: any, node: any, state: any, info: any) { + if (info?.entryPoint.functionName === "main") processedRouteRegistry.clear(); + } - handleMethodRegistration(analyzer: any, scope: any, state: any, argValues: any) { - this.processEntryPointAndTaintSource(analyzer, state, argValues[1], '0') + triggerAtPreDeclaration(analyzer: any, scope: any, node: any, state: any, info: any) { + fixKnownPackageName(node, KnownPackageName) + } - for (let i = 2; i < argValues.length; i++) { - const argValue = argValues[i] - if (argValue.vtype === "fclos") { - const middlewareFunctionValue = argValue - this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) - } - } + triggerAtAssignment(analyzer: any, scope: any, node: any, state: any, info: any) { + if (config.entryPointMode === "ONLY_CUSTOM") return; + const { lvalue, rvalue } = info + if (!(lvalue.object && lvalue.property)) return; + const { object, property } = lvalue + if (!object._qid || !property.name) return; + if (!RouteRegistryObject.some((obj) => object._qid.includes(obj))) return; + const rvalueObjs = flattenUnionValues([rvalue]) + switch (property.name) { + case "HTTPErrorHandler": + rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj, "0, 1")); + break; + case "Binder": + rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Bind, "1")); + break; + case "Renderer": + rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Render, "3")); + break; + case "Filesystem": + rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Open, "0")); + break; } + } - handleMatchOrAdd(analyzer: any, scope: any, state: any, argValues: any) { - this.processEntryPointAndTaintSource(analyzer, state, argValues[2], '0') + handleConfigObjectCollection(analyzer: any, state: any, symbol: any) { + const rules = ConfigObjectCollectionTable.get(symbol.expression.name); + if (!rules) return + flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { + rules.forEach(rule => { + const fieldValue = middlewareConfig.field[rule.name] + if (!fieldValue) return; + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, fieldValue, rule.source) + }) + }) + } - for (let i = 3; i < argValues.length; i++) { - const argValue = argValues[i] - if (argValue.vtype === "fclos") { - const middlewareFunctionValue = argValue - this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) - } - } - } + handleKnownEchoMiddlewares(analyzer: any, state: any, symbol: any) { + if (symbol.type !== "CallExpression") return + const objectQid = symbol.expression._qid; + if (!(objectQid && MiddlewareHandlerRegistryObject.some(obj => objectQid.startsWith(obj)))) return - handleFile(analyzer: any, scope: any, state: any, argValues: any) { - for (let i = 2; i < argValues.length; i++) { - const argValue = argValues[i] - if (argValue.vtype === "fclos") { - const middlewareFunctionValue = argValue - this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) + switch (symbol.expression.name) { + case "BasicAuth": + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], "0, 1, 2"); + break; + case "BodyDump": + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], "0, 1, 2"); + break; + case "KeyAuth": + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], "0, 1") + break; + case "WithConfig": + flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { + const tokenLookupFuncs = middlewareConfig.field["TokenLookupFuncs"]; + if (!tokenLookupFuncs) return; + Object.values(tokenLookupFuncs.value).forEach(v => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v as Unit, "0") + }) + }) + this.handleConfigObjectCollection(analyzer, state, symbol) + break; + case "NewMiddlewareWithConfig": + flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { + const labelFuncs = middlewareConfig.field["LabelFuncs"]; + if (!labelFuncs) return; + Object.values(labelFuncs.value).forEach(v => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v as Unit, "0, 1") + }) + }) + this.handleConfigObjectCollection(analyzer, state, symbol) + break; + case "ProxyWithConfig": + flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { + const balancer = middlewareConfig.field["Balancer"] + if (balancer) { + const next = balancer.field["Next"] + if (next) { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, next, "0") } - } - } - - handleHostOrGroup(analyzer: any, scope: any, state: any, argValues: any) { - for (let i = 1; i < argValues.length; i++) { - const argValue = argValues[i] - if (argValue.vtype === "fclos") { - const middlewareFunctionValue = argValue - this.handleMiddlewareFunctionValue(analyzer, scope, state, middlewareFunctionValue) + } + const transport = middlewareConfig.field["Transport"] + if (transport) { + const roundTrip = transport.field["RoundTrip"] + if (roundTrip) { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, roundTrip, "0") } - } + } + }) + this.handleConfigObjectCollection(analyzer, state, symbol) + break; + case "RateLimiterWithConfig": + flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { + const store = middlewareConfig.field["Store"] + if (!store) return; + const allow = store.field["Allow"] + if (!allow) return; + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, allow, "0") + }) + this.handleConfigObjectCollection(analyzer, state, symbol) + break; + case "MiddlewareWithConfig": + flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { + const store = middlewareConfig.field["Store"] + if (!store) return; + [store.field["Get"], store.field["New"], store.field["Save"]].filter(v => v).forEach(v => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v, "0") + }) + }) + this.handleConfigObjectCollection(analyzer, state, symbol) + break; + case "StaticWithConfig": + flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { + const filesystem = middlewareConfig.field["Filesystem"] + if (!filesystem) return; + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, filesystem.field["Open"], "0") + }) + this.handleConfigObjectCollection(analyzer, state, symbol) + break; + default: + this.handleConfigObjectCollection(analyzer, state, symbol) + break; } + } + + handleCustomMiddleware(analyzer: any, scope: any, state: any, middlewareFunctionValue: any) { + const retVal = analyzer.processAndCallFuncDef(scope, middlewareFunctionValue.fdef, middlewareFunctionValue, state) + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, retVal, '0') + } + + handleMiddlewareArgs(analyzer: any, scope: any, state: any, list: Array) { + const flattened = flattenUnionValues(list) + flattened.filter(unit => unit.vtype === "symbol").forEach(symbol => { + this.handleKnownEchoMiddlewares(analyzer, state, symbol) + }) + flattened.filter(unit => unit.vtype === "fclos").forEach(fclos => { + this.handleCustomMiddleware(analyzer, scope, state, fclos) + }) + } } module.exports = EchoEntrypointCollectChecker \ No newline at end of file diff --git a/src/checker/taint/go/util.ts b/src/checker/taint/go/util.ts new file mode 100644 index 00000000..9ac403b9 --- /dev/null +++ b/src/checker/taint/go/util.ts @@ -0,0 +1,49 @@ +import { VariableDeclaration } from "@ant-yasa/uast-spec"; +import Unit from "../../../engine/analyzer/common/value/unit" +const IntroduceTaint = require("../common-kit/source-util"); +const completeEntryPoint = require("../common-kit/entry-points-util") + +export function flattenUnionValues(list: Array): Array { + return list.flatMap(unit => { + switch (unit.vtype) { + case "union": + return flattenUnionValues(unit.value) + case "fclos": + case "symbol": + case "object": + return [unit] + default: + throw `flattenUnionValues: Unknown type ${unit.vtype}` + } + }) +} + +export function processEntryPointAndTaintSource(analyzer: any, state: any, processedRouteRegistry: Set, entryPointUnitValue: Unit, source: string) { + flattenUnionValues([entryPointUnitValue]) + .filter(val => val.vtype === "fclos") + .forEach(entryPointFuncValue => { + if (entryPointFuncValue?.ast.loc) { + const hash = JSON.stringify(entryPointFuncValue.ast.loc) + if (!processedRouteRegistry.has(hash)) { + processedRouteRegistry.add(hash) + IntroduceTaint.introduceFuncArgTaintBySelfCollection(entryPointFuncValue, state, analyzer, source, 'GO_INPUT') + const entryPoint = completeEntryPoint(entryPointFuncValue) + analyzer.entryPoints.push(entryPoint) + } + } + }) +} + +export function fixKnownPackageName(node: VariableDeclaration, knownPackageName: Map) { + if ( + node.cloned !== false || + node.init?.type != "ImportExpression" || + node.id.type !== "Identifier" + ) return; + + const moduleName = node.init.from.value + if (typeof moduleName !== "string") return; + const name = knownPackageName.get(moduleName) + if (!name) return; + node.id.name = name +} \ No newline at end of file From 094404dc896da8f84d0826d76c943480f3ee9a23 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Tue, 18 Nov 2025 13:44:00 +0800 Subject: [PATCH 08/14] chore: make gemini happy --- .../go/echo-entrypoint-collect-checker.ts | 55 ++++++++----------- src/checker/taint/go/util.ts | 4 +- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/checker/taint/go/echo-entrypoint-collect-checker.ts b/src/checker/taint/go/echo-entrypoint-collect-checker.ts index e216ae13..43d34396 100644 --- a/src/checker/taint/go/echo-entrypoint-collect-checker.ts +++ b/src/checker/taint/go/echo-entrypoint-collect-checker.ts @@ -203,12 +203,6 @@ const ConfigObjectCollectionTable = new Map { - const balancer = middlewareConfig.field["Balancer"] - if (balancer) { - const next = balancer.field["Next"] - if (next) { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, next, "0") - } + const balancerNext = middlewareConfig.field?.Balancer?.field?.Next; + if (balancerNext) { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, balancerNext, "0"); } - const transport = middlewareConfig.field["Transport"] - if (transport) { - const roundTrip = transport.field["RoundTrip"] - if (roundTrip) { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, roundTrip, "0") - } + const transportRoundTrip = middlewareConfig.field?.Transport?.field?.RoundTrip; + if (transportRoundTrip) { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, transportRoundTrip, "0"); } }) this.handleConfigObjectCollection(analyzer, state, symbol) break; case "RateLimiterWithConfig": flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const store = middlewareConfig.field["Store"] - if (!store) return; - const allow = store.field["Allow"] - if (!allow) return; - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, allow, "0") + const allow = middlewareConfig.field?.Store?.field?.Allow; + if (allow) { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, allow, "0"); + } }) this.handleConfigObjectCollection(analyzer, state, symbol) break; @@ -405,9 +392,10 @@ class EchoEntrypointCollectChecker extends Checker { break; case "StaticWithConfig": flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const filesystem = middlewareConfig.field["Filesystem"] - if (!filesystem) return; - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, filesystem.field["Open"], "0") + const open = middlewareConfig.field?.Filesystem?.field?.Open; + if (open) { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, open, "0"); + } }) this.handleConfigObjectCollection(analyzer, state, symbol) break; @@ -423,13 +411,14 @@ class EchoEntrypointCollectChecker extends Checker { } handleMiddlewareArgs(analyzer: any, scope: any, state: any, list: Array) { - const flattened = flattenUnionValues(list) - flattened.filter(unit => unit.vtype === "symbol").forEach(symbol => { - this.handleKnownEchoMiddlewares(analyzer, state, symbol) - }) - flattened.filter(unit => unit.vtype === "fclos").forEach(fclos => { - this.handleCustomMiddleware(analyzer, scope, state, fclos) - }) + const flattened = flattenUnionValues(list); + flattened.forEach(unit => { + if (unit.vtype === "symbol") { + this.handleKnownEchoMiddlewares(analyzer, state, unit); + } else if (unit.vtype === "fclos") { + this.handleCustomMiddleware(analyzer, scope, state, unit); + } + }); } } diff --git a/src/checker/taint/go/util.ts b/src/checker/taint/go/util.ts index 9ac403b9..a0496534 100644 --- a/src/checker/taint/go/util.ts +++ b/src/checker/taint/go/util.ts @@ -13,7 +13,7 @@ export function flattenUnionValues(list: Array): Array { case "object": return [unit] default: - throw `flattenUnionValues: Unknown type ${unit.vtype}` + throw new Error(`flattenUnionValues: Unknown type ${unit.vtype}`) } }) } @@ -37,7 +37,7 @@ export function processEntryPointAndTaintSource(analyzer: any, state: any, proce export function fixKnownPackageName(node: VariableDeclaration, knownPackageName: Map) { if ( node.cloned !== false || - node.init?.type != "ImportExpression" || + node.init?.type !== "ImportExpression" || node.id.type !== "Identifier" ) return; From 4dce9e3fd9c28368a346d22ea961d3132a5d0742 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Tue, 18 Nov 2025 13:58:20 +0800 Subject: [PATCH 09/14] chore: make sourcery happy --- src/checker/taint/go/echo-entrypoint-collect-checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/checker/taint/go/echo-entrypoint-collect-checker.ts b/src/checker/taint/go/echo-entrypoint-collect-checker.ts index 43d34396..c698c456 100644 --- a/src/checker/taint/go/echo-entrypoint-collect-checker.ts +++ b/src/checker/taint/go/echo-entrypoint-collect-checker.ts @@ -312,7 +312,7 @@ class EchoEntrypointCollectChecker extends Checker { } handleConfigObjectCollection(analyzer: any, state: any, symbol: any) { - const rules = ConfigObjectCollectionTable.get(symbol.expression.name); + const rules = ConfigObjectCollectionTable.get(symbol.expression?.name); if (!rules) return flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { rules.forEach(rule => { @@ -325,7 +325,7 @@ class EchoEntrypointCollectChecker extends Checker { handleKnownEchoMiddlewares(analyzer: any, state: any, symbol: any) { if (symbol.type !== "CallExpression") return - const objectQid = symbol.expression._qid; + const objectQid = symbol.expression?._qid; if (!(objectQid && MiddlewareHandlerRegistryObject.some(obj => objectQid.startsWith(obj)))) return switch (symbol.expression.name) { From 45003780cc0b9539c93e3c063c7fa01c4a061a00 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Tue, 18 Nov 2025 14:33:43 +0800 Subject: [PATCH 10/14] fix: add ruleConfig for echo.Context.Bind --- resource/example-rule-config/rule_config_go.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resource/example-rule-config/rule_config_go.json b/resource/example-rule-config/rule_config_go.json index 2770fc3d..71dde301 100644 --- a/resource/example-rule-config/rule_config_go.json +++ b/resource/example-rule-config/rule_config_go.json @@ -468,6 +468,15 @@ } ], "FuncCallArgTaintSource": [ + { + "args": [ + "0" + ], + "calleeType": "echo.Context", + "fsig": "Bind", + "scopeFile": "all", + "scopeFunc": "all" + }, { "args": [ "0" From b48653affcde9755c005af110799609b759efb0d Mon Sep 17 00:00:00 2001 From: Soraccc Date: Wed, 19 Nov 2025 15:45:29 +0800 Subject: [PATCH 11/14] style: format --- .../go/echo-entrypoint-collect-checker.ts | 588 +++++++++--------- src/checker/taint/go/util.ts | 60 +- 2 files changed, 345 insertions(+), 303 deletions(-) diff --git a/src/checker/taint/go/echo-entrypoint-collect-checker.ts b/src/checker/taint/go/echo-entrypoint-collect-checker.ts index c698c456..2417f1f9 100644 --- a/src/checker/taint/go/echo-entrypoint-collect-checker.ts +++ b/src/checker/taint/go/echo-entrypoint-collect-checker.ts @@ -1,425 +1,447 @@ -import Unit from "../../../engine/analyzer/common/value/unit"; -import { fixKnownPackageName, flattenUnionValues, processEntryPointAndTaintSource } from "./util"; +import type Unit from '../../../engine/analyzer/common/value/unit' +import { fixKnownPackageName, flattenUnionValues, processEntryPointAndTaintSource } from './util' -const config = require("../../../config"); +const config = require('../../../config') const KnownPackageName = new Map([ - ["github.com/labstack/echo/v4", "echo"], - ["github.com/labstack/echo-jwt/v4", "echojwt"] + ['github.com/labstack/echo/v4', 'echo'], + ['github.com/labstack/echo-jwt/v4', 'echojwt'], ]) -const RouteRegistryObject = ["github.com/labstack/echo/v4.New()"]; +const RouteRegistryObject = ['github.com/labstack/echo/v4.New()'] const MiddlewareHandlerRegistryObject = [ - "github.com/labstack/echo/v4/middleware", - "github.com/labstack/echo-contrib/casbin", - "github.com/labstack/echo-jwt/v4", - "github.com/labstack/echo-contrib/echoprometheus", - "github.com/labstack/echo-contrib/session" -]; + 'github.com/labstack/echo/v4/middleware', + 'github.com/labstack/echo-contrib/casbin', + 'github.com/labstack/echo-jwt/v4', + 'github.com/labstack/echo-contrib/echoprometheus', + 'github.com/labstack/echo-contrib/session', +] -const ConfigObjectCollectionTable = new Map>([ +const ConfigObjectCollectionTable = new Map>([ [ - "BasicAuthWithConfig", + 'BasicAuthWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "Validator", source: "0, 1, 2" } - ] + { name: 'Skipper', source: '0' }, + { name: 'Validator', source: '0, 1, 2' }, + ], ], [ - "BodyDumpWithConfig", + 'BodyDumpWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "Handler", source: "0, 1, 2" } - ] + { name: 'Skipper', source: '0' }, + { name: 'Handler', source: '0, 1, 2' }, + ], ], + ['BodyLimitWithConfig', [{ name: 'Skipper', source: '0' }]], [ - "BodyLimitWithConfig", + 'MiddlewareWithConfig', [ - { name: "Skipper", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'EnforceHandler', source: '0, 1' }, + { name: 'UserGetter', source: '0' }, + { name: 'ErrorHandler', source: '0, 1' }, + ], ], [ - "MiddlewareWithConfig", + 'ContextTimeoutWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "EnforceHandler", source: "0, 1" }, - { name: "UserGetter", source: "0" }, - { name: "ErrorHandler", source: "0, 1" } - ] + { name: 'Skipper', source: '0' }, + { name: 'ErrorHandler', source: '0, 1' }, + ], ], [ - "ContextTimeoutWithConfig", + 'CORSWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "ErrorHandler", source: "0, 1" } - ] + { name: 'Skipper', source: '0' }, + { name: 'AllowOriginFunc', source: '0' }, + ], ], [ - "CORSWithConfig", + 'CSRFWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "AllowOriginFunc", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'ErrorHandler', source: '0, 1' }, + ], ], + ['DecompressWithConfig', [{ name: 'Skipper', source: '0' }]], + ['GzipWithConfig', [{ name: 'Skipper', source: '0' }]], [ - "CSRFWithConfig", + 'WithConfig', [ - { name: "Skipper", source: "0" }, - { name: "ErrorHandler", source: "0, 1" } - ] + { name: 'Skipper', source: '0' }, + { name: 'BeforeFunc', source: '0' }, + { name: 'SuccessHandler', source: '0' }, + { name: 'ErrorHandler', source: '0, 1' }, + { name: 'KeyFunc', source: '0' }, + { name: 'ParseTokenFunc', source: '0' }, + { name: 'NewClaimsFunc', source: '0' }, + ], ], [ - "DecompressWithConfig", + 'KeyAuthWithConfig', [ - { name: "Skipper", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'Validator', source: '0, 1' }, + { name: 'ErrorHandler', source: '0, 1' }, + ], ], [ - "GzipWithConfig", + 'LoggerWithConfig', [ - { name: "Skipper", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'CustomTagFunc', source: '0' }, + ], ], [ - "WithConfig", + 'RequestLoggerWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "BeforeFunc", source: "0" }, - { name: "SuccessHandler", source: "0" }, - { name: "ErrorHandler", source: "0, 1" }, - { name: "KeyFunc", source: "0" }, - { name: "ParseTokenFunc", source: "0" }, - { name: "NewClaimsFunc", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'BeforeNextFunc', source: '0' }, + { name: 'LogValuesFunc', source: '0, 1' }, + ], ], [ - "KeyAuthWithConfig", + 'MethodOverrideWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "Validator", source: "0, 1" }, - { name: "ErrorHandler", source: "0, 1" } - ] + { name: 'Skipper', source: '0' }, + { name: 'Getter', source: '0' }, + ], ], [ - "LoggerWithConfig", + 'NewMiddlewareWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "CustomTagFunc", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'BeforeNext', source: '0' }, + { name: 'AfterNext', source: '0, 1' }, + { name: 'StatusCodeResolver', source: '0, 1' }, + ], ], + ['Proxy', [{ name: 'Next', source: '0' }]], [ - "RequestLoggerWithConfig", + 'ProxyWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "BeforeNextFunc", source: "0" }, - { name: "LogValuesFunc", source: "0, 1" } - ] + { name: 'Skipper', source: '0' }, + { name: 'RetryFilter', source: '0, 1' }, + { name: 'ErrorHandler', source: '0, 1' }, + { name: 'ModifyResponse', source: '0' }, + ], ], + ['RateLimiter', [{ name: 'Allow', source: '0' }]], [ - "MethodOverrideWithConfig", + 'RateLimiterWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "Getter", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'BeforeFunc', source: '0' }, + { name: 'IdentifierExtractor', source: '0' }, + { name: 'ErrorHandler', source: '0, 1' }, + { name: 'DenyHandler', source: '0, 1, 2' }, + ], ], [ - "NewMiddlewareWithConfig", + 'RecoverWithConfig', [ - { name: "Skipper", source: "0" }, - { name: "BeforeNext", source: "0" }, - { name: "AfterNext", source: "0, 1" }, - { name: "StatusCodeResolver", source: "0, 1" } - ] + { name: 'Skipper', source: '0' }, + { name: 'LogErrorFunc', source: '0, 1' }, + ], ], + ['HTTPSRedirectWithConfig', [{ name: 'Skipper', source: '0' }]], [ - "Proxy", + 'RequestIDWithConfig', [ - { name: "Next", source: "0" } - ] + { name: 'Skipper', source: '0' }, + { name: 'RequestIDHandler', source: '0, 1' }, + ], ], + ['RewriteWithConfig', [{ name: 'Skipper', source: '0' }]], + ['SecureWithConfig', [{ name: 'Skipper', source: '0' }]], [ - "ProxyWithConfig", + 'Middleware', [ - { name: "Skipper", source: "0" }, - { name: "RetryFilter", source: "0, 1" }, - { name: "ErrorHandler", source: "0, 1" }, - { name: "ModifyResponse", source: "0" } - ] + { name: 'Get', source: '0' }, + { name: 'New', source: '0' }, + { name: 'Save', source: '0' }, + ], ], - [ - "RateLimiter", - [ - { name: "Allow", source: "0" } - ] - ], - [ - "RateLimiterWithConfig", - [ - { name: "Skipper", source: "0" }, - { name: "BeforeFunc", source: "0" }, - { name: "IdentifierExtractor", source: "0" }, - { name: "ErrorHandler", source: "0, 1" }, - { name: "DenyHandler", source: "0, 1, 2" } - ] - ], - [ - "RecoverWithConfig", - [ - { name: "Skipper", source: "0" }, - { name: "LogErrorFunc", source: "0, 1" } - ] - ], - [ - "HTTPSRedirectWithConfig", - [ - { name: "Skipper", source: "0" } - ] - ], - [ - "RequestIDWithConfig", - [ - { name: "Skipper", source: "0" }, - { name: "RequestIDHandler", source: "0, 1" } - ] - ], - [ - "RewriteWithConfig", - [ - { name: "Skipper", source: "0" } - ] - ], - [ - "SecureWithConfig", - [ - { name: "Skipper", source: "0" } - ] - ], - [ - "Middleware", - [ - { name: "Get", source: "0" }, - { name: "New", source: "0" }, - { name: "Save", source: "0" } - ] - ], - [ - "StaticWithConfig", - [ - { name: "Skipper", source: "0" } - ] - ], - [ - "AddTrailingSlashWithConfig", - [ - { name: "Skipper", source: "0" } - ] - ], - [ - "RemoveTrailingSlashWithConfig", - [ - { name: "Skipper", source: "0" } - ] - ] + ['StaticWithConfig', [{ name: 'Skipper', source: '0' }]], + ['AddTrailingSlashWithConfig', [{ name: 'Skipper', source: '0' }]], + ['RemoveTrailingSlashWithConfig', [{ name: 'Skipper', source: '0' }]], ]) -const Checker = require("../../common/checker"); +const Checker = require('../../common/checker') -const processedRouteRegistry = new Set(); +const processedRouteRegistry = new Set() +/** + * + */ class EchoEntrypointCollectChecker extends Checker { + /** + * + * @param resultManager + */ constructor(resultManager: any) { - super(resultManager, "echo-entrypoint-collect-checker"); + super(resultManager, 'echo-entrypoint-collect-checker') } + /** + * + * @param analyzer + * @param scope + * @param node + * @param state + * @param info + */ triggerAtFunctionCallBefore(analyzer: any, scope: any, node: any, state: any, info: any) { - const { fclos, argvalues } = info; - if (config.entryPointMode === "ONLY_CUSTOM") return; - if (!(fclos && fclos.object && fclos.property)) return; - const { object, property } = fclos; - if (!object._qid || !property.name) return; - if (!RouteRegistryObject.some((obj) => object._qid.includes(obj))) return; + const { fclos, argvalues } = info + if (config.entryPointMode === 'ONLY_CUSTOM') return + if (!(fclos && fclos.object && fclos.property)) return + const { object, property } = fclos + if (!object._qid || !property.name) return + if (!RouteRegistryObject.some((obj) => object._qid.includes(obj))) return switch (property.name) { - case "Use": - case "Pre": + case 'Use': + case 'Pre': this.handleMiddlewareArgs(analyzer, scope, state, argvalues) - break; - case "CONNECT": - case "DELETE": - case "GET": - case "HEAD": - case "OPTIONS": - case "PATCH": - case "POST": - case "PUT": - case "TRACE": - case "RouteNotFound": - case "Any": - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, argvalues[1], '0'); + break + case 'CONNECT': + case 'DELETE': + case 'GET': + case 'HEAD': + case 'OPTIONS': + case 'PATCH': + case 'POST': + case 'PUT': + case 'TRACE': + case 'RouteNotFound': + case 'Any': + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, argvalues[1], '0') this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(2)) - break; - case "Match": - case "Add": - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, argvalues[2], '0'); + break + case 'Match': + case 'Add': + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, argvalues[2], '0') this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(3)) - break; - case "File": + break + case 'File': this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(2)) - break; - case "FileFS": - flattenUnionValues([argvalues[2]]).forEach(fs => { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, fs.field.Open, "0") + break + case 'FileFS': + flattenUnionValues([argvalues[2]]).forEach((fs) => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, fs.field.Open, '0') }) this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(3)) - break; - case "Host": - case "Group": + break + case 'Host': + case 'Group': this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(1)) - break; + break } } + /** + * + * @param analyzer + * @param scope + * @param node + * @param state + * @param info + */ triggerAtSymbolInterpretOfEntryPointAfter(analyzer: any, scope: any, node: any, state: any, info: any) { - if (info?.entryPoint.functionName === "main") processedRouteRegistry.clear(); + if (info?.entryPoint.functionName === 'main') processedRouteRegistry.clear() } + /** + * + * @param analyzer + * @param scope + * @param node + * @param state + * @param info + */ triggerAtPreDeclaration(analyzer: any, scope: any, node: any, state: any, info: any) { fixKnownPackageName(node, KnownPackageName) } + /** + * + * @param analyzer + * @param scope + * @param node + * @param state + * @param info + */ triggerAtAssignment(analyzer: any, scope: any, node: any, state: any, info: any) { - if (config.entryPointMode === "ONLY_CUSTOM") return; + if (config.entryPointMode === 'ONLY_CUSTOM') return const { lvalue, rvalue } = info - if (!(lvalue.object && lvalue.property)) return; + if (!(lvalue.object && lvalue.property)) return const { object, property } = lvalue - if (!object._qid || !property.name) return; - if (!RouteRegistryObject.some((obj) => object._qid.includes(obj))) return; + if (!object._qid || !property.name) return + if (!RouteRegistryObject.some((obj) => object._qid.includes(obj))) return const rvalueObjs = flattenUnionValues([rvalue]) switch (property.name) { - case "HTTPErrorHandler": - rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj, "0, 1")); - break; - case "Binder": - rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Bind, "1")); - break; - case "Renderer": - rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Render, "3")); - break; - case "Filesystem": - rvalueObjs.forEach(obj => processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Open, "0")); - break; + case 'HTTPErrorHandler': + rvalueObjs.forEach((obj) => + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj, '0, 1') + ) + break + case 'Binder': + rvalueObjs.forEach((obj) => + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Bind, '1') + ) + break + case 'Renderer': + rvalueObjs.forEach((obj) => + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Render, '3') + ) + break + case 'Filesystem': + rvalueObjs.forEach((obj) => + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, obj.field.Open, '0') + ) + break + default: + break } } + /** + * + * @param analyzer + * @param state + * @param symbol + */ handleConfigObjectCollection(analyzer: any, state: any, symbol: any) { - const rules = ConfigObjectCollectionTable.get(symbol.expression?.name); + const rules = ConfigObjectCollectionTable.get(symbol.expression?.name) if (!rules) return - flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - rules.forEach(rule => { + flattenUnionValues([symbol.arguments[0]]).forEach((middlewareConfig) => { + rules.forEach((rule) => { const fieldValue = middlewareConfig.field[rule.name] - if (!fieldValue) return; + if (!fieldValue) return processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, fieldValue, rule.source) }) }) } + /** + * + * @param analyzer + * @param state + * @param symbol + */ handleKnownEchoMiddlewares(analyzer: any, state: any, symbol: any) { - if (symbol.type !== "CallExpression") return - const objectQid = symbol.expression?._qid; - if (!(objectQid && MiddlewareHandlerRegistryObject.some(obj => objectQid.startsWith(obj)))) return + if (symbol.type !== 'CallExpression') return + const objectQid = symbol.expression?._qid + if (!(objectQid && MiddlewareHandlerRegistryObject.some((obj) => objectQid.startsWith(obj)))) return switch (symbol.expression.name) { - case "BasicAuth": - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], "0, 1, 2"); - break; - case "BodyDump": - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], "0, 1, 2"); - break; - case "KeyAuth": - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], "0, 1") - break; - case "WithConfig": - flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const tokenLookupFuncs = middlewareConfig.field["TokenLookupFuncs"]; - if (!tokenLookupFuncs) return; - Object.values(tokenLookupFuncs.value).forEach(v => { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v as Unit, "0") + case 'BasicAuth': + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], '0, 1, 2') + break + case 'BodyDump': + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], '0, 1, 2') + break + case 'KeyAuth': + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, symbol.arguments[0], '0, 1') + break + case 'WithConfig': + flattenUnionValues([symbol.arguments[0]]).forEach((middlewareConfig) => { + const tokenLookupFuncs = middlewareConfig.field.TokenLookupFuncs + if (!tokenLookupFuncs) return + Object.values(tokenLookupFuncs.value).forEach((v) => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v as Unit, '0') }) }) this.handleConfigObjectCollection(analyzer, state, symbol) - break; - case "NewMiddlewareWithConfig": - flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const labelFuncs = middlewareConfig.field["LabelFuncs"]; - if (!labelFuncs) return; - Object.values(labelFuncs.value).forEach(v => { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v as Unit, "0, 1") + break + case 'NewMiddlewareWithConfig': + flattenUnionValues([symbol.arguments[0]]).forEach((middlewareConfig) => { + const labelFuncs = middlewareConfig.field.LabelFuncs + if (!labelFuncs) return + Object.values(labelFuncs.value).forEach((v) => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v as Unit, '0, 1') }) }) this.handleConfigObjectCollection(analyzer, state, symbol) - break; - case "ProxyWithConfig": - flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const balancerNext = middlewareConfig.field?.Balancer?.field?.Next; + break + case 'ProxyWithConfig': + flattenUnionValues([symbol.arguments[0]]).forEach((middlewareConfig) => { + const balancerNext = middlewareConfig.field?.Balancer?.field?.Next if (balancerNext) { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, balancerNext, "0"); + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, balancerNext, '0') } - const transportRoundTrip = middlewareConfig.field?.Transport?.field?.RoundTrip; + const transportRoundTrip = middlewareConfig.field?.Transport?.field?.RoundTrip if (transportRoundTrip) { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, transportRoundTrip, "0"); + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, transportRoundTrip, '0') } }) this.handleConfigObjectCollection(analyzer, state, symbol) - break; - case "RateLimiterWithConfig": - flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const allow = middlewareConfig.field?.Store?.field?.Allow; + break + case 'RateLimiterWithConfig': + flattenUnionValues([symbol.arguments[0]]).forEach((middlewareConfig) => { + const allow = middlewareConfig.field?.Store?.field?.Allow if (allow) { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, allow, "0"); + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, allow, '0') } }) this.handleConfigObjectCollection(analyzer, state, symbol) - break; - case "MiddlewareWithConfig": - flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const store = middlewareConfig.field["Store"] - if (!store) return; - [store.field["Get"], store.field["New"], store.field["Save"]].filter(v => v).forEach(v => { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v, "0") - }) + break + case 'MiddlewareWithConfig': + flattenUnionValues([symbol.arguments[0]]).forEach((middlewareConfig) => { + const store = middlewareConfig.field.Store + if (!store) return + ;[store.field.Get, store.field.New, store.field.Save] + .filter((v) => v) + .forEach((v) => { + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, v, '0') + }) }) this.handleConfigObjectCollection(analyzer, state, symbol) - break; - case "StaticWithConfig": - flattenUnionValues([symbol.arguments[0]]).forEach(middlewareConfig => { - const open = middlewareConfig.field?.Filesystem?.field?.Open; + break + case 'StaticWithConfig': + flattenUnionValues([symbol.arguments[0]]).forEach((middlewareConfig) => { + const open = middlewareConfig.field?.Filesystem?.field?.Open if (open) { - processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, open, "0"); + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, open, '0') } }) this.handleConfigObjectCollection(analyzer, state, symbol) - break; + break default: this.handleConfigObjectCollection(analyzer, state, symbol) - break; + break } } + /** + * + * @param analyzer + * @param scope + * @param state + * @param middlewareFunctionValue + */ handleCustomMiddleware(analyzer: any, scope: any, state: any, middlewareFunctionValue: any) { const retVal = analyzer.processAndCallFuncDef(scope, middlewareFunctionValue.fdef, middlewareFunctionValue, state) processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, retVal, '0') } + /** + * + * @param analyzer + * @param scope + * @param state + * @param list + */ handleMiddlewareArgs(analyzer: any, scope: any, state: any, list: Array) { - const flattened = flattenUnionValues(list); - flattened.forEach(unit => { - if (unit.vtype === "symbol") { - this.handleKnownEchoMiddlewares(analyzer, state, unit); - } else if (unit.vtype === "fclos") { - this.handleCustomMiddleware(analyzer, scope, state, unit); + const flattened = flattenUnionValues(list) + flattened.forEach((unit) => { + if (unit.vtype === 'symbol') { + this.handleKnownEchoMiddlewares(analyzer, state, unit) + } else if (unit.vtype === 'fclos') { + this.handleCustomMiddleware(analyzer, scope, state, unit) } - }); + }) } } -module.exports = EchoEntrypointCollectChecker \ No newline at end of file +module.exports = EchoEntrypointCollectChecker diff --git a/src/checker/taint/go/util.ts b/src/checker/taint/go/util.ts index a0496534..94a1b018 100644 --- a/src/checker/taint/go/util.ts +++ b/src/checker/taint/go/util.ts @@ -1,16 +1,21 @@ -import { VariableDeclaration } from "@ant-yasa/uast-spec"; -import Unit from "../../../engine/analyzer/common/value/unit" -const IntroduceTaint = require("../common-kit/source-util"); -const completeEntryPoint = require("../common-kit/entry-points-util") +import type { VariableDeclaration } from '@ant-yasa/uast-spec' +import type Unit from '../../../engine/analyzer/common/value/unit' +const IntroduceTaint = require('../common-kit/source-util') +const completeEntryPoint = require('../common-kit/entry-points-util') + +/** + * + * @param list + */ export function flattenUnionValues(list: Array): Array { - return list.flatMap(unit => { + return list.flatMap((unit) => { switch (unit.vtype) { - case "union": + case 'union': return flattenUnionValues(unit.value) - case "fclos": - case "symbol": - case "object": + case 'fclos': + case 'symbol': + case 'object': return [unit] default: throw new Error(`flattenUnionValues: Unknown type ${unit.vtype}`) @@ -18,10 +23,24 @@ export function flattenUnionValues(list: Array): Array { }) } -export function processEntryPointAndTaintSource(analyzer: any, state: any, processedRouteRegistry: Set, entryPointUnitValue: Unit, source: string) { +/** + * + * @param analyzer + * @param state + * @param processedRouteRegistry + * @param entryPointUnitValue + * @param source + */ +export function processEntryPointAndTaintSource( + analyzer: any, + state: any, + processedRouteRegistry: Set, + entryPointUnitValue: Unit, + source: string +) { flattenUnionValues([entryPointUnitValue]) - .filter(val => val.vtype === "fclos") - .forEach(entryPointFuncValue => { + .filter((val) => val.vtype === 'fclos') + .forEach((entryPointFuncValue) => { if (entryPointFuncValue?.ast.loc) { const hash = JSON.stringify(entryPointFuncValue.ast.loc) if (!processedRouteRegistry.has(hash)) { @@ -34,16 +53,17 @@ export function processEntryPointAndTaintSource(analyzer: any, state: any, proce }) } +/** + * + * @param node + * @param knownPackageName + */ export function fixKnownPackageName(node: VariableDeclaration, knownPackageName: Map) { - if ( - node.cloned !== false || - node.init?.type !== "ImportExpression" || - node.id.type !== "Identifier" - ) return; + if (node.cloned !== false || node.init?.type !== 'ImportExpression' || node.id.type !== 'Identifier') return const moduleName = node.init.from.value - if (typeof moduleName !== "string") return; + if (typeof moduleName !== 'string') return const name = knownPackageName.get(moduleName) - if (!name) return; + if (!name) return node.id.name = name -} \ No newline at end of file +} From 279775e05a345c1194304ad64ee09bc1b169f79d Mon Sep 17 00:00:00 2001 From: Soraccc Date: Tue, 25 Nov 2025 13:52:41 +0800 Subject: [PATCH 12/14] feat: unified golang default import handling --- .../go/echo-entrypoint-collect-checker.ts | 26 ++++++---------- .../go/restful-entrypoint-collect-checker.ts | 30 ++++++++----------- src/checker/taint/go/util.ts | 16 ---------- .../analyzer/golang/common/go-analyzer.ts | 27 ++++++++++++----- 4 files changed, 41 insertions(+), 58 deletions(-) diff --git a/src/checker/taint/go/echo-entrypoint-collect-checker.ts b/src/checker/taint/go/echo-entrypoint-collect-checker.ts index 2417f1f9..042bd93c 100644 --- a/src/checker/taint/go/echo-entrypoint-collect-checker.ts +++ b/src/checker/taint/go/echo-entrypoint-collect-checker.ts @@ -1,12 +1,13 @@ import type Unit from '../../../engine/analyzer/common/value/unit' -import { fixKnownPackageName, flattenUnionValues, processEntryPointAndTaintSource } from './util' +import { flattenUnionValues, processEntryPointAndTaintSource } from './util' const config = require('../../../config') +const GoAnalyzer = require('../../../engine/analyzer/golang/common/go-analyzer') -const KnownPackageName = new Map([ - ['github.com/labstack/echo/v4', 'echo'], - ['github.com/labstack/echo-jwt/v4', 'echojwt'], -]) +const KnownPackageName = { + 'github.com/labstack/echo/v4': 'echo', + 'github.com/labstack/echo-jwt/v4': 'echojwt', +} const RouteRegistryObject = ['github.com/labstack/echo/v4.New()'] @@ -182,6 +183,7 @@ class EchoEntrypointCollectChecker extends Checker { */ constructor(resultManager: any) { super(resultManager, 'echo-entrypoint-collect-checker') + GoAnalyzer.registerKnownPackageNames(KnownPackageName) } /** @@ -236,6 +238,8 @@ class EchoEntrypointCollectChecker extends Checker { case 'Group': this.handleMiddlewareArgs(analyzer, scope, state, argvalues.slice(1)) break + default: + break } } @@ -251,18 +255,6 @@ class EchoEntrypointCollectChecker extends Checker { if (info?.entryPoint.functionName === 'main') processedRouteRegistry.clear() } - /** - * - * @param analyzer - * @param scope - * @param node - * @param state - * @param info - */ - triggerAtPreDeclaration(analyzer: any, scope: any, node: any, state: any, info: any) { - fixKnownPackageName(node, KnownPackageName) - } - /** * * @param analyzer diff --git a/src/checker/taint/go/restful-entrypoint-collect-checker.ts b/src/checker/taint/go/restful-entrypoint-collect-checker.ts index 8cd40af8..6fca9f0c 100644 --- a/src/checker/taint/go/restful-entrypoint-collect-checker.ts +++ b/src/checker/taint/go/restful-entrypoint-collect-checker.ts @@ -1,15 +1,20 @@ +import { processEntryPointAndTaintSource } from './util' + const config = require('../../../config') +const GoAnalyzer = require('../../../engine/analyzer/golang/common/go-analyzer') const RouteRegistryProperty = ['Filter', 'To', 'If'] +const KnownPackageName = { + 'github.com/emicklei/go-restful': 'restful', + 'github.com/emicklei/go-restful/v3': 'restful', +} const RouteRegistryObject = [ - 'github.com/emicklei/go-restful/v3.WebService', 'github.com/emicklei/go-restful.WebService', + 'github.com/emicklei/go-restful/v3.WebService', ] -const IntroduceTaint = require('../common-kit/source-util') const Checker = require('../../common/checker') -const completeEntryPoint = require('../common-kit/entry-points-util') -const processedRouteRegistry = new Set() +const processedRouteRegistry = new Set() /** * @@ -21,6 +26,7 @@ class RestfulEntrypointCollectChecker extends Checker { */ constructor(resultManager: any) { super(resultManager, 'go-restful-entryPoints-collect-checker') + GoAnalyzer.registerKnownPackageNames(KnownPackageName) } /** @@ -67,20 +73,10 @@ class RestfulEntrypointCollectChecker extends Checker { const propertyName = property.name if ( RouteRegistryObject.some((prefix) => objectQid.startsWith(prefix)) && - RouteRegistryProperty.includes(propertyName) + RouteRegistryProperty.includes(propertyName) && + argValues[0] ) { - if (argValues.length < 1) return - const arg0 = argValues[0] - - if (arg0?.vtype === 'fclos' && arg0?.ast.loc) { - const hash = JSON.stringify(arg0.ast.loc) - if (!processedRouteRegistry.has(hash)) { - processedRouteRegistry.add(hash) - IntroduceTaint.introduceFuncArgTaintBySelfCollection(arg0, state, analyzer, '0', 'GO_INPUT') - const entryPoint = completeEntryPoint(arg0) - analyzer.entryPoints.push(entryPoint) - } - } + processEntryPointAndTaintSource(analyzer, state, processedRouteRegistry, argValues[0], '0') } } } diff --git a/src/checker/taint/go/util.ts b/src/checker/taint/go/util.ts index 94a1b018..13c27790 100644 --- a/src/checker/taint/go/util.ts +++ b/src/checker/taint/go/util.ts @@ -1,4 +1,3 @@ -import type { VariableDeclaration } from '@ant-yasa/uast-spec' import type Unit from '../../../engine/analyzer/common/value/unit' const IntroduceTaint = require('../common-kit/source-util') @@ -52,18 +51,3 @@ export function processEntryPointAndTaintSource( } }) } - -/** - * - * @param node - * @param knownPackageName - */ -export function fixKnownPackageName(node: VariableDeclaration, knownPackageName: Map) { - if (node.cloned !== false || node.init?.type !== 'ImportExpression' || node.id.type !== 'Identifier') return - - const moduleName = node.init.from.value - if (typeof moduleName !== 'string') return - const name = knownPackageName.get(moduleName) - if (!name) return - node.id.name = name -} diff --git a/src/engine/analyzer/golang/common/go-analyzer.ts b/src/engine/analyzer/golang/common/go-analyzer.ts index 0297fdba..02a4e6f2 100644 --- a/src/engine/analyzer/golang/common/go-analyzer.ts +++ b/src/engine/analyzer/golang/common/go-analyzer.ts @@ -514,6 +514,16 @@ class GoAnalyzer extends Analyzer { } } + private static knownPackageName: Record = {} + + /** + * Register known module name to package name mapping for default import variable name fix + * @param knownPackageName A map from module name to known package name + */ + static registerKnownPackageNames(knownPackageName: Record) { + GoAnalyzer.knownPackageName = { ...GoAnalyzer.knownPackageName, ...knownPackageName } + } + /** * * @param scope @@ -591,14 +601,15 @@ class GoAnalyzer extends Analyzer { } } } else { - // 如果是import,则定义真正的包名而非目录名 - if ( - initialNode?.type === 'ImportExpression' && - initVal?.vtype === 'package' && - initVal.name && - id.name === initialNode.from?.value?.split('/').at(-1) - ) { - id.name = initVal.name + if (initialNode?.type === 'ImportExpression') { + // 处理 default import 情况 + if (node._meta?.isDefaultImport === true && GoAnalyzer.knownPackageName[initialNode.from?.value]) { + id.name = GoAnalyzer.knownPackageName[initialNode.from?.value] + } + // 如果是import,则定义真正的包名而非目录名 + if (initVal?.vtype === 'package' && initVal.name && id.name === initialNode.from?.value?.split('/').at(-1)) { + id.name = initVal.name + } } this.saveVarInCurrentScope(scope, id, initVal, state) } From 3ff9de6853302b432ff1bc5d48f4c79455a88031 Mon Sep 17 00:00:00 2001 From: Soraccc Date: Tue, 25 Nov 2025 13:53:18 +0800 Subject: [PATCH 13/14] fix: main-entrypoint-collect-checker --- src/checker/taint/go/main-entrypoint-collect-checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checker/taint/go/main-entrypoint-collect-checker.ts b/src/checker/taint/go/main-entrypoint-collect-checker.ts index 35f834f2..fc2e8254 100644 --- a/src/checker/taint/go/main-entrypoint-collect-checker.ts +++ b/src/checker/taint/go/main-entrypoint-collect-checker.ts @@ -2,7 +2,7 @@ import type { EntryPoint } from '../../../engine/analyzer/common/entrypoint' const _ = require('lodash') const GoEntryPoint = require('../../../engine/analyzer/golang/common/entrypoint-collector/go-default-entrypoint') -const { completeEntryPoint } = require('../common-kit/entry-points-util') +const completeEntryPoint = require('../common-kit/entry-points-util') const Config = require('../../../config') const Checker = require('../../common/checker') From aae54915b02608e90c3dbf075456d76acca45cdd Mon Sep 17 00:00:00 2001 From: Soraccc Date: Tue, 25 Nov 2025 13:53:51 +0800 Subject: [PATCH 14/14] perf: reuse calculated fclos in processCallExpression --- build.sh | 0 src/engine/analyzer/common/analyzer.ts | 7 +++---- src/engine/analyzer/golang/common/go-analyzer.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) mode change 100755 => 100644 build.sh diff --git a/build.sh b/build.sh old mode 100755 new mode 100644 diff --git a/src/engine/analyzer/common/analyzer.ts b/src/engine/analyzer/common/analyzer.ts index c567cb84..4dff8ca9 100644 --- a/src/engine/analyzer/common/analyzer.ts +++ b/src/engine/analyzer/common/analyzer.ts @@ -1,5 +1,3 @@ -import { floor } from 'lodash' - const _ = require('lodash') const Uuid = require('node-uuid') const chalk = require('chalk') @@ -1486,8 +1484,9 @@ class Analyzer extends MemSpace { * @param scope * @param node * @param state + * @param cachedFclos */ - processCallExpression(scope: any, node: any, state: any) { + processCallExpression(scope: any, node: any, state: any, cachedFclos?: any) { /* { callee, arguments, } @@ -1498,7 +1497,7 @@ class Analyzer extends MemSpace { einfo: state.einfo, }) - const fclos = this.processInstruction(scope, node.callee, state) + const fclos = cachedFclos ?? this.processInstruction(scope, node.callee, state) if (!fclos) return UndefinedValue() if (node?.callee?.type === 'MemberAccess' && fclos.fdef && node.callee?.object?.type !== 'SuperExpression') { fclos._this = this.processInstruction(scope, node.callee.object, state) diff --git a/src/engine/analyzer/golang/common/go-analyzer.ts b/src/engine/analyzer/golang/common/go-analyzer.ts index 02a4e6f2..a6789380 100644 --- a/src/engine/analyzer/golang/common/go-analyzer.ts +++ b/src/engine/analyzer/golang/common/go-analyzer.ts @@ -345,7 +345,7 @@ class GoAnalyzer extends Analyzer { ainfo: this.ainfo, }) } - ret = super.processCallExpression(scope, node, state) + ret = super.processCallExpression(scope, node, state, fclos) if (ret && this.checkerManager) { this.checkerManager.checkAtFunctionCallAfter(this, scope, node, state, { fclos,