From 3e4e2db722e50cce97435ab9c73dcd251a62e99d Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Mon, 1 Jun 2026 02:50:49 +0800 Subject: [PATCH] feat: stream large host scans --- common/parsers/host_iterator.go | 485 +++++++++++++++++++++++++++ common/parsers/host_iterator_test.go | 61 ++++ core/alive_scanner.go | 52 +-- core/host_batch.go | 22 ++ core/port_scan.go | 4 +- core/portfinger/match_engine.go | 2 +- core/service_scanner.go | 91 +++-- plugins/services/ftp.go | 6 +- plugins/services/ms17010.go | 4 +- plugins/services/smb.go | 2 +- webscan/lib/poc_executor.go | 2 +- 11 files changed, 660 insertions(+), 71 deletions(-) create mode 100644 common/parsers/host_iterator.go create mode 100644 common/parsers/host_iterator_test.go create mode 100644 core/host_batch.go diff --git a/common/parsers/host_iterator.go b/common/parsers/host_iterator.go new file mode 100644 index 0000000..a50a488 --- /dev/null +++ b/common/parsers/host_iterator.go @@ -0,0 +1,485 @@ +package parsers + +import ( + "bufio" + "context" + "fmt" + "net" + "os" + "strconv" + "strings" + + "github.com/shadow1ng/fscan/common/i18n" +) + +const DefaultHostBatchSize = 4096 + +type HostIterator struct { + sources []hostSource + current hostSource + exclude *hostMatcher +} + +func NewHostIterator(host string, filename string, nohosts ...string) (*HostIterator, error) { + var sources []hostSource + + if filename != "" { + fileSrc, err := newFileHostSource(filename) + if err != nil { + return nil, err + } + sources = append(sources, fileSrc) + } + + hostSources, err := newHostSources(host) + if err != nil { + closeHostSources(sources) + return nil, err + } + sources = append(sources, hostSources...) + + matcher := newHostMatcher() + if len(nohosts) > 0 && strings.TrimSpace(nohosts[0]) != "" { + if err := matcher.add(nohosts[0]); err != nil { + closeHostSources(sources) + return nil, err + } + } + + return &HostIterator{ + sources: sources, + exclude: matcher, + }, nil +} + +func (it *HostIterator) Close() error { + if it == nil { + return nil + } + var firstErr error + if it.current != nil { + firstErr = it.current.Close() + it.current = nil + } + for _, src := range it.sources { + if err := src.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + it.sources = nil + return firstErr +} + +func (it *HostIterator) Next() (string, bool, error) { + for { + if it.current == nil { + if len(it.sources) == 0 { + return "", false, nil + } + it.current = it.sources[0] + it.sources = it.sources[1:] + } + + host, ok, err := it.current.Next() + if err != nil { + return "", false, err + } + if !ok { + if err := it.current.Close(); err != nil { + return "", false, err + } + it.current = nil + continue + } + if it.exclude != nil && it.exclude.match(host) { + continue + } + return host, true, nil + } +} + +func (it *HostIterator) NextBatch(ctx context.Context, size int) ([]string, error) { + if size <= 0 { + size = DefaultHostBatchSize + } + + batch := make([]string, 0, size) + seen := make(map[string]struct{}, size) + for len(batch) < size { + select { + case <-ctx.Done(): + return batch, ctx.Err() + default: + } + + host, ok, err := it.Next() + if err != nil { + return batch, err + } + if !ok { + return batch, nil + } + if _, exists := seen[host]; exists { + continue + } + seen[host] = struct{}{} + batch = append(batch, host) + } + return batch, nil +} + +type hostSource interface { + Next() (string, bool, error) + Close() error +} + +type singleHostSource struct { + host string + done bool +} + +func (s *singleHostSource) Next() (string, bool, error) { + if s.done { + return "", false, nil + } + s.done = true + return s.host, true, nil +} + +func (s *singleHostSource) Close() error { return nil } + +type cidrHostSource struct { + current uint32 + end uint32 + done bool +} + +func (s *cidrHostSource) Next() (string, bool, error) { + if s.done || s.current > s.end { + return "", false, nil + } + host := uint32ToIP(s.current) + if s.current == s.end { + s.done = true + } else { + s.current++ + } + return host, true, nil +} + +func (s *cidrHostSource) Close() error { return nil } + +type fileHostSource struct { + file *os.File + scanner *bufio.Scanner + current hostSource +} + +func newFileHostSource(filename string) (*fileHostSource, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + return &fileHostSource{ + file: file, + scanner: bufio.NewScanner(file), + }, nil +} + +func (s *fileHostSource) Next() (string, bool, error) { + for { + if s.current != nil { + host, ok, err := s.current.Next() + if err != nil { + return "", false, err + } + if ok { + return host, true, nil + } + _ = s.current.Close() + s.current = nil + } + + if !s.scanner.Scan() { + if err := s.scanner.Err(); err != nil { + return "", false, err + } + return "", false, nil + } + line := strings.TrimSpace(s.scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + sources, err := newHostSources(line) + if err != nil || len(sources) == 0 { + continue + } + if len(sources) == 1 { + s.current = sources[0] + continue + } + s.current = &multiHostSource{sources: sources} + } +} + +func (s *fileHostSource) Close() error { + if s.current != nil { + _ = s.current.Close() + s.current = nil + } + if s.file == nil { + return nil + } + err := s.file.Close() + s.file = nil + return err +} + +type multiHostSource struct { + sources []hostSource + current hostSource +} + +func (s *multiHostSource) Next() (string, bool, error) { + for { + if s.current == nil { + if len(s.sources) == 0 { + return "", false, nil + } + s.current = s.sources[0] + s.sources = s.sources[1:] + } + host, ok, err := s.current.Next() + if err != nil { + return "", false, err + } + if ok { + return host, true, nil + } + _ = s.current.Close() + s.current = nil + } +} + +func (s *multiHostSource) Close() error { + if s.current != nil { + _ = s.current.Close() + s.current = nil + } + closeHostSources(s.sources) + s.sources = nil + return nil +} + +func newHostSources(host string) ([]hostSource, error) { + var sources []hostSource + for _, h := range strings.Split(host, ",") { + h = strings.TrimSpace(h) + if h == "" { + continue + } + src, err := newHostSource(h) + if err != nil { + closeHostSources(sources) + return nil, err + } + sources = append(sources, src) + } + return sources, nil +} + +func newHostSource(host string) (hostSource, error) { + switch { + case host == "192": + return newCIDRHostSource("192.168.0.0/16") + case host == "172": + return newCIDRHostSource("172.16.0.0/12") + case host == "10": + return newCIDRHostSource("10.0.0.0/8") + case strings.Contains(host, "/"): + src, err := newCIDRHostSource(host) + if err != nil { + return nil, fmt.Errorf(i18n.Tr("parser_cidr_failed", host)+": %w", err) + } + return src, nil + case strings.Contains(host, "-") && !strings.Contains(host, ":") && looksLikeIPRange(host): + src, err := newRangeHostSource(host) + if err != nil { + return nil, fmt.Errorf(i18n.Tr("parser_ip_range_failed", host)+": %w", err) + } + return src, nil + default: + return &singleHostSource{host: host}, nil + } +} + +func newCIDRHostSource(cidr string) (hostSource, error) { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return nil, err + } + + start, ok := ipToUint32(ipNet.IP) + if !ok { + return nil, fmt.Errorf("%s", i18n.GetText("parser_ipv4_only")) + } + ones, bits := ipNet.Mask.Size() + if bits != 32 { + return nil, fmt.Errorf("%s", i18n.GetText("parser_ipv4_only")) + } + size := uint64(1) << uint(32-ones) + end := start + uint32(size-1) + if size > 2 { + start++ + end-- + } + return &cidrHostSource{current: start, end: end}, nil +} + +func newRangeHostSource(rangeStr string) (hostSource, error) { + parts := strings.Split(rangeStr, "-") + if len(parts) != 2 { + return nil, fmt.Errorf("%s", i18n.Tr("parser_invalid_ip_range_fmt", rangeStr)) + } + + startIPStr := strings.TrimSpace(parts[0]) + endIPStr := strings.TrimSpace(parts[1]) + startIP := net.ParseIP(startIPStr) + if startIP == nil { + return nil, fmt.Errorf("%s", i18n.Tr("parser_invalid_start_ip", startIPStr)) + } + + if len(endIPStr) < 4 || !strings.Contains(endIPStr, ".") { + endNum, err := strconv.Atoi(endIPStr) + if err != nil || endNum > 255 { + return nil, fmt.Errorf("%s", i18n.Tr("parser_invalid_ip_end_val", endIPStr)) + } + parts := strings.Split(startIPStr, ".") + if len(parts) != 4 { + return nil, fmt.Errorf("%s", i18n.Tr("parser_invalid_ip_fmt", startIPStr)) + } + parts[3] = strconv.Itoa(endNum) + endIPStr = strings.Join(parts, ".") + } + + start, ok := ipToUint32(startIP) + if !ok { + return nil, fmt.Errorf("%s", i18n.GetText("parser_ipv4_only")) + } + end, ok := ipToUint32(net.ParseIP(endIPStr)) + if !ok { + return nil, fmt.Errorf("%s", i18n.Tr("parser_invalid_end_ip", endIPStr)) + } + if start > end { + return nil, fmt.Errorf("%s", i18n.GetText("parser_start_gt_end")) + } + return &cidrHostSource{current: start, end: end}, nil +} + +func closeHostSources(sources []hostSource) { + for _, src := range sources { + _ = src.Close() + } +} + +type hostMatcher struct { + exact map[string]struct{} + ranges []ipRange +} + +type ipRange struct { + start uint32 + end uint32 +} + +func newHostMatcher() *hostMatcher { + return &hostMatcher{exact: make(map[string]struct{})} +} + +func (m *hostMatcher) add(input string) error { + for _, h := range strings.Split(input, ",") { + h = strings.TrimSpace(h) + if h == "" { + continue + } + switch { + case h == "192": + if err := m.addCIDR("192.168.0.0/16"); err != nil { + return err + } + case h == "172": + if err := m.addCIDR("172.16.0.0/12"); err != nil { + return err + } + case h == "10": + if err := m.addCIDR("10.0.0.0/8"); err != nil { + return err + } + case strings.Contains(h, "/"): + if err := m.addCIDR(h); err != nil { + return err + } + case strings.Contains(h, "-") && !strings.Contains(h, ":") && looksLikeIPRange(h): + if err := m.addRange(h); err != nil { + return err + } + default: + m.exact[h] = struct{}{} + } + } + return nil +} + +func (m *hostMatcher) addCIDR(cidr string) error { + src, err := newCIDRHostSource(cidr) + if err != nil { + return err + } + rangeSrc, ok := src.(*cidrHostSource) + if !ok { + return fmt.Errorf("%s", i18n.GetText("parser_ipv4_only")) + } + m.ranges = append(m.ranges, ipRange{start: rangeSrc.current, end: rangeSrc.end}) + return nil +} + +func (m *hostMatcher) addRange(rangeStr string) error { + src, err := newRangeHostSource(rangeStr) + if err != nil { + return err + } + rangeSrc, ok := src.(*cidrHostSource) + if !ok { + return fmt.Errorf("%s", i18n.GetText("parser_ipv4_only")) + } + m.ranges = append(m.ranges, ipRange{start: rangeSrc.current, end: rangeSrc.end}) + return nil +} + +func (m *hostMatcher) match(host string) bool { + if _, ok := m.exact[host]; ok { + return true + } + ip, ok := ipToUint32(net.ParseIP(host)) + if !ok { + return false + } + for _, r := range m.ranges { + if ip >= r.start && ip <= r.end { + return true + } + } + return false +} + +func ipToUint32(ip net.IP) (uint32, bool) { + ip4 := ip.To4() + if ip4 == nil { + return 0, false + } + return uint32(ip4[0])<<24 | uint32(ip4[1])<<16 | uint32(ip4[2])<<8 | uint32(ip4[3]), true +} + +func uint32ToIP(v uint32) string { + return fmt.Sprintf("%d.%d.%d.%d", byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) +} diff --git a/common/parsers/host_iterator_test.go b/common/parsers/host_iterator_test.go new file mode 100644 index 0000000..5d7c9de --- /dev/null +++ b/common/parsers/host_iterator_test.go @@ -0,0 +1,61 @@ +package parsers + +import ( + "context" + "reflect" + "testing" +) + +func TestHostIteratorCIDRBatch(t *testing.T) { + iter, err := NewHostIterator("192.168.1.0/30", "", "") + if err != nil { + t.Fatalf("NewHostIterator error = %v", err) + } + defer iter.Close() + + batch, err := iter.NextBatch(context.Background(), 10) + if err != nil { + t.Fatalf("NextBatch error = %v", err) + } + + want := []string{"192.168.1.1", "192.168.1.2"} + if !reflect.DeepEqual(batch, want) { + t.Fatalf("batch = %#v, want %#v", batch, want) + } +} + +func TestHostIteratorDoesNotExpandWholeRangeAtOnce(t *testing.T) { + iter, err := NewHostIterator("10", "", "") + if err != nil { + t.Fatalf("NewHostIterator error = %v", err) + } + defer iter.Close() + + batch, err := iter.NextBatch(context.Background(), 3) + if err != nil { + t.Fatalf("NextBatch error = %v", err) + } + + want := []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"} + if !reflect.DeepEqual(batch, want) { + t.Fatalf("batch = %#v, want %#v", batch, want) + } +} + +func TestHostIteratorExcludeCIDR(t *testing.T) { + iter, err := NewHostIterator("192.168.1.0/29", "", "192.168.1.2-192.168.1.4") + if err != nil { + t.Fatalf("NewHostIterator error = %v", err) + } + defer iter.Close() + + batch, err := iter.NextBatch(context.Background(), 10) + if err != nil { + t.Fatalf("NextBatch error = %v", err) + } + + want := []string{"192.168.1.1", "192.168.1.5", "192.168.1.6"} + if !reflect.DeepEqual(batch, want) { + t.Fatalf("batch = %#v, want %#v", batch, want) + } +} diff --git a/core/alive_scanner.go b/core/alive_scanner.go index 9a21d8f..e421d25 100644 --- a/core/alive_scanner.go +++ b/core/alive_scanner.go @@ -63,52 +63,54 @@ func (s *AliveScanStrategy) Execute(ctx context.Context, session *common.ScanSes // 执行存活探测 s.performAliveScan(ctx, info, session) - - // 输出统计信息 - s.outputStats(session) } // performAliveScan 执行存活探测 func (s *AliveScanStrategy) performAliveScan(ctx context.Context, info common.HostInfo, session *common.ScanSession) { - // 解析目标主机 - hosts, err := parsers.ParseIP(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts) + iter, err := parsers.NewHostIterator(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts) if err != nil { session.LogError(i18n.Tr("parse_target_failed", err)) return } + defer func() { + _ = iter.Close() + }() - if len(hosts) == 0 { + s.stats.TotalHosts = 0 + s.stats.AliveHosts = 0 + s.stats.DeadHosts = 0 + + for { + hosts, err := iter.NextBatch(ctx, targetHostBatchSize(session.Config)) + if err != nil { + session.LogError(i18n.Tr("parse_target_failed", err)) + return + } + if len(hosts) == 0 { + break + } + + s.stats.TotalHosts += len(hosts) + aliveList := CheckLive(ctx, hosts, false, session) + s.stats.AliveHosts += len(aliveList) + for _, host := range aliveList { + session.LogSuccess(fmt.Sprintf("alive %s", host)) + } + } + + if s.stats.TotalHosts == 0 { session.LogError(i18n.GetText("parse_error_no_hosts")) return } - // 初始化统计信息 - s.stats.TotalHosts = len(hosts) - s.stats.AliveHosts = 0 - s.stats.DeadHosts = 0 - - // 执行存活检测 - aliveList := CheckLive(ctx, hosts, false, session) // 使用ICMP探测 - - // 更新统计信息 - s.stats.AliveHosts = len(aliveList) s.stats.DeadHosts = s.stats.TotalHosts - s.stats.AliveHosts s.stats.ScanDuration = time.Since(s.startTime) - s.stats.AliveHostList = aliveList // 存储存活主机列表 if s.stats.TotalHosts > 0 { s.stats.SuccessRate = float64(s.stats.AliveHosts) / float64(s.stats.TotalHosts) * 100 } } -// outputStats 输出统计信息(精简版) -func (s *AliveScanStrategy) outputStats(session *common.ScanSession) { - // 只输出存活主机列表,不输出冗余统计 - for _, host := range s.stats.AliveHostList { - session.LogSuccess(fmt.Sprintf("alive %s", host)) - } -} - // PrepareTargets 存活探测不需要准备扫描目标 func (s *AliveScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo { // 存活探测不需要返回目标列表,因为它不进行后续扫描 diff --git a/core/host_batch.go b/core/host_batch.go new file mode 100644 index 0000000..696fd02 --- /dev/null +++ b/core/host_batch.go @@ -0,0 +1,22 @@ +package core + +import ( + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/parsers" +) + +const maxHostBatchSize = 65536 + +func targetHostBatchSize(config *common.Config) int { + size := parsers.DefaultHostBatchSize + if config != nil && config.ThreadNum > 0 { + threadWindow := config.ThreadNum * 8 + if threadWindow > size { + size = threadWindow + } + } + if size > maxHostBatchSize { + return maxHostBatchSize + } + return size +} diff --git a/core/port_scan.go b/core/port_scan.go index f1dc14c..7ac944e 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -437,7 +437,7 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s info = append(info, fmt.Sprintf("Version:%s", serviceInfo.Version)) } if len(info) > 0 { - msg.WriteString(fmt.Sprintf(" [%s]", strings.Join(info, " ||"))) + fmt.Fprintf(&msg, " [%s]", strings.Join(info, " ||")) } // Banner 信息 @@ -446,7 +446,7 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s if len(banner) > 80 { banner = banner[:80] + "..." } - msg.WriteString(fmt.Sprintf(" Banner:(%s)", banner)) + fmt.Fprintf(&msg, " Banner:(%s)", banner) } return msg.String() diff --git a/core/portfinger/match_engine.go b/core/portfinger/match_engine.go index dd6b6b4..c34458b 100644 --- a/core/portfinger/match_engine.go +++ b/core/portfinger/match_engine.go @@ -15,7 +15,7 @@ func BytesToRegexSafeString(b []byte) string { for _, c := range b { if c < 32 || c >= 128 { // 控制字符和高位字节转换为 \x{NN} 格式 - result.WriteString(fmt.Sprintf("\\x{%02x}", c)) + fmt.Fprintf(&result, "\\x{%02x}", c) } else { result.WriteByte(c) } diff --git a/core/service_scanner.go b/core/service_scanner.go index 14927ed..5aa3b28 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -145,46 +145,76 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm config := session.Config state := session.State - // 解析目标主机 - hosts, err := parsers.ParseIP(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts) + iter, err := parsers.NewHostIterator(info.Host, session.Params.HostsFile, session.Params.ExcludeHosts) if err != nil { session.LogError(fmt.Sprintf("%s: %v", i18n.GetText("parse_target_failed"), err)) return } + defer func() { + _ = iter.Close() + }() - // 主机存活检测 - if s.shouldPerformLivenessCheck(hosts, config) { - hosts = CheckLive(ctx, hosts, false, session) - session.LogInfo(i18n.Tr("alive_hosts_count_info", len(hosts))) + pluginsToRun, isCustomMode := s.GetPlugins(config) + totalAlive := 0 + sawHosts := false + + for { + hosts, err := iter.NextBatch(ctx, targetHostBatchSize(config)) + if err != nil { + session.LogError(fmt.Sprintf("%s: %v", i18n.GetText("parse_target_failed"), err)) + return + } + if len(hosts) == 0 { + break + } + sawHosts = true + + if s.shouldPerformLivenessCheck(hosts, config) { + hosts = CheckLive(ctx, hosts, false, session) + } + totalAlive += len(hosts) + if len(hosts) == 0 { + continue + } + + s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) + s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg) } - if len(hosts) == 0 && len(state.GetHostPorts()) == 0 { + if sawHosts && s.shouldReportAliveCount(config) { + session.LogInfo(i18n.Tr("alive_hosts_count_info", totalAlive)) + } + + if !sawHosts && len(state.GetHostPorts()) == 0 { return } - // UDP 插件并行分发:直接对存活主机发协议探测包,不走端口扫描 - if len(hosts) > 0 { - s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) + // 合并预设的 host:port + hostPorts := state.GetHostPorts() + if len(hostPorts) > 0 { + merged := mergeHostPorts(nil, hostPorts) + targets := s.convertToTargetInfos(merged, info) + for _, target := range targets { + for _, pluginName := range pluginsToRun { + if s.IsPluginApplicableByName(pluginName, target.Host, target.Port, isCustomMode, config) { + executeScanTask(ctx, session, pluginName, target, ch, wg) + } + } + } + state.ClearHostPorts() } +} - // 流式 channel:端口扫描发现开放端口后立即通知插件执行 +func (s *ServiceScanStrategy) scanHostBatch(ctx context.Context, session *common.ScanSession, hosts []string, info common.HostInfo, pluginsToRun []string, isCustomMode bool, ch chan struct{}, wg *sync.WaitGroup) { + config := session.Config stream := make(chan string, 64) - // 启动端口扫描 goroutine - go func() { - if len(hosts) > 0 { - EnhancedPortScan(ctx, hosts, config.Target.Ports, int64(config.Timeout.Seconds()), session, stream) - } else { - close(stream) - } - }() + go EnhancedPortScan(ctx, hosts, config.Target.Ports, int64(config.Timeout.Seconds()), session, stream) - // pipeline 消费:边收开放端口边执行插件 - pluginsToRun, isCustomMode := s.GetPlugins(config) cancelled := false for addr := range stream { if cancelled { - continue // ctx 已取消,排空 stream 防止写端阻塞 + continue } select { case <-ctx.Done(): @@ -202,21 +232,10 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm } } } +} - // 合并预设的 host:port - hostPorts := state.GetHostPorts() - if len(hostPorts) > 0 { - merged := mergeHostPorts(nil, hostPorts) - targets := s.convertToTargetInfos(merged, info) - for _, target := range targets { - for _, pluginName := range pluginsToRun { - if s.IsPluginApplicableByName(pluginName, target.Host, target.Port, isCustomMode, config) { - executeScanTask(ctx, session, pluginName, target, ch, wg) - } - } - } - state.ClearHostPorts() - } +func (s *ServiceScanStrategy) shouldReportAliveCount(config *common.Config) bool { + return !config.DisablePing } // dispatchUDPPlugins 分发UDP协议插件,跳过TCP端口扫描链路 diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index 900ba3a..00dc343 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -57,10 +57,10 @@ func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co // 成功后重新连接获取文件列表 fileList := p.getFileListAfterAuth(info, result.Username, result.Password, config, state) var output strings.Builder - output.WriteString(fmt.Sprintf("FTP %s %s:%s", target, result.Username, result.Password)) + fmt.Fprintf(&output, "FTP %s %s:%s", target, result.Username, result.Password) if len(fileList) > 0 { for _, file := range fileList { - output.WriteString(fmt.Sprintf("\n [->] %s", file)) + fmt.Fprintf(&output, "\n [->] %s", file) } } session.LogVuln(output.String()) @@ -205,7 +205,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn output.WriteString(i18n.Tr("ftp_anonymous_access_detail", target, cred.Username, cred.Password)) if len(fileList) > 0 { for _, file := range fileList { - output.WriteString(fmt.Sprintf("\n [->] %s", file)) + fmt.Fprintf(&output, "\n [->] %s", file) } } session.LogVuln(output.String()) diff --git a/plugins/services/ms17010.go b/plugins/services/ms17010.go index 920d3c0..f4e0524 100644 --- a/plugins/services/ms17010.go +++ b/plugins/services/ms17010.go @@ -143,11 +143,11 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred switch config.Shellcode { case "bind": output.WriteString("\n" + i18n.GetText("ms17010_exploit_bind_hint") + "\n") - output.WriteString(fmt.Sprintf(" nc %s 64531\n", info.Host)) + fmt.Fprintf(&output, " nc %s 64531\n", info.Host) case "add": output.WriteString("\n" + i18n.GetText("ms17010_exploit_add_hint") + "\n") output.WriteString(i18n.GetText("ms17010_exploit_add_credential") + "\n") - output.WriteString(fmt.Sprintf(" RDP: mstsc /v:%s\n", info.Host)) + fmt.Fprintf(&output, " RDP: mstsc /v:%s\n", info.Host) case "guest": output.WriteString("\n" + i18n.GetText("ms17010_exploit_guest_hint") + "\n") } diff --git a/plugins/services/smb.go b/plugins/services/smb.go index 632ba33..6270906 100644 --- a/plugins/services/smb.go +++ b/plugins/services/smb.go @@ -145,7 +145,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos } output.WriteString(i18n.Tr("smb_anonymous_access_detail", target, displayUser, cred.Password)) for _, share := range shareInfo { - output.WriteString(fmt.Sprintf("\n%s", share)) + fmt.Fprintf(&output, "\n%s", share) } common.LogSuccess(output.String()) diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index f5c2743..c4f79f6 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -899,7 +899,7 @@ func CheckInfoPoc(infostr string) string { func GetHeader(header map[string]string) string { var builder strings.Builder for name, values := range header { - builder.WriteString(fmt.Sprintf("%s: %s\n", name, values)) + fmt.Fprintf(&builder, "%s: %s\n", name, values) } builder.WriteString("\r\n") return builder.String()