From a92df59dfe3a5e1acc5426e6ca9baef345862605 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Tue, 19 May 2026 00:37:35 +0800 Subject: [PATCH] fix: NFS plugin RPC probe + UDP dispatch in auto mode NFS: use RPC NULL call to detect NFS service before MOUNT EXPORT UDP dispatch: query plugin registry directly in auto mode instead of GetPlugins which excludes UDP from FilterService --- core/service_scanner.go | 17 ++++++++--- plugins/services/nfs.go | 62 ++++++++++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/core/service_scanner.go b/core/service_scanner.go index a4b83df..b3470e0 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -221,12 +221,21 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm // dispatchUDPPlugins 分发UDP协议插件,跳过TCP端口扫描链路 func (s *ServiceScanStrategy) dispatchUDPPlugins(ctx context.Context, session *common.ScanSession, hosts []string, baseInfo common.HostInfo, config *common.Config, ch chan struct{}, wg *sync.WaitGroup) { - allPlugins, isCustomMode := s.GetPlugins(config) + _, isCustomMode := s.GetPlugins(config) var udpPlugins []string - for _, name := range allPlugins { - if plugins.IsUDP(name) { - if isCustomMode || plugins.IsSafe(name) { + if isCustomMode { + // custom mode: 只跑用户指定的 UDP 插件 + requested, _ := s.GetPlugins(config) + for _, name := range requested { + if plugins.IsUDP(name) { + udpPlugins = append(udpPlugins, name) + } + } + } else { + // auto mode: 跑所有已注册的安全 UDP 插件 + for _, name := range plugins.All() { + if plugins.IsUDP(name) && plugins.IsSafe(name) { udpPlugins = append(udpPlugins, name) } } diff --git a/plugins/services/nfs.go b/plugins/services/nfs.go index e55928e..c147a2b 100644 --- a/plugins/services/nfs.go +++ b/plugins/services/nfs.go @@ -34,30 +34,72 @@ func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co defer conn.Close() _ = conn.SetDeadline(time.Now().Add(timeout)) - exports, err := p.getExports(conn) - if err != nil { - return &ScanResult{Success: false, Service: "nfs"} + // NFS NULL call (program=100003, version=3, procedure=0) to confirm NFS service + if err := p.rpcNullCall(conn, 100003, 3); err != nil { + // Try v4 + _ = conn.SetDeadline(time.Now().Add(timeout)) + if err := p.rpcNullCall(conn, 100003, 4); err != nil { + return &ScanResult{Success: false, Service: "nfs"} + } } - if len(exports) == 0 { + // NFS confirmed. Try MOUNT EXPORT on a separate connection (port 2049 may also host mountd). + var exports []string + mountConn, err := session.DialTCP(ctx, "tcp", addr, timeout) + if err == nil { + _ = mountConn.SetDeadline(time.Now().Add(timeout)) + exports, _ = p.getExports(mountConn) + mountConn.Close() + } + + if len(exports) > 0 { return &ScanResult{ Success: true, - Type: plugins.ResultTypeService, + Type: plugins.ResultTypeVuln, Service: "nfs", - Banner: "NFS service detected (no exports)", + VulInfo: fmt.Sprintf("NFS Exported Shares: %v", exports), + Banner: fmt.Sprintf("NFS exports: %v", exports), } } - banner := fmt.Sprintf("NFS exports: %v", exports) return &ScanResult{ Success: true, - Type: plugins.ResultTypeVuln, + Type: plugins.ResultTypeService, Service: "nfs", - VulInfo: fmt.Sprintf("NFS Exported Shares: %v", exports), - Banner: banner, + Banner: "NFS service detected", } } +func (p *NFSPlugin) rpcNullCall(conn interface { + Read([]byte) (int, error) + Write([]byte) (int, error) +}, program, version uint32) error { + xid := uint32(0x12340000 + program) + rpcCall := p.buildRPCCall(xid, program, version, 0, nil) + rpcFragment := p.wrapRPCFragment(rpcCall) + + if _, err := conn.Write(rpcFragment); err != nil { + return err + } + + buf := make([]byte, 512) + n, err := conn.Read(buf) + if err != nil || n < 28 { + return fmt.Errorf("short response") + } + + reply := buf[4:n] + replyXID := binary.BigEndian.Uint32(reply[0:4]) + if replyXID != xid { + return fmt.Errorf("xid mismatch") + } + msgType := binary.BigEndian.Uint32(reply[4:8]) + if msgType != 1 { + return fmt.Errorf("not a reply") + } + return nil +} + func (p *NFSPlugin) getExports(conn interface { Read([]byte) (int, error) Write([]byte) (int, error)