mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
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
This commit is contained in:
+13
-4
@@ -221,12 +221,21 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
|
|||||||
|
|
||||||
// dispatchUDPPlugins 分发UDP协议插件,跳过TCP端口扫描链路
|
// 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) {
|
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
|
var udpPlugins []string
|
||||||
for _, name := range allPlugins {
|
if isCustomMode {
|
||||||
if plugins.IsUDP(name) {
|
// custom mode: 只跑用户指定的 UDP 插件
|
||||||
if isCustomMode || plugins.IsSafe(name) {
|
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)
|
udpPlugins = append(udpPlugins, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-10
@@ -34,30 +34,72 @@ func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
_ = conn.SetDeadline(time.Now().Add(timeout))
|
_ = conn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
|
||||||
exports, err := p.getExports(conn)
|
// NFS NULL call (program=100003, version=3, procedure=0) to confirm NFS service
|
||||||
if err != nil {
|
if err := p.rpcNullCall(conn, 100003, 3); err != nil {
|
||||||
return &ScanResult{Success: false, Service: "nfs"}
|
// 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{
|
return &ScanResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
Type: plugins.ResultTypeService,
|
Type: plugins.ResultTypeVuln,
|
||||||
Service: "nfs",
|
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{
|
return &ScanResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
Type: plugins.ResultTypeVuln,
|
Type: plugins.ResultTypeService,
|
||||||
Service: "nfs",
|
Service: "nfs",
|
||||||
VulInfo: fmt.Sprintf("NFS Exported Shares: %v", exports),
|
Banner: "NFS service detected",
|
||||||
Banner: banner,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func (p *NFSPlugin) getExports(conn interface {
|
||||||
Read([]byte) (int, error)
|
Read([]byte) (int, error)
|
||||||
Write([]byte) (int, error)
|
Write([]byte) (int, error)
|
||||||
|
|||||||
Reference in New Issue
Block a user