From 265e855fc36b5614e1fc6e24bdfed31588a1ee41 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Mon, 11 May 2026 18:37:40 +0800 Subject: [PATCH] fix web result protocol output (#577) --- common/output/writers.go | 29 +++++++++++++------------- common/output/writers_test.go | 38 +++++++++++++++++++++++++++++++++++ core/scanner.go | 10 ++++++++- core/scanner_test.go | 21 +++++++++++++++++++ plugins/web/webtitle.go | 1 + 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/common/output/writers.go b/common/output/writers.go index 2accf6f..d345b4a 100644 --- a/common/output/writers.go +++ b/common/output/writers.go @@ -196,13 +196,7 @@ func (w *TXTWriter) formatWebServiceLine(result *ScanResult) string { } } - protocol := "http" - service := w.getDetailStr(result, "service") - if service == "https" || strings.Contains(target, ":443") { - protocol = "https" - } - - url := fmt.Sprintf("%s://%s", protocol, target) + url := fmt.Sprintf("%s://%s", w.webProtocol(result, target), target) title := w.getDetailStr(result, "title") status := w.getDetail(result, "status") server := w.getDetailStr(result, "server") @@ -375,13 +369,7 @@ func (w *TXTWriter) writeWebServices() { } } - protocol := "http" - service := w.getDetailStr(result, "service") - if service == "https" || strings.Contains(target, ":443") { - protocol = "https" - } - - urls = append(urls, fmt.Sprintf("%s://%s", protocol, target)) + urls = append(urls, fmt.Sprintf("%s://%s", w.webProtocol(result, target), target)) } if len(urls) == 0 { @@ -410,6 +398,19 @@ func (w *TXTWriter) isWebService(result *ScanResult) bool { return service == "http" || service == "https" } +func (w *TXTWriter) webProtocol(result *ScanResult, target string) string { + protocol := strings.ToLower(w.getDetailStr(result, "protocol")) + if protocol == "http" || protocol == "https" { + return protocol + } + + service := strings.ToLower(w.getDetailStr(result, "service")) + if service == "https" || strings.Contains(target, ":443") { + return "https" + } + return "http" +} + // GetFormat 获取格式类型 func (w *TXTWriter) GetFormat() Format { return FormatTXT diff --git a/common/output/writers_test.go b/common/output/writers_test.go index 04036ff..fd590a7 100644 --- a/common/output/writers_test.go +++ b/common/output/writers_test.go @@ -1230,6 +1230,44 @@ func TestCSVWriter_WebServiceFields(t *testing.T) { } } +func TestTXTWriter_WebServiceProtocolFromDetails(t *testing.T) { + dir := createTestDir(t) + filePath := filepath.Join(dir, "test_web_protocol.txt") + + writer, err := NewTXTWriter(filePath) + if err != nil { + t.Fatalf("创建TXTWriter失败: %v", err) + } + + result := createTestResult( + TypeService, + "192.168.1.1:8443", + "web", + map[string]interface{}{ + "plugin": "webtitle", + "is_web": true, + "port": 8443, + "protocol": "https", + "title": "Home", + "status": 200, + }, + ) + if err := writer.Write(result); err != nil { + t.Fatalf("Write()失败: %v", err) + } + if err := writer.Close(); err != nil { + t.Fatalf("Close()失败: %v", err) + } + + content := readFileContent(t, filePath) + if !strings.Contains(content, "https://192.168.1.1:8443") { + t.Fatalf("TXT输出缺少HTTPS URL,内容:\n%s", content) + } + if strings.Contains(content, "http://192.168.1.1:8443") { + t.Fatalf("TXT输出不应把HTTPS目标降级为HTTP,内容:\n%s", content) + } +} + // TestJSONWriter_FlushAndFormat 测试JSON的Flush和GetFormat func TestJSONWriter_FlushAndFormat(t *testing.T) { dir := createTestDir(t) diff --git a/core/scanner.go b/core/scanner.go index 200ee62..7703b25 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -3,6 +3,7 @@ package core import ( "context" "fmt" + "net/url" "os" "os/signal" "sync" @@ -340,9 +341,16 @@ var resultSerializers = map[plugins.ResultType]resultSerializer{ plugins.ResultTypeWeb: { outputType: output.TypeService, getStatus: func(_ *plugins.Result, _ *common.HostInfo) string { return "web" }, - fillDetail: func(_ *plugins.Result, info *common.HostInfo, d map[string]interface{}) { + fillDetail: func(r *plugins.Result, info *common.HostInfo, d map[string]interface{}) { d["is_web"] = true d["port"] = info.Port + if r.Output == "" { + return + } + d["url"] = r.Output + if parsed, err := url.Parse(r.Output); err == nil && (parsed.Scheme == "http" || parsed.Scheme == "https") { + d["protocol"] = parsed.Scheme + } }, }, } diff --git a/core/scanner_test.go b/core/scanner_test.go index 5f91517..ddb7f81 100644 --- a/core/scanner_test.go +++ b/core/scanner_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins" ) /* @@ -35,6 +36,26 @@ scanner_test.go - Scanner核心逻辑测试 // 核心逻辑测试:策略选择 // ============================================================================= +func TestWebResultSerializerPreservesDetectedProtocol(t *testing.T) { + serializer := resultSerializers[plugins.ResultTypeWeb] + details := map[string]interface{}{} + result := &plugins.Result{ + Type: plugins.ResultTypeWeb, + Success: true, + Output: "https://192.168.1.1:8443", + } + info := &common.HostInfo{Host: "192.168.1.1", Port: 8443} + + serializer.fillDetail(result, info, details) + + if details["protocol"] != "https" { + t.Fatalf("protocol = %v, 期望 https", details["protocol"]) + } + if details["url"] != "https://192.168.1.1:8443" { + t.Fatalf("url = %v, 期望检测出的URL", details["url"]) + } +} + // TestSelectStrategy 测试策略选择逻辑 func TestSelectStrategy(t *testing.T) { // 保存原始配置 diff --git a/plugins/web/webtitle.go b/plugins/web/webtitle.go index 410c815..8b24692 100644 --- a/plugins/web/webtitle.go +++ b/plugins/web/webtitle.go @@ -72,6 +72,7 @@ func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo, sessio return &WebScanResult{ Type: plugins.ResultTypeWeb, Success: true, + Output: url, Title: title, Status: status, Server: server,