refactor: 完成全局状态到 session 的完整迁移

将 plugins/services、plugins/local、plugins/web、webscan 层的日志输出、
漏洞结果保存和 TCP 计数器从全局 common.Log*/GetGlobalState() 迁移到
session 实例方法,确保 SDK 并发扫描时各实例完全隔离。

- 50 个文件,所有插件日志走 session.Log*
- DoRequest 加入 session 参数,计数器走 session.State
- POC 执行器通过 POCContext.Session 传递
- 仅保留 init() 和 CEL runtime 等无 session 场景的全局回退
This commit is contained in:
ZacharyZcR
2026-06-01 08:13:23 +08:00
parent 569d21a8bc
commit d6d323854a
50 changed files with 256 additions and 225 deletions
+22 -7
View File
@@ -367,7 +367,7 @@ func reverseCheck(r *Reverse, timeout int64) bool {
if err != nil {
return false
}
resp, err := DoRequest(req, false)
resp, err := DoRequest(req, false, nil)
if err != nil {
return false
}
@@ -419,7 +419,8 @@ func RandomStr(randSource *rand.Rand, letterBytes string, n int) string {
}
// DoRequest 执行 HTTP 请求
func DoRequest(req *http.Request, redirect bool) (*Response, error) {
// session 为 nil 时回退到全局 state(兼容 CEL runtime 等无 session 场景)
func DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (*Response, error) {
// 处理请求头
if req.Body != nil && req.Body != http.NoBody {
body, err := io.ReadAll(req.Body)
@@ -444,9 +445,23 @@ func DoRequest(req *http.Request, redirect bool) (*Response, error) {
// 执行请求
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(i18n.Tr("webscan_request_restricted", req.URL.String(), reason))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason))
var state *common.State
if session != nil {
state = session.State
if canSend, err := common.CanSendPacketWith(session.Config, state); !canSend {
reason := ""
if err != nil {
reason = err.Error()
}
common.LogError(i18n.Tr("webscan_request_restricted", req.URL.String(), reason))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason))
}
} else {
state = common.GetGlobalState()
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(i18n.Tr("webscan_request_restricted", req.URL.String(), reason))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason))
}
}
var (
@@ -480,12 +495,12 @@ func DoRequest(req *http.Request, redirect bool) (*Response, error) {
if err != nil {
// HTTP请求失败,计为TCP失败
common.GetGlobalState().IncrementTCPFailedPacketCount()
state.IncrementTCPFailedPacketCount()
return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_request_execute_failed"), err)
}
// HTTP请求成功,计为TCP成功
common.GetGlobalState().IncrementTCPSuccessPacketCount()
state.IncrementTCPSuccessPacketCount()
defer func() { _ = oResp.Body.Close() }()
// 解析响应
+2 -2
View File
@@ -1104,7 +1104,7 @@ func TestDoRequestBuffersUnknownLengthBody(t *testing.T) {
}
req.ContentLength = -1
if _, err := DoRequest(req, false); err != nil {
if _, err := DoRequest(req, false, nil); err != nil {
t.Fatalf("DoRequest error = %v", err)
}
if gotContentLength != "3" {
@@ -1147,7 +1147,7 @@ func TestDoRequestReplaysBodyForGMTLSFallback(t *testing.T) {
t.Fatalf("NewRequest error = %v", err)
}
if _, err := DoRequest(req, false); err != nil {
if _, err := DoRequest(req, false, nil); err != nil {
t.Fatalf("DoRequest error = %v", err)
}
if gotBody != "payload" {
+18 -17
View File
@@ -52,6 +52,7 @@ type VulnResult struct {
type POCContext struct {
DNSLog bool // 是否启用DNSLog检测
POCFull bool // 是否完整POC扫描
Session *common.ScanSession
}
// CheckMultiPoc 并发执行多个POC检测
@@ -82,7 +83,7 @@ func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int, pocCtx *POCConte
// 处理执行过程中的错误
if err != nil {
common.LogError(i18n.Tr("webscan_poc_exec_error", task.Poc.Name, err))
pocCtx.Session.LogError(i18n.Tr("webscan_poc_exec_error", task.Poc.Name, err))
continue
}
@@ -117,7 +118,7 @@ func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int, pocCtx *POCConte
Status: "vulnerable",
Details: details,
}
_ = common.SaveResult(result)
_ = pocCtx.Session.SaveResult(result)
// 构造控制台输出的日志信息
logMsg := i18n.Tr("webscan_vuln_detail_header",
@@ -141,7 +142,7 @@ func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int, pocCtx *POCConte
}
// 输出成功日志
common.LogVuln(logMsg)
pocCtx.Session.LogVuln(logMsg)
}
}
}()
@@ -216,7 +217,7 @@ func executePoc(oReq *http.Request, p *Poc, pocCtx *POCContext) (bool, string, e
continue
}
if _, err = evalset(env, variableMap, key, expression); err != nil {
common.LogError(i18n.Tr("webscan_set_exec_error", p.Name, err))
pocCtx.Session.LogError(i18n.Tr("webscan_set_exec_error", p.Name, err))
}
}
@@ -226,11 +227,11 @@ func executePoc(oReq *http.Request, p *Poc, pocCtx *POCContext) (bool, string, e
return success, "", err
}
return executeRules(oReq, p, variableMap, req, env)
return executeRules(oReq, p, variableMap, req, env, pocCtx.Session)
}
// executeRules 执行POC规则并返回结果
func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env) (bool, string, error) {
func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, session *common.ScanSession) (bool, string, error) {
// 处理单个规则的函数
executeRule := func(rule Rules) (bool, error) {
Headers := cloneMap(rule.Headers)
@@ -279,7 +280,7 @@ func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{}
_ = Headers // 清空Headers
// 发送请求
resp, err := DoRequest(newRequest, rule.FollowRedirects)
resp, err := DoRequest(newRequest, rule.FollowRedirects, session)
newRequest = nil
if err != nil {
return false, err
@@ -447,7 +448,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{},
// 检查是否需要进行参数Fuzz测试
if !isFuzz(rule, p.Sets) {
// 不需要Fuzz,直接发送请求
success, err = clustersend(oReq, variableMap, req, env, rule)
success, err = clustersend(oReq, variableMap, req, env, rule, pocCtx.Session)
if err != nil {
return false, err
}
@@ -492,7 +493,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{},
}
output, err := evalset1(env, variableMap, key, expr)
if err != nil {
common.LogError(i18n.Tr("webscan_set_exec_error", key, err))
pocCtx.Session.LogError(i18n.Tr("webscan_set_exec_error", key, err))
}
payloads[key] = output
}
@@ -513,7 +514,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{},
ruleHash[ruleMD5] = struct{}{}
// 发送请求并处理结果
success, err = clustersend(oReq, variableMap, req, env, currentRule)
success, err = clustersend(oReq, variableMap, req, env, currentRule, pocCtx.Session)
if err != nil {
return false, err
}
@@ -524,7 +525,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{},
// 处理成功情况
if currentRule.Continue {
// 使用Continue标志时,记录但继续测试其他参数
recordVulnerabilityResult(targetURL, p, currentParams, false)
recordVulnerabilityResult(targetURL, p, currentParams, false, pocCtx.Session)
continue
}
@@ -532,7 +533,7 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{},
strMap = append(strMap, currentParams...)
if ruleIndex == len(p.Rules)-1 {
// 最终规则成功,记录完整的结果并返回
recordVulnerabilityResult(targetURL, p, strMap, false)
recordVulnerabilityResult(targetURL, p, strMap, false, pocCtx.Session)
return false, nil
}
break paramLoop
@@ -617,7 +618,7 @@ func getRuleHash(rule *Rules) string {
}
// recordVulnerabilityResult 记录漏洞检测结果
func recordVulnerabilityResult(targetURL string, pocDef *Poc, params StrMap, skipSave bool) {
func recordVulnerabilityResult(targetURL string, pocDef *Poc, params StrMap, skipSave bool, session *common.ScanSession) {
// 构造详细信息
details := make(map[string]interface{})
details["vulnerability_type"] = pocDef.Name
@@ -656,7 +657,7 @@ func recordVulnerabilityResult(targetURL string, pocDef *Poc, params StrMap, ski
Status: "vulnerable",
Details: details,
}
_ = common.SaveResult(result)
_ = session.SaveResult(result)
}
// 生成日志消息
@@ -668,7 +669,7 @@ func recordVulnerabilityResult(targetURL string, pocDef *Poc, params StrMap, ski
}
// 输出成功日志
common.LogVuln(logMsg)
session.LogVuln(logMsg)
}
// isFuzz 检查规则是否包含需要Fuzz测试的参数
@@ -738,7 +739,7 @@ func MakeData(base [][]string, nextData []string) [][]string {
}
// clustersend 执行单个规则的HTTP请求和响应检测
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules, session *common.ScanSession) (bool, error) {
// 替换请求中的变量
for varName, varValue := range variableMap {
// 跳过map类型的变量
@@ -786,7 +787,7 @@ func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Re
}
// 发送请求
resp, err := DoRequest(newRequest, rule.FollowRedirects)
resp, err := DoRequest(newRequest, rule.FollowRedirects, session)
if err != nil {
return false, fmt.Errorf("%s: %w", i18n.GetText("webscan_request_send_error"), err)
}
+15 -14
View File
@@ -48,7 +48,7 @@ var (
)
// WebScan 执行Web漏洞扫描
func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config) {
func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config, session *common.ScanSession) {
// 初始化POC配置(用于CEL回调函数)
lib.InitPOCConfig(cfg.DNSLog)
@@ -65,19 +65,19 @@ func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config) {
// 验证输入
if info == nil {
common.LogError(i18n.GetText("invalid_scan_target"))
session.LogError(i18n.GetText("invalid_scan_target"))
return
}
if len(allPocs) == 0 {
common.LogError(i18n.GetText("poc_load_failed"))
session.LogError(i18n.GetText("poc_load_failed"))
return
}
// 构建目标URL
target, err := buildTargetURL(info)
if err != nil {
common.LogError(i18n.Tr("webscan_target_url_failed", err))
session.LogError(i18n.Tr("webscan_target_url_failed", err))
return
}
@@ -91,13 +91,13 @@ func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config) {
// 根据扫描策略执行POC
if cfg.POC.PocName == "" && len(info.Info) == 0 {
// 执行所有POC
executePOCs(ctx, config.PocInfo{Target: target}, cfg)
executePOCs(ctx, config.PocInfo{Target: target}, cfg, session)
} else if len(info.Info) > 0 {
// 基于指纹信息执行POC
scanByFingerprints(ctx, target, info.Info, cfg)
scanByFingerprints(ctx, target, info.Info, cfg, session)
} else if cfg.POC.PocName != "" {
// 基于指定POC名称执行
executePOCs(ctx, config.PocInfo{Target: target, PocName: cfg.POC.PocName}, cfg)
executePOCs(ctx, config.PocInfo{Target: target, PocName: cfg.POC.PocName}, cfg, session)
}
}
@@ -126,7 +126,7 @@ func hasProtocolPrefix(urlStr string) bool {
}
// scanByFingerprints 根据指纹执行POC
func scanByFingerprints(ctx context.Context, target string, fingerprints []string, cfg *common.Config) {
func scanByFingerprints(ctx context.Context, target string, fingerprints []string, cfg *common.Config, session *common.ScanSession) {
for _, fingerprint := range fingerprints {
if fingerprint == "" {
continue
@@ -137,15 +137,15 @@ func scanByFingerprints(ctx context.Context, target string, fingerprints []strin
continue
}
executePOCs(ctx, config.PocInfo{Target: target, PocName: pocName}, cfg)
executePOCs(ctx, config.PocInfo{Target: target, PocName: pocName}, cfg, session)
}
}
// executePOCs 执行POC检测
func executePOCs(ctx context.Context, pocInfo config.PocInfo, cfg *common.Config) {
func executePOCs(ctx context.Context, pocInfo config.PocInfo, cfg *common.Config, session *common.ScanSession) {
// 验证目标
if pocInfo.Target == "" {
common.LogError(ErrEmptyTarget.Error())
session.LogError(ErrEmptyTarget.Error())
return
}
@@ -157,21 +157,21 @@ func executePOCs(ctx context.Context, pocInfo config.PocInfo, cfg *common.Config
// 验证URL
_, err := url.Parse(pocInfo.Target)
if err != nil {
common.LogError(i18n.Tr("webscan_invalid_url", ErrInvalidURL, pocInfo.Target, err))
session.LogError(i18n.Tr("webscan_invalid_url", ErrInvalidURL, pocInfo.Target, err))
return
}
// 创建基础请求
req, err := createBaseRequest(ctx, pocInfo.Target, cfg)
if err != nil {
common.LogError(i18n.Tr("webscan_request_create_failed", err))
session.LogError(i18n.Tr("webscan_request_create_failed", err))
return
}
// 筛选POC
matchedPocs := filterPocs(pocInfo.PocName)
if len(matchedPocs) == 0 {
common.LogDebug(fmt.Sprintf("%v: %s", ErrPocNotFound, pocInfo.PocName))
session.LogDebug(fmt.Sprintf("%v: %s", ErrPocNotFound, pocInfo.PocName))
return
}
@@ -179,6 +179,7 @@ func executePOCs(ctx context.Context, pocInfo config.PocInfo, cfg *common.Config
pocCtx := &lib.POCContext{
DNSLog: cfg.DNSLog,
POCFull: cfg.POC.Full,
Session: session,
}
// 执行POC检测