mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
fix: 修复实机测试发现的可靠性问题 (v2.2.0-rc.1)
- UDP 插件在 -p 指定端口时被跳过 - Redis exploit 无超时保护 / readReply 吞没非超时错误 - service_probe 连接丢失后静默成功 - SNMP 探测成功但终端无输出 - SSH 爆破不稳定 (并发过高 + 自适应超时过短 + 限流误判) - 进度条 isActive 竞态 新增 Config.ModuleTimeout() 协议级超时下限 (≥3s) 新增 ErrorTypeThrottle 限流错误分类
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
# fscan v2.2.0-rc.1
|
||||||
|
|
||||||
|
> ⚠️ **这是预发布版本 (Release Candidate)**,可能存在未发现的问题。
|
||||||
|
> 如果你在使用中遇到任何异常,请积极通过 [Issue](https://github.com/shadow1ng/fscan/issues/new/choose) 反馈,帮助我们尽快稳定正式版。
|
||||||
|
> 生产环境建议继续使用 [v2.1.3](https://github.com/shadow1ng/fscan/releases/tag/v2.1.3)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与 v2.2.0-rc 的变更
|
||||||
|
|
||||||
|
本版本聚焦**实机测试发现的可靠性问题修复**,无新功能。
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- **UDP 插件在 `-p` 指定端口时被跳过** — 用户指定 `-p 53,161` 等包含 UDP 端口时,DNS/SNMP 等 UDP 插件不会执行。现在按用户指定的端口过滤并正确调度
|
||||||
|
- **Redis exploit 操作无超时保护** — exploit 阶段移除了全部 deadline,服务端卡滞时 goroutine 永久阻塞。现在设置 30s 操作超时
|
||||||
|
- **Redis readReply 吞没非超时错误** — 只要读到任何数据就忽略所有错误,可能返回截断响应。现在仅对 timeout 类型错误做容忍
|
||||||
|
- **service_probe 连接丢失后静默成功** — Write/Read 在 Conn=nil 时返回 nil 而非错误,导致后续探测静默跳过。现在返回明确的 errConnLost
|
||||||
|
- **SNMP 探测成功但终端无输出** — SNMP 插件缺少 `session.LogVuln` 调用,成功结果只写入文件不在终端显示
|
||||||
|
|
||||||
|
### ⚡ 可靠性改善
|
||||||
|
|
||||||
|
- **协议级超时下限(ModuleTimeout)** — 新增 `Config.ModuleTimeout()` 方法,保证插件级交互超时不低于 3s。自适应系统将端口扫描超时压到 1s 时,SSH 握手/SNMP 探测/数据库认证等多轮交互协议不再受影响。全部 44 个服务插件已迁移
|
||||||
|
- **SSH 爆破并发优化** — SSH 并发从 30 降至 3,避免触发 OpenSSH MaxStartups 限流导致大量连接被丢弃
|
||||||
|
- **SSH 限流错误分类(ErrorTypeThrottle)** — 新增限流错误类型,区分服务端限流(MaxStartups)和真正的网络不可达。限流错误不计入连续失败计数,仅触发 500ms 退避后继续,避免误判目标不可达而提前放弃
|
||||||
|
- **SSH 握手 TCP deadline** — 在 SSH NewClientConn 前设置 TCP 级别 deadline 兜底整个握手过程,握手成功后清除
|
||||||
|
- **进度条竞态修复** — `ProgressManager.isActive` 从 `bool` 改为 `atomic.Bool`,消除 UpdateProgress 与 FinishProgress 之间的数据竞态
|
||||||
|
- **gmtls stdout 竞态修复** — 移除 `suppressGMTLSStdout` 中对 `os.Stdout` 的非同步重定向,消除与 gmtls 内部 goroutine 的数据竞态
|
||||||
|
- **Lint 清理** — 修复 cassandra/ipmi/mongodb/webscan 中的 ineffassign、unused、errcheck 问题
|
||||||
|
|
||||||
|
### 📊 实测验证
|
||||||
|
|
||||||
|
| 指标 | v2.2.0-rc | v2.2.0-rc.1 |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| SSH `-m ssh` 爆破成功率 | ~60% | 100% (10/10) |
|
||||||
|
| SNMP `-p 161` 终端输出 | ✗ 不显示 | ✓ 正常 |
|
||||||
|
| UDP 插件 `-p` 指定端口 | ✗ 跳过 | ✓ 正确调度 |
|
||||||
|
| Redis exploit 超时保护 | ✗ 无 | ✓ 30s |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 反馈与贡献
|
||||||
|
|
||||||
|
- 🐛 发现 Bug → [提交 Bug 报告](https://github.com/shadow1ng/fscan/issues/new?template=bug_report.yml)
|
||||||
|
- 🎯 结果不准 → [提交误报/漏报](https://github.com/shadow1ng/fscan/issues/new?template=false_positive.yml)
|
||||||
|
- ✨ 功能建议 → [提交功能请求](https://github.com/shadow1ng/fscan/issues/new?template=feature_request.yml)
|
||||||
|
- 💬 使用疑问 → [Discussions](https://github.com/shadow1ng/fscan/discussions)
|
||||||
@@ -179,6 +179,17 @@ func clonePortMap(values map[int][]string) map[int][]string {
|
|||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minModuleTimeout = 3 * time.Second
|
||||||
|
|
||||||
|
// ModuleTimeout 返回插件级超时(用于弱口令测试、服务交互等多轮协议)
|
||||||
|
// 保证下限 3s,避免自适应把端口扫描超时压低后影响 SSH/SNMP 等交互型协议
|
||||||
|
func (c *Config) ModuleTimeout() time.Duration {
|
||||||
|
if c.Timeout >= minModuleTimeout {
|
||||||
|
return c.Timeout
|
||||||
|
}
|
||||||
|
return minModuleTimeout
|
||||||
|
}
|
||||||
|
|
||||||
// NewConfig 创建带默认值的Config(后备用,正常流程使用BuildConfigFromFlags)
|
// NewConfig 创建带默认值的Config(后备用,正常流程使用BuildConfigFromFlags)
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ const (
|
|||||||
|
|
||||||
// 版本信息,通过 ldflags 注入
|
// 版本信息,通过 ldflags 注入
|
||||||
var (
|
var (
|
||||||
version = "2.2.0-rc"
|
version = "2.2.0-rc.1"
|
||||||
commit = "unknown"
|
commit = "unknown"
|
||||||
date = "unknown"
|
date = "unknown"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ type ProgressManager struct {
|
|||||||
current atomic.Int64
|
current atomic.Int64
|
||||||
description string
|
description string
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
isActive bool
|
isActive atomic.Bool
|
||||||
terminalHeight int
|
terminalHeight int
|
||||||
reservedLines int // 为进度条保留的行数
|
reservedLines int // 为进度条保留的行数
|
||||||
lastContentLine int // 最后一行内容的位置
|
lastContentLine int // 最后一行内容的位置
|
||||||
@@ -121,7 +121,7 @@ func (pm *ProgressManager) InitProgress(total int64, description string) {
|
|||||||
pm.current.Store(0)
|
pm.current.Store(0)
|
||||||
pm.description = description
|
pm.description = description
|
||||||
pm.startTime = time.Now()
|
pm.startTime = time.Now()
|
||||||
pm.isActive = true
|
pm.isActive.Store(true)
|
||||||
pm.enabled = true
|
pm.enabled = true
|
||||||
pm.lastActivity = time.Now()
|
pm.lastActivity = time.Now()
|
||||||
pm.spinnerIndex = 0
|
pm.spinnerIndex = 0
|
||||||
@@ -139,7 +139,7 @@ func (pm *ProgressManager) InitProgress(total int64, description string) {
|
|||||||
|
|
||||||
// UpdateProgress 更新进度
|
// UpdateProgress 更新进度
|
||||||
func (pm *ProgressManager) UpdateProgress(increment int64) {
|
func (pm *ProgressManager) UpdateProgress(increment int64) {
|
||||||
if !pm.enabled || !pm.isActive {
|
if !pm.enabled || !pm.isActive.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ func (pm *ProgressManager) UpdateProgress(increment int64) {
|
|||||||
|
|
||||||
// FinishProgress 完成进度条
|
// FinishProgress 完成进度条
|
||||||
func (pm *ProgressManager) FinishProgress() {
|
func (pm *ProgressManager) FinishProgress() {
|
||||||
if !pm.enabled || !pm.isActive {
|
if !pm.enabled || !pm.isActive.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ func (pm *ProgressManager) FinishProgress() {
|
|||||||
|
|
||||||
// 清理进度条区域,恢复正常输出
|
// 清理进度条区域,恢复正常输出
|
||||||
pm.clearProgressArea()
|
pm.clearProgressArea()
|
||||||
pm.isActive = false
|
pm.isActive.Store(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupProgressSpace 设置进度条空间
|
// setupProgressSpace 设置进度条空间
|
||||||
@@ -342,7 +342,7 @@ func (pm *ProgressManager) clearProgressArea() {
|
|||||||
func (pm *ProgressManager) IsActive() bool {
|
func (pm *ProgressManager) IsActive() bool {
|
||||||
pm.mu.RLock()
|
pm.mu.RLock()
|
||||||
defer pm.mu.RUnlock()
|
defer pm.mu.RUnlock()
|
||||||
return pm.isActive && pm.enabled
|
return pm.isActive.Load() && pm.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTerminalHeight 获取终端高度
|
// getTerminalHeight 获取终端高度
|
||||||
@@ -479,7 +479,7 @@ func (pm *ProgressManager) GetPercent() float64 {
|
|||||||
pm.mu.RLock()
|
pm.mu.RLock()
|
||||||
defer pm.mu.RUnlock()
|
defer pm.mu.RUnlock()
|
||||||
|
|
||||||
if !pm.isActive || pm.total.Load() == 0 {
|
if !pm.isActive.Load() || pm.total.Load() == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return float64(pm.current.Load()) / float64(pm.total.Load()) * 100
|
return float64(pm.current.Load()) / float64(pm.total.Load()) * 100
|
||||||
@@ -517,7 +517,7 @@ func LogWithProgress(message string) {
|
|||||||
|
|
||||||
// renderProgressUnsafe 不加锁的进度条渲染(内部使用)
|
// renderProgressUnsafe 不加锁的进度条渲染(内部使用)
|
||||||
func (pm *ProgressManager) renderProgressUnsafe() {
|
func (pm *ProgressManager) renderProgressUnsafe() {
|
||||||
if !pm.enabled || !pm.isActive {
|
if !pm.enabled || !pm.isActive.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,7 +586,7 @@ func (pm *ProgressManager) startActivityIndicator() {
|
|||||||
select {
|
select {
|
||||||
case <-pm.activityTicker.C:
|
case <-pm.activityTicker.C:
|
||||||
// 只有在活跃状态下才更新指示器
|
// 只有在活跃状态下才更新指示器
|
||||||
if pm.isActive && pm.enabled {
|
if pm.isActive.Load() && pm.enabled {
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars)
|
pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars)
|
||||||
pm.mu.Unlock()
|
pm.mu.Unlock()
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const (
|
|||||||
defaultIntensity = 7 // 默认探测强度 (1-9)
|
defaultIntensity = 7 // 默认探测强度 (1-9)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errConnLost = errors.New("connection lost and reconnect failed")
|
||||||
|
|
||||||
// sslSecondProbes SSL服务二次探测的探针名称
|
// sslSecondProbes SSL服务二次探测的探针名称
|
||||||
var sslSecondProbes = []string{"TerminalServerCookie", "TerminalServer"}
|
var sslSecondProbes = []string{"TerminalServerCookie", "TerminalServer"}
|
||||||
|
|
||||||
@@ -527,7 +529,7 @@ var defaultReadTimeoutMS = WrTimeout * 1000
|
|||||||
// Write 写入数据到连接
|
// Write 写入数据到连接
|
||||||
func (i *Info) Write(msg []byte) error {
|
func (i *Info) Write(msg []byte) error {
|
||||||
if i.Conn == nil {
|
if i.Conn == nil {
|
||||||
return nil
|
return errConnLost
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置写入超时
|
// 设置写入超时
|
||||||
@@ -570,7 +572,7 @@ func (i *Info) Write(msg []byte) error {
|
|||||||
// Read 从连接读取响应
|
// Read 从连接读取响应
|
||||||
func (i *Info) Read() ([]byte, error) {
|
func (i *Info) Read() ([]byte, error) {
|
||||||
if i.Conn == nil {
|
if i.Conn == nil {
|
||||||
return nil, nil
|
return nil, errConnLost
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置读取超时(使用动态超时)
|
// 设置读取超时(使用动态超时)
|
||||||
|
|||||||
+15
-4
@@ -186,10 +186,8 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
|
|||||||
ep.TuneConfig(config, session)
|
ep.TuneConfig(config, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅在默认端口扫描时调度 UDP 插件(用户指定 -p 时跳过,避免不相关的 UDP 探测拖慢扫描)
|
// UDP 插件调度:默认端口模式全量调度,用户指定 -p 时只调度端口有交集的 UDP 插件
|
||||||
if config.Target.Ports == "" || config.Target.Ports == "all" {
|
s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg)
|
||||||
s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg)
|
|
||||||
}
|
|
||||||
s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg)
|
s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,9 +269,22 @@ func (s *ServiceScanStrategy) dispatchUDPPlugins(ctx context.Context, session *c
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户指定 -p 时,只调度端口有交集的 UDP 插件
|
||||||
|
var userPorts map[int]bool
|
||||||
|
if config.Target.Ports != "" && config.Target.Ports != "all" {
|
||||||
|
parsed := parsers.ParsePort(config.Target.Ports)
|
||||||
|
userPorts = make(map[int]bool, len(parsed))
|
||||||
|
for _, p := range parsed {
|
||||||
|
userPorts[p] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
for _, pluginName := range udpPlugins {
|
for _, pluginName := range udpPlugins {
|
||||||
for _, port := range plugins.GetPluginPorts(pluginName) {
|
for _, port := range plugins.GetPluginPorts(pluginName) {
|
||||||
|
if userPorts != nil && !userPorts[port] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
target := baseInfo
|
target := baseInfo
|
||||||
target.Host = host
|
target.Host = host
|
||||||
target.Port = port
|
target.Port = port
|
||||||
|
|||||||
+8
-31
@@ -7,7 +7,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -58,17 +57,14 @@ func DetectHTTPSchemeContext(ctx context.Context, host string, port int, config
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 第二步:尝试国密TLS握手(GM TLS fallback)
|
// 第二步:尝试国密TLS握手(GM TLS fallback)
|
||||||
// 抑制 gmtls 库的 fmt.Println("handshake error") 噪声输出
|
gmConn, gmErr := gmtls.DialWithDialer(
|
||||||
gmConn, gmErr := suppressGMTLSStdout(func() (net.Conn, error) {
|
tlsDialer,
|
||||||
return gmtls.DialWithDialer(
|
"tcp", addr,
|
||||||
tlsDialer,
|
&gmtls.Config{
|
||||||
"tcp", addr,
|
GMSupport: gmtls.NewGMSupport(),
|
||||||
&gmtls.Config{
|
InsecureSkipVerify: true,
|
||||||
GMSupport: gmtls.NewGMSupport(),
|
},
|
||||||
InsecureSkipVerify: true,
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
if gmErr == nil {
|
if gmErr == nil {
|
||||||
_ = gmConn.Close()
|
_ = gmConn.Close()
|
||||||
@@ -503,22 +499,3 @@ func hasMalformedURLPort(host string) bool {
|
|||||||
return strings.Contains(host, ":")
|
return strings.Contains(host, ":")
|
||||||
}
|
}
|
||||||
|
|
||||||
// suppressGMTLSStdout 抑制 gmtls 库硬编码的 fmt.Println("handshake error") 输出
|
|
||||||
// gmtls/conn.go:1304 在握手失败时直接 Println 到 os.Stdout,无法通过 API 关闭
|
|
||||||
var gmtlsStdoutMu sync.Mutex
|
|
||||||
|
|
||||||
func suppressGMTLSStdout(fn func() (net.Conn, error)) (net.Conn, error) {
|
|
||||||
gmtlsStdoutMu.Lock()
|
|
||||||
orig := os.Stdout
|
|
||||||
devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
|
|
||||||
if err == nil {
|
|
||||||
os.Stdout = devNull
|
|
||||||
}
|
|
||||||
conn, dialErr := fn()
|
|
||||||
os.Stdout = orig
|
|
||||||
if devNull != nil {
|
|
||||||
_ = devNull.Close()
|
|
||||||
}
|
|
||||||
gmtlsStdoutMu.Unlock()
|
|
||||||
return conn, dialErr
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -708,12 +708,8 @@ func TestIsWebServiceByFingerprint_Priority(t *testing.T) {
|
|||||||
|
|
||||||
// TestDetectHTTPScheme 测试HTTP/HTTPS协议智能检测
|
// TestDetectHTTPScheme 测试HTTP/HTTPS协议智能检测
|
||||||
func TestDetectHTTPScheme(t *testing.T) {
|
func TestDetectHTTPScheme(t *testing.T) {
|
||||||
// 设置WebTimeout避免测试超时
|
cfg := common.NewConfig()
|
||||||
cfg := common.GetGlobalConfig()
|
|
||||||
oldTimeout := cfg.Network.WebTimeout
|
|
||||||
cfg.Network.WebTimeout = 2 * time.Second
|
cfg.Network.WebTimeout = 2 * time.Second
|
||||||
defer func() { cfg.Network.WebTimeout = oldTimeout }()
|
|
||||||
|
|
||||||
session := common.NewScanSession(cfg, common.NewState(), common.GetFlagVars())
|
session := common.NewScanSession(cfg, common.NewState(), common.GetFlagVars())
|
||||||
|
|
||||||
t.Run("HTTPS服务器检测", func(t *testing.T) {
|
t.Run("HTTPS服务器检测", func(t *testing.T) {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (p *ActiveMQPlugin) createAuthFunc(info *common.HostInfo, session *common.S
|
|||||||
func (p *ActiveMQPlugin) doActiveMQAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
func (p *ActiveMQPlugin) doActiveMQAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
config := session.Config
|
config := session.Config
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
resultChan := make(chan *AuthResult, 1)
|
resultChan := make(chan *AuthResult, 1)
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ func classifyActiveMQErrorType(err error) ErrorType {
|
|||||||
|
|
||||||
// authenticateSTOMP 使用STOMP协议认证ActiveMQ
|
// authenticateSTOMP 使用STOMP协议认证ActiveMQ
|
||||||
func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string, config *common.Config) (bool, error) {
|
func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string, config *common.Config) (bool, error) {
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
if err := rejectLineBreaks(username, password); err != nil {
|
if err := rejectLineBreaks(username, password); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -202,7 +202,7 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str
|
|||||||
// identifyService ActiveMQ服务识别
|
// identifyService ActiveMQ服务识别
|
||||||
func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func NewBACnetPlugin() *BACnetPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const (
|
|||||||
|
|
||||||
func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
addr := info.Target()
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||||
@@ -270,7 +270,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
|
|||||||
state := session.State
|
state := session.State
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
addr := info.Target()
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||||
@@ -286,7 +286,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
|
|||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
opcode, body, err := cqlRecv(conn)
|
opcode, _, err := cqlRecv(conn)
|
||||||
if err != nil || opcode != cqlOpReady {
|
if err != nil || opcode != cqlOpReady {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -296,6 +296,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
|
|||||||
if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil {
|
if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var body []byte
|
||||||
opcode, body, err = cqlRecv(conn)
|
opcode, body, err = cqlRecv(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -321,7 +322,7 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host
|
|||||||
state := session.State
|
state := session.State
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
addr := info.Target()
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ credential_tester.go - 统一凭据测试框架
|
|||||||
type ErrorType int
|
type ErrorType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试
|
ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试
|
||||||
ErrorTypeNetwork // 网络错误 - 连接问题,可重试
|
ErrorTypeNetwork // 网络错误 - 连接不可达,可重试但计入连续失败
|
||||||
ErrorTypeUnknown // 未知错误
|
ErrorTypeThrottle // 限流错误 - 服务端拒绝连接(MaxStartups等),退避后重试,不计入连续失败
|
||||||
|
ErrorTypeUnknown // 未知错误
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -321,10 +322,13 @@ func workerTestCredentials(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跟踪连续网络错误
|
// 跟踪连续网络错误(限流错误不计入,只做短暂退避)
|
||||||
if errType == ErrorTypeNetwork {
|
switch errType {
|
||||||
|
case ErrorTypeNetwork:
|
||||||
consecutiveNetErrors++
|
consecutiveNetErrors++
|
||||||
} else {
|
case ErrorTypeThrottle:
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
default:
|
||||||
consecutiveNetErrors = 0
|
consecutiveNetErrors = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,8 +378,8 @@ func testCredentialWithRetry(
|
|||||||
case ErrorTypeAuth:
|
case ErrorTypeAuth:
|
||||||
// 认证错误(密码错误),不重试
|
// 认证错误(密码错误),不重试
|
||||||
return nil, result.ErrorType
|
return nil, result.ErrorType
|
||||||
case ErrorTypeNetwork, ErrorTypeUnknown:
|
case ErrorTypeNetwork, ErrorTypeThrottle, ErrorTypeUnknown:
|
||||||
// 网络错误或未知错误,可以重试(可能是服务端限流等临时问题)
|
// 网络/限流/未知错误,可以重试
|
||||||
if attempt < testConfig.MaxRetries-1 {
|
if attempt < testConfig.MaxRetries-1 {
|
||||||
timer := time.NewTimer(testConfig.RetryDelay)
|
timer := time.NewTimer(testConfig.RetryDelay)
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func NewDNSPlugin() *DNSPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func NewDNSTCPPlugin() *DNSTCPPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *DNSTCPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *DNSTCPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo, s
|
|||||||
func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) bool {
|
func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) bool {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: config.Timeout,
|
Timeout: config.ModuleTimeout(),
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{
|
return &ScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -61,7 +61,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
|||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
// 设置超时
|
// 设置超时
|
||||||
_ = conn.SetDeadline(time.Now().Add(config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout()))
|
||||||
|
|
||||||
// 执行RPC网络发现
|
// 执行RPC网络发现
|
||||||
networkInfo, err := p.performNetworkDiscovery(conn)
|
networkInfo, err := p.performNetworkDiscovery(conn)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (p *FTPPlugin) createAuthFunc(info *common.HostInfo, config *common.Config,
|
|||||||
func (p *FTPPlugin) doFTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *FTPPlugin) doFTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.Timeout)...)
|
conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.ModuleTimeout())...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
return &AuthResult{
|
return &AuthResult{
|
||||||
@@ -162,7 +162,7 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo, session *common.ScanS
|
|||||||
state := session.State
|
state := session.State
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout))
|
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
return &ScanResult{
|
return &ScanResult{
|
||||||
@@ -241,7 +241,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn
|
|||||||
func (p *FTPPlugin) getFileListAfterAuth(info *common.HostInfo, username, password string, config *common.Config, state *common.State) []string {
|
func (p *FTPPlugin) getFileListAfterAuth(info *common.HostInfo, username, password string, config *common.Config, state *common.State) []string {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout))
|
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func NewIMAPPlugin() *IMAPPlugin {
|
|||||||
|
|
||||||
func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func NewIPMIPlugin() *IPMIPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *IPMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *IPMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
@@ -60,8 +60,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getChannelAuth 需要独立连接,暂不执行(核心检测已完成)
|
|
||||||
|
|
||||||
return &ScanResult{
|
return &ScanResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
Type: plugins.ResultTypeVuln,
|
Type: plugins.ResultTypeVuln,
|
||||||
@@ -71,68 +69,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *IPMIPlugin) getChannelAuth(conn interface {
|
|
||||||
Read([]byte) (int, error)
|
|
||||||
Write([]byte) (int, error)
|
|
||||||
SetDeadline(time.Time) error
|
|
||||||
}) string {
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(2 * time.Second))
|
|
||||||
|
|
||||||
// IPMI Get Channel Authentication Capabilities
|
|
||||||
// RMCP header + IPMI session wrapper + message
|
|
||||||
pkt := []byte{
|
|
||||||
0x06, 0x00, 0xff, 0x07, // RMCP: version, reserved, seq=0xff, class=IPMI
|
|
||||||
0x00, 0x00, 0x00, 0x00, // auth type = none
|
|
||||||
0x00, 0x00, 0x00, 0x00, // session seq
|
|
||||||
0x00, 0x00, 0x00, 0x00, // session id
|
|
||||||
0x09, // message length
|
|
||||||
0x20, // target = BMC
|
|
||||||
0x18, // netFn=App(6) << 2 | lun=0
|
|
||||||
0xc8, // checksum
|
|
||||||
0x81, // source
|
|
||||||
0x00, // seq
|
|
||||||
0x38, // cmd = Get Channel Auth Capabilities
|
|
||||||
0x8e, // channel=14 (current), IPMI v2.0
|
|
||||||
0x04, // privilege = Administrator
|
|
||||||
0xb5, // checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := conn.Write(pkt); err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 512)
|
|
||||||
n, err := conn.Read(buf)
|
|
||||||
if err != nil || n < 30 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse auth capabilities from response
|
|
||||||
if n >= 27 {
|
|
||||||
authTypes := buf[22]
|
|
||||||
var methods []string
|
|
||||||
if authTypes&0x01 != 0 {
|
|
||||||
methods = append(methods, "none")
|
|
||||||
}
|
|
||||||
if authTypes&0x02 != 0 {
|
|
||||||
methods = append(methods, "md2")
|
|
||||||
}
|
|
||||||
if authTypes&0x04 != 0 {
|
|
||||||
methods = append(methods, "md5")
|
|
||||||
}
|
|
||||||
if authTypes&0x10 != 0 {
|
|
||||||
methods = append(methods, "password")
|
|
||||||
}
|
|
||||||
if authTypes&0x20 != 0 {
|
|
||||||
methods = append(methods, "oem")
|
|
||||||
}
|
|
||||||
if len(methods) > 0 {
|
|
||||||
return fmt.Sprintf("[auth: %v]", methods)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterUDPPluginWithPorts("ipmi", func() Plugin {
|
RegisterUDPPluginWithPorts("ipmi", func() Plugin {
|
||||||
return NewIPMIPlugin()
|
return NewIPMIPlugin()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func NewJDWPPlugin() *JDWPPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *JDWPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *JDWPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
|
|||||||
|
|
||||||
func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", target)
|
conn, err := dialer.DialContext(ctx, "tcp", target)
|
||||||
@@ -242,7 +242,7 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
|||||||
config := session.Config
|
config := session.Config
|
||||||
state := session.State
|
state := session.State
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", target)
|
conn, err := dialer.DialContext(ctx, "tcp", target)
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses
|
|||||||
resultChan := make(chan result, 1)
|
resultChan := make(chan result, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- result{nil, err}
|
resultChan <- result{nil, err}
|
||||||
return
|
return
|
||||||
@@ -222,7 +222,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses
|
|||||||
} else {
|
} else {
|
||||||
conn = ldaplib.NewConn(tcpConn, false)
|
conn = ldaplib.NewConn(tcpConn, false)
|
||||||
}
|
}
|
||||||
conn.SetTimeout(session.Config.Timeout)
|
conn.SetTimeout(session.Config.ModuleTimeout())
|
||||||
conn.Start()
|
conn.Start()
|
||||||
|
|
||||||
resultChan <- result{conn, nil}
|
resultChan <- result{conn, nil}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *comm
|
|||||||
|
|
||||||
func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn {
|
func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
|
|
||||||
connChan := make(chan net.Conn, 1)
|
connChan := make(chan net.Conn, 1)
|
||||||
|
|
||||||
@@ -97,12 +97,12 @@ func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.H
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MemcachedPlugin) testBasicCommand(conn net.Conn, config *common.Config) bool {
|
func (p *MemcachedPlugin) testBasicCommand(conn net.Conn, config *common.Config) bool {
|
||||||
_ = conn.SetWriteDeadline(time.Now().Add(config.Timeout))
|
_ = conn.SetWriteDeadline(time.Now().Add(config.ModuleTimeout()))
|
||||||
if _, err := conn.Write([]byte("version\r\n")); err != nil {
|
if _, err := conn.Write([]byte("version\r\n")); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(config.Timeout))
|
_ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout()))
|
||||||
response := make([]byte, 1024)
|
response := make([]byte, 1024)
|
||||||
n, err := conn.Read(response)
|
n, err := conn.Read(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func NewModbusPlugin() *ModbusPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ModbusPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *ModbusPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Con
|
|||||||
|
|
||||||
func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
addr := info.Target()
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
conn, err := dialTCP(ctx, addr, timeout)
|
conn, err := dialTCP(ctx, addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -369,6 +369,7 @@ type mongoCommandReply struct {
|
|||||||
errmsg string
|
errmsg string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func parseMongoCommandReply(doc []byte) (mongoCommandReply, error) {
|
func parseMongoCommandReply(doc []byte) (mongoCommandReply, error) {
|
||||||
var reply mongoCommandReply
|
var reply mongoCommandReply
|
||||||
if len(doc) < 5 {
|
if len(doc) < 5 {
|
||||||
@@ -557,11 +558,6 @@ func dialTCP(ctx context.Context, addr string, timeout time.Duration) (net.Conn,
|
|||||||
return dialer.DialContext(ctx, "tcp", addr)
|
return dialer.DialContext(ctx, "tcp", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// base64EncodeStr Base64 编码(标准编码)
|
|
||||||
func base64EncodeStr(s string) string {
|
|
||||||
return base64.StdEncoding.EncodeToString([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// randomString 生成加密安全的随机字符串
|
// randomString 生成加密安全的随机字符串
|
||||||
func randomString(n int) string {
|
func randomString(n int) string {
|
||||||
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
@@ -641,7 +637,7 @@ func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, packet []byte, session *common.ScanSession) (string, error) {
|
func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, packet []byte, session *common.ScanSession) (string, error) {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err)
|
return "", fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err)
|
||||||
}
|
}
|
||||||
@@ -653,7 +649,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.Timeout)); deadlineErr != nil {
|
if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); deadlineErr != nil {
|
||||||
return "", deadlineErr
|
return "", deadlineErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewMQTTPlugin() *MQTTPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MQTTPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *MQTTPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,13 +291,13 @@ func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) {
|
func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_connection_error"), err)
|
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_connection_error"), err)
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
if err = conn.SetDeadline(time.Now().Add(session.Config.Timeout)); err != nil {
|
if err = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); err != nil {
|
||||||
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_set_timeout_error"), err)
|
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_set_timeout_error"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ func (p *MSSQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
|
|||||||
|
|
||||||
// doMSSQLAuth 执行MSSQL认证
|
// doMSSQLAuth 执行MSSQL认证
|
||||||
func (p *MSSQLPlugin) doMSSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *MSSQLPlugin) doMSSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
authCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
authCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
_, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.Timeout)
|
_, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
return &AuthResult{
|
return &AuthResult{
|
||||||
@@ -129,10 +129,10 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
|||||||
state := session.State
|
state := session.State
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
identifyCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
identifyCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.Timeout)
|
result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.ModuleTimeout())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (p *MySQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
|
|||||||
|
|
||||||
// doMySQLAuth 执行MySQL认证
|
// doMySQLAuth 执行MySQL认证
|
||||||
func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.Timeout)
|
connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &AuthResult{
|
return &AuthResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -98,7 +98,7 @@ func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.SetConnMaxLifetime(config.Timeout)
|
db.SetConnMaxLifetime(config.ModuleTimeout())
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
db.SetMaxIdleConns(0)
|
db.SetMaxIdleConns(0)
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ func (p *MySQLPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MySQLPlugin) readMySQLBanner(conn net.Conn, config *common.Config) string {
|
func (p *MySQLPlugin) readMySQLBanner(conn net.Conn, config *common.Config) string {
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(config.Timeout))
|
_ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout()))
|
||||||
|
|
||||||
header := make([]byte, 5)
|
header := make([]byte, 5)
|
||||||
if _, err := io.ReadFull(conn, header); err != nil {
|
if _, err := io.ReadFull(conn, header); err != nil {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cr
|
|||||||
config := session.Config
|
config := session.Config
|
||||||
baseURL := "http://" + info.Target()
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.ModuleTimeout()}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -148,7 +148,7 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
|
|||||||
config := session.Config
|
config := session.Config
|
||||||
baseURL := "http://" + info.Target()
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.ModuleTimeout()}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -193,7 +193,7 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
|||||||
target := info.Target()
|
target := info.Target()
|
||||||
baseURL := "http://" + info.Target()
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.ModuleTimeout()}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -164,14 +164,14 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st
|
|||||||
|
|
||||||
target := fmt.Sprintf("%s:137", host)
|
target := fmt.Sprintf("%s:137", host)
|
||||||
|
|
||||||
conn, err := net.DialTimeout("udp", target, config.Timeout)
|
conn, err := net.DialTimeout("udp", target, config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_name_connect_failed"), err)
|
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_name_connect_failed"), err)
|
||||||
}
|
}
|
||||||
state.IncrementUDPPacketCount()
|
state.IncrementUDPPacketCount()
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout()))
|
||||||
|
|
||||||
_, err = conn.Write(queryPacket)
|
_, err = conn.Write(queryPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -191,13 +191,13 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st
|
|||||||
func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, session *common.ScanSession) (*NetBIOSInfo, error) {
|
func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, session *common.ScanSession) (*NetBIOSInfo, error) {
|
||||||
target := fmt.Sprintf("%s:139", host)
|
target := fmt.Sprintf("%s:139", host)
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_session_connect_failed"), err)
|
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_session_connect_failed"), err)
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
// 发送SMB协商数据包
|
// 发送SMB协商数据包
|
||||||
smbNegotiate1 := []byte{
|
smbNegotiate1 := []byte{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func NewNFSPlugin() *NFSPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,8 +72,8 @@ func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo,
|
|||||||
serviceNames := []string{"ORCL", "XE", "XEPDB1", target}
|
serviceNames := []string{"ORCL", "XE", "XEPDB1", target}
|
||||||
|
|
||||||
for _, serviceName := range serviceNames {
|
for _, serviceName := range serviceNames {
|
||||||
connectCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
connectCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
|
||||||
err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.Timeout)
|
err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
errorType := classifyOracleErrorType(err)
|
errorType := classifyOracleErrorType(err)
|
||||||
@@ -168,7 +168,7 @@ func (p *OraclePlugin) testUnauthorizedAccess(ctx context.Context, info *common.
|
|||||||
func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{
|
return &ScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func NewPOP3Plugin() *POP3Plugin {
|
|||||||
|
|
||||||
func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (p *PostgreSQLPlugin) createAuthFunc(info *common.HostInfo, config *common.
|
|||||||
|
|
||||||
// doPostgreSQLAuth 执行PostgreSQL认证
|
// doPostgreSQLAuth 执行PostgreSQL认证
|
||||||
func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.Timeout.Seconds()))
|
connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.ModuleTimeout().Seconds()))
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connStr)
|
db, err := sql.Open("postgres", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,11 +85,11 @@ func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.Ho
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.SetConnMaxLifetime(config.Timeout)
|
db.SetConnMaxLifetime(config.ModuleTimeout())
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
db.SetMaxIdleConns(0)
|
db.SetMaxIdleConns(0)
|
||||||
|
|
||||||
pingCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = db.PingContext(pingCtx)
|
err = db.PingContext(pingCtx)
|
||||||
@@ -169,7 +169,7 @@ func postgreSQLConnString(username, password string, info *common.HostInfo, time
|
|||||||
|
|
||||||
// testUnauthorizedAccess 测试PostgreSQL未授权访问
|
// testUnauthorizedAccess 测试PostgreSQL未授权访问
|
||||||
func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
||||||
connStr := postgreSQLConnString("postgres", "", info, int64(config.Timeout.Seconds()))
|
connStr := postgreSQLConnString("postgres", "", info, int64(config.ModuleTimeout().Seconds()))
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connStr)
|
db, err := sql.Open("postgres", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -177,11 +177,11 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com
|
|||||||
}
|
}
|
||||||
defer func() { _ = db.Close() }()
|
defer func() { _ = db.Close() }()
|
||||||
|
|
||||||
db.SetConnMaxLifetime(config.Timeout)
|
db.SetConnMaxLifetime(config.ModuleTimeout())
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
db.SetMaxIdleConns(0)
|
db.SetMaxIdleConns(0)
|
||||||
|
|
||||||
pingCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = db.PingContext(pingCtx)
|
err = db.PingContext(pingCtx)
|
||||||
@@ -192,7 +192,7 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com
|
|||||||
|
|
||||||
state.IncrementTCPSuccessPacketCount()
|
state.IncrementTCPSuccessPacketCount()
|
||||||
|
|
||||||
queryCtx, queryCancel := context.WithTimeout(ctx, config.Timeout)
|
queryCtx, queryCancel := context.WithTimeout(ctx, config.ModuleTimeout())
|
||||||
defer queryCancel()
|
defer queryCancel()
|
||||||
|
|
||||||
var version string
|
var version string
|
||||||
@@ -222,7 +222,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos
|
|||||||
state := session.State
|
state := session.State
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.Timeout.Seconds()))
|
connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.ModuleTimeout().Seconds()))
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connStr)
|
db, err := sql.Open("postgres", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -234,7 +234,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos
|
|||||||
}
|
}
|
||||||
defer func() { _ = db.Close() }()
|
defer func() { _ = db.Close() }()
|
||||||
|
|
||||||
pingCtx, cancel := context.WithTimeout(ctx, config.Timeout)
|
pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = db.PingContext(pingCtx)
|
err = db.PingContext(pingCtx)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func (p *RabbitMQPlugin) doRabbitMQAuth(ctx context.Context, info *common.HostIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
|
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.ModuleTimeout()}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -165,7 +165,7 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
|
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.ModuleTimeout()}
|
||||||
|
|
||||||
// 测试无认证访问
|
// 测试无认证访问
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
|
||||||
@@ -213,13 +213,13 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo
|
|||||||
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
// 发送AMQP协议头
|
// 发送AMQP协议头
|
||||||
amqpHeader := []byte{0x41, 0x4d, 0x51, 0x50, 0x00, 0x00, 0x09, 0x01}
|
amqpHeader := []byte{0x41, 0x4d, 0x51, 0x50, 0x00, 0x00, 0x09, 0x01}
|
||||||
@@ -280,7 +280,7 @@ func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *comm
|
|||||||
target := info.Target()
|
target := info.Target()
|
||||||
baseURL := "http://" + info.Target()
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.ModuleTimeout()}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
|
|
||||||
// rdpCrack 使用NLA认证验证凭据,不建立完整会话,不会挤掉已登录用户
|
// rdpCrack 使用NLA认证验证凭据,不建立完整会话,不会挤掉已登录用户
|
||||||
func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common.Config, state *common.State) (bool, error) {
|
func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common.Config, state *common.State) (bool, error) {
|
||||||
timeout := int64(config.Timeout.Seconds())
|
timeout := int64(config.ModuleTimeout().Seconds())
|
||||||
|
|
||||||
// 使用NLA仅验证模式:只验证凭据,不建立RDP会话
|
// 使用NLA仅验证模式:只验证凭据,不建立RDP会话
|
||||||
// 这样不会挤掉目标机器上已登录的用户
|
// 这样不会挤掉目标机器上已登录的用户
|
||||||
@@ -180,7 +180,7 @@ func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common
|
|||||||
|
|
||||||
// probeOSInfo 通过NLA协商获取系统信息(无需密码)
|
// probeOSInfo 通过NLA协商获取系统信息(无需密码)
|
||||||
func (p *RDPPlugin) probeOSInfo(host string, config *common.Config, state *common.State) map[string]any {
|
func (p *RDPPlugin) probeOSInfo(host string, config *common.Config, state *common.State) map[string]any {
|
||||||
timeout := int64(config.Timeout.Seconds())
|
timeout := int64(config.ModuleTimeout().Seconds())
|
||||||
client := login.NewClient(host, glog.NONE)
|
client := login.NewClient(host, glog.NONE)
|
||||||
|
|
||||||
// 使用 PROTOCOL_HYBRID 协议探测系统信息
|
// 使用 PROTOCOL_HYBRID 协议探测系统信息
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func (p *RedisPlugin) createAuthFunc(info *common.HostInfo, session *common.Scan
|
|||||||
// doRedisAuth 执行Redis认证
|
// doRedisAuth 执行Redis认证
|
||||||
func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
|
|
||||||
// 建立TCP连接
|
// 建立TCP连接
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
||||||
@@ -223,7 +223,7 @@ func (p *RedisPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
|
|||||||
func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.HostInfo, password string, session *common.ScanSession) {
|
func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.HostInfo, password string, session *common.ScanSession) {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
session.LogError(i18n.Tr("redis_reconnect_failed", err))
|
session.LogError(i18n.Tr("redis_reconnect_failed", err))
|
||||||
return
|
return
|
||||||
@@ -232,11 +232,11 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host
|
|||||||
|
|
||||||
// 如果有密码,先认证
|
// 如果有密码,先认证
|
||||||
if password != "" {
|
if password != "" {
|
||||||
_ = conn.SetWriteDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetWriteDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
if _, writeErr := conn.Write(buildRedisAuthCommand(password)); writeErr != nil {
|
if _, writeErr := conn.Write(buildRedisAuthCommand(password)); writeErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
response := make([]byte, 512)
|
response := make([]byte, 512)
|
||||||
if _, readErr := conn.Read(response); readErr != nil {
|
if _, readErr := conn.Read(response); readErr != nil {
|
||||||
return
|
return
|
||||||
@@ -249,7 +249,7 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host
|
|||||||
// identifyService 服务识别
|
// identifyService 服务识别
|
||||||
func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -325,7 +325,7 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Time{})
|
_ = conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
|
||||||
dbfilename, dir, err := p.getConfig(conn)
|
dbfilename, dir, err := p.getConfig(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -397,14 +397,24 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
func (p *RedisPlugin) readReply(conn net.Conn) (string, error) {
|
func (p *RedisPlugin) readReply(conn net.Conn) (string, error) {
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(time.Second))
|
_ = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||||
bytes, err := io.ReadAll(io.LimitReader(conn, maxRedisReplyBytes))
|
bytes, err := io.ReadAll(io.LimitReader(conn, maxRedisReplyBytes))
|
||||||
if len(bytes) > 0 {
|
if len(bytes) > 0 && isTimeoutError(err) {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return string(bytes), err
|
return string(bytes), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isTimeoutError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// sendCmd 发送Redis命令并检查OK响应
|
// sendCmd 发送Redis命令并检查OK响应
|
||||||
// 返回响应文本、是否成功、错误
|
// 返回响应文本、是否成功、错误
|
||||||
func (p *RedisPlugin) sendCmd(conn net.Conn, cmd []byte) (text string, ok bool, err error) {
|
func (p *RedisPlugin) sendCmd(conn net.Conn, cmd []byte) (text string, ok bool, err error) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func NewRMIPlugin() *RMIPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *RMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *RMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
|
|||||||
// connectToRsync 连接到Rsync服务
|
// connectToRsync 连接到Rsync服务
|
||||||
func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn {
|
func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
|
|
||||||
connChan := make(chan net.Conn, 1)
|
connChan := make(chan net.Conn, 1)
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo,
|
|||||||
|
|
||||||
// getModules 获取Rsync模块列表
|
// getModules 获取Rsync模块列表
|
||||||
func (p *RsyncPlugin) getModules(conn net.Conn, config *common.Config) []string {
|
func (p *RsyncPlugin) getModules(conn net.Conn, config *common.Config) []string {
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
|
|
||||||
// 读取服务器版本
|
// 读取服务器版本
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(timeout))
|
_ = conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
@@ -342,7 +342,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
|||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
|
|
||||||
_ = conn.SetWriteDeadline(time.Now().Add(timeout))
|
_ = conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||||
if _, err := conn.Write([]byte("\n")); err != nil {
|
if _, err := conn.Write([]byte("\n")); err != nil {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. 协议探测和信息收集
|
// 1. 协议探测和信息收集
|
||||||
smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.Timeout, session)
|
smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.ModuleTimeout(), session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{
|
return &ScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -53,7 +53,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
|
|
||||||
// 2. 漏洞检测 (仅SMBv2+且端口445)
|
// 2. 漏洞检测 (仅SMBv2+且端口445)
|
||||||
if smbTarget.Protocol == SMBProtocol2 && info.Port == 445 {
|
if smbTarget.Protocol == SMBProtocol2 && info.Port == 445 {
|
||||||
if checkSMBGhost(ctx, info.Host, config.Timeout, session) {
|
if checkSMBGhost(ctx, info.Host, config.ModuleTimeout(), session) {
|
||||||
smbTarget.Vulnerable = &SMBVuln{CVE20200796: true}
|
smbTarget.Vulnerable = &SMBVuln{CVE20200796: true}
|
||||||
session.LogVuln(i18n.Tr("smbghost_vuln", target))
|
session.LogVuln(i18n.Tr("smbghost_vuln", target))
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ func (p *SmbPlugin) getAuthenticator(protocol SMBProtocol) SMBAuthenticator {
|
|||||||
func (p *SmbPlugin) createAuthFunc(info *common.HostInfo, auth SMBAuthenticator, session *common.ScanSession) AuthFunc {
|
func (p *SmbPlugin) createAuthFunc(info *common.HostInfo, auth SMBAuthenticator, session *common.ScanSession) AuthFunc {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
return func(ctx context.Context, cred Credential) *AuthResult {
|
return func(ctx context.Context, cred Credential) *AuthResult {
|
||||||
result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session)
|
result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cred := range unauthorizedCreds {
|
for _, cred := range unauthorizedCreds {
|
||||||
shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session)
|
shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session)
|
||||||
if err == nil && len(shareInfo) > 0 {
|
if err == nil && len(shareInfo) > 0 {
|
||||||
var output strings.Builder
|
var output strings.Builder
|
||||||
displayUser := cred.Username
|
displayUser := cred.Username
|
||||||
|
|||||||
+10
-10
@@ -78,7 +78,7 @@ func (p *SMTPPlugin) createAuthFunc(info *common.HostInfo, session *common.ScanS
|
|||||||
// doSMTPAuth 执行SMTP认证
|
// doSMTPAuth 执行SMTP认证
|
||||||
func (p *SMTPPlugin) doSMTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
func (p *SMTPPlugin) doSMTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
|
|
||||||
resultChan := make(chan *AuthResult, 1)
|
resultChan := make(chan *AuthResult, 1)
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ func (p *SMTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostI
|
|||||||
resultChan := make(chan *ScanResult, 1)
|
resultChan := make(chan *ScanResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- nil
|
resultChan <- nil
|
||||||
return
|
return
|
||||||
@@ -288,7 +288,7 @@ func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo, s
|
|||||||
resultChan := make(chan *ScanResult, 1)
|
resultChan := make(chan *ScanResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- nil
|
resultChan <- nil
|
||||||
return
|
return
|
||||||
@@ -340,14 +340,14 @@ func (p *SMTPPlugin) testVRFYCommand(ctx context.Context, info *common.HostInfo,
|
|||||||
resultChan := make(chan *ScanResult, 1)
|
resultChan := make(chan *ScanResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- nil
|
resultChan <- nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil {
|
if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil {
|
||||||
resultChan <- nil
|
resultChan <- nil
|
||||||
@@ -410,14 +410,14 @@ func (p *SMTPPlugin) testEXPNCommand(ctx context.Context, info *common.HostInfo,
|
|||||||
resultChan := make(chan *ScanResult, 1)
|
resultChan := make(chan *ScanResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- nil
|
resultChan <- nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil {
|
if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil {
|
||||||
resultChan <- nil
|
resultChan <- nil
|
||||||
@@ -480,14 +480,14 @@ func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo, s
|
|||||||
resultChan := make(chan string, 1)
|
resultChan := make(chan string, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- ""
|
resultChan <- ""
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
n, err := conn.Read(buffer)
|
n, err := conn.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -524,7 +524,7 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo,
|
|||||||
if serverInfo != "" {
|
if serverInfo != "" {
|
||||||
banner = i18n.Tr("smtp_mail_service_info", serverInfo)
|
banner = i18n.Tr("smtp_mail_service_info", serverInfo)
|
||||||
} else {
|
} else {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{
|
return &ScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|||||||
@@ -23,10 +23,7 @@ func NewSNMPPlugin() *SNMPPlugin {
|
|||||||
|
|
||||||
func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
timeout := config.Timeout
|
timeout := config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
|
||||||
timeout = 3 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
@@ -35,6 +32,8 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
return &ScanResult{Success: false, Service: "snmp"}
|
return &ScanResult{Success: false, Service: "snmp"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.LogVuln(fmt.Sprintf("SNMP %s %s", target, result.Banner))
|
||||||
|
|
||||||
if config.DisableBrute {
|
if config.DisableBrute {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-11
@@ -64,8 +64,12 @@ func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 使用公共框架进行并发凭据测试
|
// 使用公共框架进行并发凭据测试
|
||||||
|
// SSH 并发限制为 3:OpenSSH MaxStartups 默认 10:30:60,高并发会被随机丢弃
|
||||||
authFn := p.createAuthFunc(info, session)
|
authFn := p.createAuthFunc(info, session)
|
||||||
testConfig := DefaultConcurrentTestConfigWithTarget(config, info)
|
testConfig := DefaultConcurrentTestConfigWithTarget(config, info)
|
||||||
|
if testConfig.Concurrency > 3 {
|
||||||
|
testConfig.Concurrency = 3
|
||||||
|
}
|
||||||
|
|
||||||
result := TestCredentialsConcurrently(ctx, credentials, authFn, "ssh", testConfig)
|
result := TestCredentialsConcurrently(ctx, credentials, authFn, "ssh", testConfig)
|
||||||
|
|
||||||
@@ -90,9 +94,10 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
|
|||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
// 创建SSH配置
|
// 创建SSH配置
|
||||||
|
moduleTimeout := config.ModuleTimeout()
|
||||||
sshConfig := &ssh.ClientConfig{
|
sshConfig := &ssh.ClientConfig{
|
||||||
User: cred.Username,
|
User: cred.Username,
|
||||||
Timeout: config.Timeout,
|
Timeout: moduleTimeout,
|
||||||
//nolint:gosec // G106: 扫描工具需要忽略主机密钥验证以连接未知主机
|
//nolint:gosec // G106: 扫描工具需要忽略主机密钥验证以连接未知主机
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
}
|
}
|
||||||
@@ -133,6 +138,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 设置 TCP 级别 deadline 兜底整个握手过程
|
||||||
|
_ = conn.SetDeadline(time.Now().Add(moduleTimeout))
|
||||||
|
|
||||||
// 在TCP连接上创建SSH客户端
|
// 在TCP连接上创建SSH客户端
|
||||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, target, sshConfig)
|
sshConn, chans, reqs, err := ssh.NewClientConn(conn, target, sshConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -151,6 +159,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 握手成功,清除 deadline
|
||||||
|
_ = conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
// 创建SSH客户端
|
// 创建SSH客户端
|
||||||
client := ssh.NewClient(sshConn, chans, reqs)
|
client := ssh.NewClient(sshConn, chans, reqs)
|
||||||
|
|
||||||
@@ -183,16 +194,39 @@ func classifySSHErrorType(err error) ErrorType {
|
|||||||
"no supported methods remain",
|
"no supported methods remain",
|
||||||
)
|
)
|
||||||
|
|
||||||
// SSH 特有的网络/临时错误(需要重试)
|
// SSH 限流错误 — 服务端主动拒绝(MaxStartups 等),退避后重试即可
|
||||||
sshNetworkErrors := append(CommonNetworkErrors,
|
sshThrottleErrors := []string{
|
||||||
"handshake failed", // 握手失败,可能是服务端限流
|
"handshake failed",
|
||||||
"ssh: disconnect", // SSH 主动断开
|
"ssh: disconnect",
|
||||||
"connection closed", // 连接被关闭
|
"connection closed",
|
||||||
"max startups", // SSH MaxStartups 限制
|
"max startups",
|
||||||
"too many authentication", // 认证次数过多
|
"too many authentication",
|
||||||
)
|
}
|
||||||
|
|
||||||
return ClassifyError(err, sshAuthErrors, sshNetworkErrors)
|
return classifySSHError(err, sshAuthErrors, sshThrottleErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func classifySSHError(err error, authKeywords, throttleKeywords []string) ErrorType {
|
||||||
|
if err == nil {
|
||||||
|
return ErrorTypeUnknown
|
||||||
|
}
|
||||||
|
errStr := err.Error()
|
||||||
|
for _, kw := range authKeywords {
|
||||||
|
if containsIgnoreCase(errStr, kw) {
|
||||||
|
return ErrorTypeAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kw := range throttleKeywords {
|
||||||
|
if containsIgnoreCase(errStr, kw) {
|
||||||
|
return ErrorTypeThrottle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kw := range CommonNetworkErrors {
|
||||||
|
if containsIgnoreCase(errStr, kw) {
|
||||||
|
return ErrorTypeNetwork
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrorTypeUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanWithKey 使用SSH私钥扫描
|
// scanWithKey 使用SSH私钥扫描
|
||||||
@@ -272,7 +306,7 @@ func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo,
|
|||||||
|
|
||||||
// readSSHBanner 读取SSH服务器Banner
|
// readSSHBanner 读取SSH服务器Banner
|
||||||
func (p *SSHPlugin) readSSHBanner(conn net.Conn, config *common.Config) string {
|
func (p *SSHPlugin) readSSHBanner(conn net.Conn, config *common.Config) string {
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(config.Timeout))
|
_ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout()))
|
||||||
|
|
||||||
banner := make([]byte, 256)
|
banner := make([]byte, 256)
|
||||||
n, err := conn.Read(banner)
|
n, err := conn.Read(banner)
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo,
|
|||||||
resultChan := make(chan *AuthResult, 1)
|
resultChan := make(chan *AuthResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- &AuthResult{
|
resultChan <- &AuthResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -131,7 +131,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
if p.performTelnetAuth(conn, cred.Username, cred.Password) {
|
if p.performTelnetAuth(conn, cred.Username, cred.Password) {
|
||||||
resultChan <- &AuthResult{
|
resultChan <- &AuthResult{
|
||||||
@@ -215,14 +215,14 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn
|
|||||||
resultChan := make(chan *ScanResult, 1)
|
resultChan := make(chan *ScanResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- nil
|
resultChan <- nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
attempts := 0
|
attempts := 0
|
||||||
@@ -510,7 +510,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
|
|||||||
resultChan := make(chan *ScanResult, 1)
|
resultChan := make(chan *ScanResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- &ScanResult{
|
resultChan <- &ScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -521,7 +521,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
|
|||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
buffer := make([]byte, 2048)
|
buffer := make([]byte, 2048)
|
||||||
n, err := conn.Read(buffer)
|
n, err := conn.Read(buffer)
|
||||||
@@ -595,14 +595,14 @@ func (p *TelnetPlugin) verifyCommandExecution(ctx context.Context, info *common.
|
|||||||
resultChan := make(chan rceResult, 1)
|
resultChan := make(chan rceResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- rceResult{}
|
resultChan <- rceResult{}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout + telnetRCEExtraTimeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout() + telnetRCEExtraTimeout))
|
||||||
|
|
||||||
// 需要认证时先登录
|
// 需要认证时先登录
|
||||||
if username != "" || password != "" {
|
if username != "" || password != "" {
|
||||||
@@ -808,7 +808,7 @@ func (p *TelnetPlugin) checkCVE202624061Concurrent(ctx context.Context, info *co
|
|||||||
// 利用 NEW-ENVIRON (option 39) 子协商注入恶意环境变量,实现认证绕过
|
// 利用 NEW-ENVIRON (option 39) 子协商注入恶意环境变量,实现认证绕过
|
||||||
// 返回 (是否漏洞, 触发用户名, 证据)
|
// 返回 (是否漏洞, 触发用户名, 证据)
|
||||||
func (p *TelnetPlugin) checkCVE202624061(ctx context.Context, info *common.HostInfo, session *common.ScanSession, user string) (bool, string, string) {
|
func (p *TelnetPlugin) checkCVE202624061(ctx context.Context, info *common.HostInfo, session *common.ScanSession, user string) (bool, string, string) {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", ""
|
return false, "", ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func NewTFTPPlugin() *TFTPPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C
|
|||||||
resultChan := make(chan *AuthResult, 1)
|
resultChan := make(chan *AuthResult, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- &AuthResult{
|
resultChan <- &AuthResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -84,7 +84,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
|
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
|
||||||
|
|
||||||
vncConfig := &vnc.ClientConfig{
|
vncConfig := &vnc.ClientConfig{
|
||||||
Auth: []vnc.ClientAuth{
|
Auth: []vnc.ClientAuth{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func NewZooKeeperPlugin() *ZooKeeperPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ZooKeeperPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *ZooKeeperPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
timeout := session.Config.Timeout
|
timeout := session.Config.ModuleTimeout()
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,14 +303,18 @@ func matchRegex(matcher struct {
|
|||||||
// 从 sync.Map 缓存获取或编译正则
|
// 从 sync.Map 缓存获取或编译正则
|
||||||
var re *regexp.Regexp
|
var re *regexp.Regexp
|
||||||
if cached, ok := enhancedDB.regexCache.Load(cacheKey); ok {
|
if cached, ok := enhancedDB.regexCache.Load(cacheKey); ok {
|
||||||
re = cached.(*regexp.Regexp)
|
if r, ok := cached.(*regexp.Regexp); ok {
|
||||||
|
re = r
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
compiled, err := regexp.Compile(cacheKey)
|
compiled, err := regexp.Compile(cacheKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
actual, _ := enhancedDB.regexCache.LoadOrStore(cacheKey, compiled)
|
actual, _ := enhancedDB.regexCache.LoadOrStore(cacheKey, compiled)
|
||||||
re = actual.(*regexp.Regexp)
|
if r, ok := actual.(*regexp.Regexp); ok {
|
||||||
|
re = r
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保 re 不为 nil(防止并发场景下的 nil panic)
|
// 确保 re 不为 nil(防止并发场景下的 nil panic)
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ func registerStringImplementations() []*functions.Overload {
|
|||||||
pattern := string(v1)
|
pattern := string(v1)
|
||||||
var re *regexp.Regexp
|
var re *regexp.Regexp
|
||||||
if cached, found := regexCache.Load(pattern); found {
|
if cached, found := regexCache.Load(pattern); found {
|
||||||
re = cached.(*regexp.Regexp)
|
re, _ = cached.(*regexp.Regexp)
|
||||||
} else {
|
} else {
|
||||||
compiled, err := regexp.Compile(pattern)
|
compiled, err := regexp.Compile(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.NewErr("%v", err)
|
return types.NewErr("%v", err)
|
||||||
}
|
}
|
||||||
actual, _ := regexCache.LoadOrStore(pattern, compiled)
|
actual, _ := regexCache.LoadOrStore(pattern, compiled)
|
||||||
re = actual.(*regexp.Regexp)
|
re, _ = actual.(*regexp.Regexp)
|
||||||
}
|
}
|
||||||
return types.Bool(re.Match(v2))
|
return types.Bool(re.Match(v2))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ func doSearch(re string, body string) map[string]string {
|
|||||||
// 编译正则表达式(带缓存)
|
// 编译正则表达式(带缓存)
|
||||||
var r *regexp.Regexp
|
var r *regexp.Regexp
|
||||||
if cached, ok := regexCache.Load(re); ok {
|
if cached, ok := regexCache.Load(re); ok {
|
||||||
r = cached.(*regexp.Regexp)
|
r, _ = cached.(*regexp.Regexp)
|
||||||
} else {
|
} else {
|
||||||
compiled, err := regexp.Compile(re)
|
compiled, err := regexp.Compile(re)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -309,7 +309,7 @@ func doSearch(re string, body string) map[string]string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
actual, _ := regexCache.LoadOrStore(re, compiled)
|
actual, _ := regexCache.LoadOrStore(re, compiled)
|
||||||
r = actual.(*regexp.Regexp)
|
r, _ = actual.(*regexp.Regexp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行正则匹配
|
// 执行正则匹配
|
||||||
|
|||||||
Reference in New Issue
Block a user