From c266912dcbbbf43c280b58ecc0e340f01d4c3a9f Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Mon, 18 May 2026 03:47:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D10=E4=B8=AA=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BC=BA=E9=99=B7:=20panic=E9=98=B2=E6=8A=A4,=20=E6=AD=BB?= =?UTF-8?q?=E9=94=81,=20=E5=AF=86=E9=92=A5=E6=B3=84=E6=BC=8F,=20=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=90=9E=E6=B2=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - eval_random: randomInt参数max<=min时不再panic,返回CEL错误 - scanner: 长驻插件nil/panic时兜底发送ready通道,消除死锁 - poc_executor: Ceye API密钥改为环境变量CEYE_API/CEYE_DOMAIN - Eval: ParseResponse加入oResp.Request nil检查 - Eval: reverseCheck中http.NewRequest错误不再忽略 - poc_executor: clusterpoc中CEL表达式求值错误记录日志 - winwmi: PowerShell执行失败完整记录错误信息 - sshkey: authorized_keys读取失败处理错误 - minidump: Scan结束后释放系统DLL句柄 - Windows插件: PE文件错误消息改用i18n --- common/i18n/locales/en.yaml | 8 ++++++++ common/i18n/locales/zh.yaml | 8 ++++++++ core/scanner.go | 3 ++- plugins/local/minidump.go | 10 ++++++++++ plugins/local/sshkey.go | 6 +++++- plugins/local/winbits.go | 4 ++-- plugins/local/winifeo.go | 4 ++-- plugins/local/winlogon.go | 4 ++-- plugins/local/winregistry.go | 4 ++-- plugins/local/winschtask.go | 6 +++--- plugins/local/winservice.go | 4 ++-- plugins/local/winstartup.go | 4 ++-- plugins/local/winwmi.go | 9 ++++++--- webscan/lib/Eval.go | 13 +++++++++++-- webscan/lib/eval_random.go | 9 ++++++--- webscan/lib/poc_executor.go | 23 +++++++++++++++++------ 16 files changed, 88 insertions(+), 31 deletions(-) diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index bfd445d..3e0f7a9 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -756,6 +756,14 @@ minidump_panic: minidump_success: other: "Successfully dumped lsass.exe memory to file: {{.Arg1}} (size: {{.Arg2}} bytes)" +# Local plugin common errors +local_pe_not_specified: + other: "No PE file specified, use -win-pe parameter" +local_pe_not_found: + other: "PE file not found: {{.Arg1}}" +local_invalid_pe: + other: "Invalid PE file: {{.Arg1}}" + # ========================= WebScan Messages ========================= webscan_target_url_failed: other: "Failed to build target URL: {{.Arg1}}" diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index b6a6321..f2b5c66 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -753,6 +753,14 @@ minidump_panic: minidump_success: other: "成功将lsass.exe内存转储到文件: {{.Arg1}} (大小: {{.Arg2}} bytes)" +# 本地插件通用错误 +local_pe_not_specified: + other: "未指定PE文件,使用 -win-pe 参数" +local_pe_not_found: + other: "PE文件不存在: {{.Arg1}}" +local_invalid_pe: + other: "无效的PE文件: {{.Arg1}}" + # ========================= WebScan消息 ========================= webscan_target_url_failed: other: "构建目标URL失败: {{.Arg1}}" diff --git a/core/scanner.go b/core/scanner.go index 007a661..68ba27e 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -231,12 +231,13 @@ func executeScanTask(ctx context.Context, session *common.ScanSession, pluginNam go func() { plugin := plugins.Get(pluginName) if plugin != nil { - // 给主线程一点时间让 state 标记生效 go func() { time.Sleep(500 * time.Millisecond) ready <- struct{}{} }() plugin.Scan(ctx, &target, session) + } else { + ready <- struct{}{} } }() <-ready diff --git a/plugins/local/minidump.go b/plugins/local/minidump.go index b52f60c..edc14f4 100644 --- a/plugins/local/minidump.go +++ b/plugins/local/minidump.go @@ -102,6 +102,7 @@ func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio if err := p.loadSystemDLLs(); err != nil { return &plugins.Result{Success: false, Output: fmt.Sprintf("加载系统DLL失败: %v\n", err), Error: err} } + defer p.releaseSystemDLLs() pm := &ProcessManager{kernel32: p.kernel32, dbghelp: p.dbghelp, advapi32: p.advapi32} avActive := p.isAVBlocking() @@ -238,6 +239,15 @@ func (p *MiniDumpPlugin) loadSystemDLLs() error { return nil } +// releaseSystemDLLs 释放已加载的系统DLL +func (p *MiniDumpPlugin) releaseSystemDLLs() { + for _, dll := range []*syscall.DLL{p.kernel32, p.dbghelp, p.advapi32} { + if dll != nil { + _ = dll.Release() + } + } +} + // isAdmin 检查是否具有管理员权限 func (p *MiniDumpPlugin) isAdmin() bool { var sid *windows.SID diff --git a/plugins/local/sshkey.go b/plugins/local/sshkey.go index 97bd3f7..b9acf64 100644 --- a/plugins/local/sshkey.go +++ b/plugins/local/sshkey.go @@ -49,7 +49,11 @@ func (p *SSHKeyPlugin) Scan(ctx context.Context, info *common.HostInfo, session } // 追加公钥到 authorized_keys - existing, _ := os.ReadFile(authFile) + existing, err := os.ReadFile(authFile) + if err != nil && !os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("[失败] %s: 读取 authorized_keys 失败: %v\n", u.Username, err)) + continue + } if strings.Contains(string(existing), pubKey) { output.WriteString(fmt.Sprintf("[跳过] %s: 公钥已存在\n", u.Username)) continue diff --git a/plugins/local/winbits.go b/plugins/local/winbits.go index 54a6222..e020f50 100644 --- a/plugins/local/winbits.go +++ b/plugins/local/winbits.go @@ -26,10 +26,10 @@ func NewWinBITSPlugin() *WinBITSPlugin { func (p *WinBITSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } absPath, _ := filepath.Abs(pePath) diff --git a/plugins/local/winifeo.go b/plugins/local/winifeo.go index f53eef7..60f8109 100644 --- a/plugins/local/winifeo.go +++ b/plugins/local/winifeo.go @@ -26,10 +26,10 @@ func NewWinIFEOPlugin() *WinIFEOPlugin { func (p *WinIFEOPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } absPath, _ := filepath.Abs(pePath) diff --git a/plugins/local/winlogon.go b/plugins/local/winlogon.go index 7887835..7c9efe9 100644 --- a/plugins/local/winlogon.go +++ b/plugins/local/winlogon.go @@ -26,10 +26,10 @@ func NewWinLogonPlugin() *WinLogonPlugin { func (p *WinLogonPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } absPath, _ := filepath.Abs(pePath) diff --git a/plugins/local/winregistry.go b/plugins/local/winregistry.go index 95c9177..4721c29 100644 --- a/plugins/local/winregistry.go +++ b/plugins/local/winregistry.go @@ -28,10 +28,10 @@ func NewWinRegistryPlugin() *WinRegistryPlugin { func (p *WinRegistryPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } absPath, _ := filepath.Abs(pePath) diff --git a/plugins/local/winschtask.go b/plugins/local/winschtask.go index a80520c..6995c8d 100644 --- a/plugins/local/winschtask.go +++ b/plugins/local/winschtask.go @@ -28,14 +28,14 @@ func NewWinSchTaskPlugin() *WinSchTaskPlugin { func (p *WinSchTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } ext := strings.ToLower(filepath.Ext(pePath)) if ext != ".exe" && ext != ".dll" { - return &plugins.Result{Success: false, Error: fmt.Errorf("无效的PE文件: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_invalid_pe", pePath))} } absPath, _ := filepath.Abs(pePath) diff --git a/plugins/local/winservice.go b/plugins/local/winservice.go index cb22154..ae2d4a1 100644 --- a/plugins/local/winservice.go +++ b/plugins/local/winservice.go @@ -28,10 +28,10 @@ func NewWinServicePlugin() *WinServicePlugin { func (p *WinServicePlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } absPath, _ := filepath.Abs(pePath) diff --git a/plugins/local/winstartup.go b/plugins/local/winstartup.go index 33f098f..3948911 100644 --- a/plugins/local/winstartup.go +++ b/plugins/local/winstartup.go @@ -28,10 +28,10 @@ func NewWinStartupPlugin() *WinStartupPlugin { func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } absPath, _ := filepath.Abs(pePath) diff --git a/plugins/local/winwmi.go b/plugins/local/winwmi.go index ed36e59..24bd3d0 100644 --- a/plugins/local/winwmi.go +++ b/plugins/local/winwmi.go @@ -28,10 +28,10 @@ func NewWinWMIPlugin() *WinWMIPlugin { func (p *WinWMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { pePath := session.Config.WinPEFile if pePath == "" { - return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.GetText("local_pe_not_specified"))} } if _, err := os.Stat(pePath); err != nil { - return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + return &plugins.Result{Success: false, Error: fmt.Errorf(i18n.Tr("local_pe_not_found", pePath))} } absPath, _ := filepath.Abs(pePath) @@ -62,7 +62,10 @@ try { Write-Output "TOTAL:$ok"`, filterName, consumerName, absPath, absPath, filterName, consumerName) - out, _ := exec.Command("powershell", "-NoProfile", "-Command", ps).CombinedOutput() + out, err := exec.Command("powershell", "-NoProfile", "-Command", ps).CombinedOutput() + if err != nil { + common.LogError(i18n.Tr("error_generic", fmt.Errorf("PowerShell执行失败: %w, 输出: %s", err, strings.TrimSpace(string(out))))) + } result := string(out) var output strings.Builder diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index 6ac88a4..b61d611 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -363,7 +363,10 @@ func reverseCheck(r *Reverse, timeout int64) bool { ceyeAPI, sub) // 创建并发送请求 - req, _ := http.NewRequest("GET", apiURL, nil) + req, err := http.NewRequest("GET", apiURL, nil) + if err != nil { + return false + } resp, err := DoRequest(req, false) if err != nil { return false @@ -521,9 +524,15 @@ func ParseRequest(oReq *http.Request) (*Request, error) { // ParseResponse 将标准 HTTP 响应转换为自定义响应对象 func ParseResponse(oResp *http.Response) (*Response, error) { + var respURL *UrlType + if oResp.Request != nil { + respURL = ParseURL(oResp.Request.URL) + } else { + respURL = &UrlType{} + } resp := Response{ Status: int32(oResp.StatusCode), - URL: ParseURL(oResp.Request.URL), + URL: respURL, Headers: make(map[string]string), ContentType: oResp.Header.Get("Content-Type"), } diff --git a/webscan/lib/eval_random.go b/webscan/lib/eval_random.go index 4f595f8..cb4f970 100644 --- a/webscan/lib/eval_random.go +++ b/webscan/lib/eval_random.go @@ -46,9 +46,12 @@ func registerRandomImplementations() []*functions.Overload { if !ok { return types.ValOrErr(rhs, "unexpected type '%v' passed to randomInt", rhs.Type()) } - min, max := int(from), int(to) - //nolint:gosec // G404: 用于生成POC测试随机数,非加密用途 - return types.Int(rand.Intn(max-min) + min) + min, max := int(from), int(to) + if max <= min { + return types.NewErr("randomInt: max(%d) must be greater than min(%d)", max, min) + } + //nolint:gosec // G404: 用于生成POC测试随机数,非加密用途 + return types.Int(rand.Intn(max-min) + min) }, }, { diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index 0f2a1b9..e9430f0 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -6,6 +6,7 @@ import ( "math/rand" //nolint:gosec // G404: math/rand用于生成测试数据,非加密用途 "net/http" "net/url" + "os" "regexp" "strings" "sync" @@ -19,11 +20,18 @@ import ( exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) -// API配置常量 -const ( - ceyeAPI = "a78a1cb49d91fe09e01876078d1868b2" // Ceye平台的API密钥 - ceyeDomain = "7wtusr.ceye.io" // Ceye平台的域名 -) +// Ceye平台凭据(通过环境变量配置,避免将密钥硬编码在源码中) +// CEYE_API: Ceye API令牌 +// CEYE_DOMAIN: Ceye平台域名(可选,默认使用api.ceye.io) +var ceyeAPI, ceyeDomain string + +func init() { + ceyeAPI = os.Getenv("CEYE_API") + ceyeDomain = os.Getenv("CEYE_DOMAIN") + if ceyeDomain == "" { + ceyeDomain = "api.ceye.io" + } +} // Task 定义单个POC检测任务的结构体 type Task struct { @@ -480,7 +488,10 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, if key == "payload" { payloadExpr = expr } - output, _ := evalset1(env, variableMap, key, expr) + output, err := evalset1(env, variableMap, key, expr) + if err != nil { + common.LogError(i18n.Tr("webscan_set_exec_error", key, err)) + } payloads[key] = output }