diff --git a/Plugins/ActiveMQ.go b/Plugins/ActiveMQ.go index 3e3a8b2..3869b59 100644 --- a/Plugins/ActiveMQ.go +++ b/Plugins/ActiveMQ.go @@ -1,189 +1,318 @@ package Plugins import ( + "context" "fmt" "github.com/shadow1ng/fscan/Common" "net" "strings" + "sync" "time" ) +// ActiveMQCredential 表示一个ActiveMQ凭据 +type ActiveMQCredential struct { + Username string + Password string +} + +// ActiveMQScanResult 表示扫描结果 +type ActiveMQScanResult struct { + Success bool + Error error + Credential ActiveMQCredential +} + func ActiveMQScan(info *Common.HostInfo) (tmperr error) { if Common.DisableBrute { return } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 先尝试默认账户 Common.LogDebug("尝试默认账户 admin:admin") - // 首先测试默认账户 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试默认账户", retryCount+1)) - } + defaultCredential := ActiveMQCredential{Username: "admin", Password: "admin"} + defaultResult := tryActiveCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries) - flag, err := ActiveMQConn(info, "admin", "admin") - if flag { - successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: admin 密码: admin", target) - Common.LogSuccess(successMsg) - - // 保存结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "activemq", - "username": "admin", - "password": "admin", - "type": "weak-password", - }, - } - Common.SaveResult(result) - return nil - } - if err != nil { - errMsg := fmt.Sprintf("ActiveMQ服务 %s 默认账户尝试失败: %v", target, err) - Common.LogError(errMsg) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - return err - } - continue - } - } - break + if defaultResult.Success { + saveActiveMQSuccess(info, target, defaultResult.Credential) + return nil } - totalUsers := len(Common.Userdict["activemq"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) + // 生成所有凭据组合 + credentials := generateActiveMQCredentials(Common.Userdict["activemq"], Common.Passwords) + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["activemq"]), len(Common.Passwords), len(credentials))) - tried := 0 - total := totalUsers * totalPass - - // 遍历所有用户名密码组合 - for _, user := range Common.Userdict["activemq"] { - for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) - - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } - - done := make(chan struct { - success bool - err error - }, 1) - - go func(user, pass string) { - flag, err := ActiveMQConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{flag, err}: - default: - } - }(user, pass) - - var err error - select { - case result := <-done: - err = result.err - if result.success { - successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "activemq", - "username": user, - "password": pass, - "type": "weak-password", - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errMsg := fmt.Sprintf("ActiveMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err) - Common.LogError(errMsg) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue - } - } - break - } - } + // 使用工作池并发扫描 + result := concurrentActiveMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveActiveMQSuccess(info, target, result.Credential) + return nil } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("ActiveMQ扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认凭据 + return nil + } } -// ActiveMQConn 统一的连接测试函数 -func ActiveMQConn(info *Common.HostInfo, user string, pass string) (bool, error) { - timeout := time.Duration(Common.Timeout) * time.Second - addr := fmt.Sprintf("%s:%s", info.Host, info.Ports) +// generateActiveMQCredentials 生成ActiveMQ的用户名密码组合 +func generateActiveMQCredentials(users, passwords []string) []ActiveMQCredential { + var credentials []ActiveMQCredential + for _, user := range users { + for _, pass := range passwords { + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, ActiveMQCredential{ + Username: user, + Password: actualPass, + }) + } + } + return credentials +} - conn, err := net.DialTimeout("tcp", addr, timeout) +// concurrentActiveMQScan 并发扫描ActiveMQ服务 +func concurrentActiveMQScan(ctx context.Context, info *Common.HostInfo, credentials []ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *ActiveMQScanResult, 1) + workChan := make(chan ActiveMQCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := tryActiveCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("ActiveMQ并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryActiveCredential 尝试单个ActiveMQ凭据 +func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &ActiveMQScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建单个连接超时的上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := ActiveMQConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &ActiveMQScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } + } + } + } + + return &ActiveMQScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } +} + +// ActiveMQConn 尝试ActiveMQ连接 +func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { + addr := fmt.Sprintf("%s:%v", info.Host, info.Ports) + + // 使用上下文创建带超时的连接 + dialer := &net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second} + conn, err := dialer.DialContext(ctx, "tcp", addr) if err != nil { return false, err } defer conn.Close() - // STOMP协议的CONNECT命令 - stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass) + // 创建结果通道 + resultChan := make(chan struct { + success bool + err error + }, 1) - // 发送认证请求 - conn.SetWriteDeadline(time.Now().Add(timeout)) - if _, err := conn.Write([]byte(stompConnect)); err != nil { - return false, err + // 在协程中处理认证 + go func() { + // STOMP协议的CONNECT命令 + stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass) + + // 发送认证请求 + conn.SetWriteDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)) + if _, err := conn.Write([]byte(stompConnect)); err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{false, err}: + } + return + } + + // 读取响应 + conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)) + respBuf := make([]byte, 1024) + n, err := conn.Read(respBuf) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{false, err}: + } + return + } + + // 检查认证结果 + response := string(respBuf[:n]) + + var success bool + var resultErr error + + if strings.Contains(response, "CONNECTED") { + success = true + resultErr = nil + } else if strings.Contains(response, "Authentication failed") || strings.Contains(response, "ERROR") { + success = false + resultErr = fmt.Errorf("认证失败") + } else { + success = false + resultErr = fmt.Errorf("未知响应: %s", response) + } + + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{success, resultErr}: + } + }() + + // 等待认证结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.err + case <-ctx.Done(): + return false, ctx.Err() } - - // 读取响应 - conn.SetReadDeadline(time.Now().Add(timeout)) - respBuf := make([]byte, 1024) - n, err := conn.Read(respBuf) - if err != nil { - return false, err - } - - // 检查认证结果 - response := string(respBuf[:n]) - - if strings.Contains(response, "CONNECTED") { - return true, nil - } - - if strings.Contains(response, "Authentication failed") || strings.Contains(response, "ERROR") { - return false, fmt.Errorf("认证失败") - } - - return false, fmt.Errorf("未知响应: %s", response) +} + +// saveActiveMQSuccess 记录并保存ActiveMQ成功结果 +func saveActiveMQSuccess(info *Common.HostInfo, target string, credential ActiveMQCredential) { + successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v", + target, credential.Username, credential.Password) + Common.LogSuccess(successMsg) + + // 保存结果 + result := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "activemq", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + }, + } + Common.SaveResult(result) } diff --git a/Plugins/Cassandra.go b/Plugins/Cassandra.go index 7a8de35..a1e9298 100644 --- a/Plugins/Cassandra.go +++ b/Plugins/Cassandra.go @@ -1,154 +1,225 @@ package Plugins import ( + "context" "fmt" "github.com/gocql/gocql" "github.com/shadow1ng/fscan/Common" "strconv" "strings" + "sync" "time" ) +// CassandraCredential 表示一个Cassandra凭据 +type CassandraCredential struct { + Username string + Password string +} + +// CassandraScanResult 表示扫描结果 +type CassandraScanResult struct { + Success bool + IsAnonymous bool + Error error + Credential CassandraCredential +} + func CassandraScan(info *Common.HostInfo) (tmperr error) { if Common.DisableBrute { return } target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - maxRetries := Common.MaxRetries - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 先尝试无认证访问 Common.LogDebug("尝试无认证访问...") - // 首先测试无认证访问 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1)) - } + anonymousCredential := CassandraCredential{Username: "", Password: ""} + anonymousResult := tryCassandraCredential(ctx, info, anonymousCredential, Common.Timeout, Common.MaxRetries) - flag, err := CassandraConn(info, "", "") - if flag && err == nil { - successMsg := fmt.Sprintf("Cassandra服务 %s 无认证访问成功", target) - Common.LogSuccess(successMsg) - - // 保存无认证访问结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "cassandra", - "auth_type": "anonymous", - "type": "unauthorized-access", - "description": "数据库允许无认证访问", - }, - } - Common.SaveResult(result) - return err - } - if err != nil && Common.CheckErrs(err) != nil { - if retryCount == maxRetries-1 { - return err - } - continue - } - break + if anonymousResult.Success { + saveCassandraSuccess(info, target, anonymousResult.Credential, true) + return nil } - totalUsers := len(Common.Userdict["cassandra"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) + // 生成所有凭据组合 + credentials := generateCassandraCredentials(Common.Userdict["cassandra"], Common.Passwords) + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["cassandra"]), len(Common.Passwords), len(credentials))) - tried := 0 - total := totalUsers * totalPass - - // 遍历所有用户名密码组合 - for _, user := range Common.Userdict["cassandra"] { - for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) - - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } - - done := make(chan struct { - success bool - err error - }, 1) - - go func(user, pass string) { - success, err := CassandraConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) - - var err error - select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("Cassandra服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(successMsg) - - // 保存爆破成功结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "cassandra", - "username": user, - "password": pass, - "type": "weak-password", - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errlog := fmt.Sprintf("Cassandra服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue - } - } - break - } - } + // 使用工作池并发扫描 + result := concurrentCassandraScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveCassandraSuccess(info, target, result.Credential, false) + return nil } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("Cassandra扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 + return nil + } } -// CassandraConn 清理后的连接测试函数 -func CassandraConn(info *Common.HostInfo, user string, pass string) (bool, error) { +// generateCassandraCredentials 生成Cassandra的用户名密码组合 +func generateCassandraCredentials(users, passwords []string) []CassandraCredential { + var credentials []CassandraCredential + for _, user := range users { + for _, pass := range passwords { + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, CassandraCredential{ + Username: user, + Password: actualPass, + }) + } + } + return credentials +} + +// concurrentCassandraScan 并发扫描Cassandra服务 +func concurrentCassandraScan(ctx context.Context, info *Common.HostInfo, credentials []CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *CassandraScanResult, 1) + workChan := make(chan CassandraCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := tryCassandraCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("Cassandra并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryCassandraCredential 尝试单个Cassandra凭据 +func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credential CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &CassandraScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建单个连接超时的上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := CassandraConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &CassandraScanResult{ + Success: true, + IsAnonymous: credential.Username == "" && credential.Password == "", + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } + } + } + } + + return &CassandraScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } +} + +// CassandraConn 尝试Cassandra连接,支持上下文超时 +func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { host, port := info.Host, info.Ports timeout := time.Duration(Common.Timeout) * time.Second cluster := gocql.NewCluster(host) cluster.Port, _ = strconv.Atoi(port) cluster.Timeout = timeout + cluster.ConnectTimeout = timeout cluster.ProtoVersion = 4 cluster.Consistency = gocql.One @@ -161,18 +232,111 @@ func CassandraConn(info *Common.HostInfo, user string, pass string) (bool, error cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3} - session, err := cluster.CreateSession() - if err != nil { - return false, err + // 创建会话通道 + sessionChan := make(chan struct { + session *gocql.Session + err error + }, 1) + + // 在后台创建会话,以便可以通过上下文取消 + go func() { + session, err := cluster.CreateSession() + select { + case <-ctx.Done(): + if session != nil { + session.Close() + } + case sessionChan <- struct { + session *gocql.Session + err error + }{session, err}: + } + }() + + // 等待会话创建或上下文取消 + var session *gocql.Session + var err error + select { + case result := <-sessionChan: + session, err = result.session, result.err + if err != nil { + return false, err + } + case <-ctx.Done(): + return false, ctx.Err() } + defer session.Close() - var version string - if err := session.Query("SELECT peer FROM system.peers").Scan(&version); err != nil { - if err := session.Query("SELECT now() FROM system.local").Scan(&version); err != nil { - return false, err + // 尝试执行查询,测试连接是否成功 + resultChan := make(chan struct { + success bool + err error + }, 1) + + go func() { + var version string + var err error + + // 尝试两种查询,确保至少一种成功 + err = session.Query("SELECT peer FROM system.peers").WithContext(ctx).Scan(&version) + if err != nil { + err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&version) + } + + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{err == nil, err}: + } + }() + + // 等待查询结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.err + case <-ctx.Done(): + return false, ctx.Err() + } +} + +// saveCassandraSuccess 记录并保存Cassandra成功结果 +func saveCassandraSuccess(info *Common.HostInfo, target string, credential CassandraCredential, isAnonymous bool) { + var successMsg string + var details map[string]interface{} + + if isAnonymous { + successMsg = fmt.Sprintf("Cassandra服务 %s 无认证访问成功", target) + details = map[string]interface{}{ + "port": info.Ports, + "service": "cassandra", + "auth_type": "anonymous", + "type": "unauthorized-access", + "description": "数据库允许无认证访问", + } + } else { + successMsg = fmt.Sprintf("Cassandra服务 %s 爆破成功 用户名: %v 密码: %v", + target, credential.Username, credential.Password) + details = map[string]interface{}{ + "port": info.Ports, + "service": "cassandra", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", } } - return true, nil + Common.LogSuccess(successMsg) + + // 保存结果 + result := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(result) } diff --git a/Plugins/Elasticsearch.go b/Plugins/Elasticsearch.go index 5d1dddc..f5ad1a1 100644 --- a/Plugins/Elasticsearch.go +++ b/Plugins/Elasticsearch.go @@ -1,153 +1,214 @@ package Plugins import ( + "context" "crypto/tls" "encoding/base64" "fmt" "github.com/shadow1ng/fscan/Common" "net/http" "strings" + "sync" "time" ) -func ElasticScan(info *Common.HostInfo) (tmperr error) { - if Common.DisableBrute { - return - } - - maxRetries := Common.MaxRetries - target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - Common.LogDebug("尝试无认证访问...") - - // 首先测试无认证访问 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1)) - } - flag, err := ElasticConn(info, "", "") - if flag && err == nil { - successMsg := fmt.Sprintf("Elasticsearch服务 %s 无需认证", target) - Common.LogSuccess(successMsg) - - // 保存无认证访问结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "elasticsearch", - "type": "unauthorized-access", - }, - } - Common.SaveResult(result) - return err - } - if err != nil && Common.CheckErrs(err) != nil { - if retryCount == maxRetries-1 { - return err - } - continue - } - break - } - - totalUsers := len(Common.Userdict["elastic"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", - totalUsers, totalPass)) - - tried := 0 - total := totalUsers * totalPass - - // 遍历所有用户名密码组合 - for _, user := range Common.Userdict["elastic"] { - for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) - - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } - - done := make(chan struct { - success bool - err error - }, 1) - - go func(user, pass string) { - flag, err := ElasticConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{flag, err}: - default: - } - }(user, pass) - - var err error - select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("Elasticsearch服务 %s 爆破成功 用户名: %v 密码: %v", - target, user, pass) - Common.LogSuccess(successMsg) - - // 保存弱密码结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "elasticsearch", - "username": user, - "password": pass, - "type": "weak-password", - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errlog := fmt.Sprintf("Elasticsearch服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", - target, user, pass, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue - } - } - break - } - } - } - - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr +// ElasticCredential 表示Elasticsearch的凭据 +type ElasticCredential struct { + Username string + Password string } -// ElasticConn 尝试 Elasticsearch 连接 -func ElasticConn(info *Common.HostInfo, user string, pass string) (bool, error) { - host, port := info.Host, info.Ports - timeout := time.Duration(Common.Timeout) * time.Second +// ElasticScanResult 表示扫描结果 +type ElasticScanResult struct { + Success bool + IsUnauth bool + Error error + Credential ElasticCredential +} +func ElasticScan(info *Common.HostInfo) error { + if Common.DisableBrute { + return nil + } + + target := fmt.Sprintf("%v:%v", info.Host, info.Ports) + Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 首先测试无认证访问 + Common.LogDebug("尝试无认证访问...") + unauthResult := tryElasticCredential(ctx, info, ElasticCredential{"", ""}, Common.Timeout, Common.MaxRetries) + + if unauthResult.Success { + // 无需认证情况 + saveElasticResult(info, target, unauthResult.Credential, true) + return nil + } + + // 构建凭据列表 + var credentials []ElasticCredential + for _, user := range Common.Userdict["elastic"] { + for _, pass := range Common.Passwords { + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, ElasticCredential{ + Username: user, + Password: actualPass, + }) + } + } + + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["elastic"]), len(Common.Passwords), len(credentials))) + + // 并发扫描 + result := concurrentElasticScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveElasticResult(info, target, result.Credential, false) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("Elasticsearch扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1是因为尝试了无认证 + return nil + } +} + +// concurrentElasticScan 并发扫描Elasticsearch服务 +func concurrentElasticScan(ctx context.Context, info *Common.HostInfo, credentials []ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *ElasticScanResult, 1) + workChan := make(chan ElasticCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := tryElasticCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("Elasticsearch并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryElasticCredential 尝试单个Elasticsearch凭据 +func tryElasticCredential(ctx context.Context, info *Common.HostInfo, credential ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &ElasticScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + success, err := ElasticConn(ctx, info, credential.Username, credential.Password, timeoutSeconds) + if success { + isUnauth := credential.Username == "" && credential.Password == "" + return &ElasticScanResult{ + Success: true, + IsUnauth: isUnauth, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } + } + } + } + + return &ElasticScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } +} + +// ElasticConn 尝试Elasticsearch连接 +func ElasticConn(ctx context.Context, info *Common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) { + host, port := info.Host, info.Ports + timeout := time.Duration(timeoutSeconds) * time.Second + + // 创建带有超时的HTTP客户端 client := &http.Client{ Timeout: timeout, Transport: &http.Transport{ @@ -156,7 +217,9 @@ func ElasticConn(info *Common.HostInfo, user string, pass string) (bool, error) } baseURL := fmt.Sprintf("http://%s:%s", host, port) - req, err := http.NewRequest("GET", baseURL+"/_cat/indices", nil) + + // 使用上下文创建请求 + req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/_cat/indices", nil) if err != nil { return false, err } @@ -166,11 +229,78 @@ func ElasticConn(info *Common.HostInfo, user string, pass string) (bool, error) req.Header.Add("Authorization", "Basic "+auth) } - resp, err := client.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() + // 创建结果通道 + resultChan := make(chan struct { + success bool + err error + }, 1) - return resp.StatusCode == 200, nil + // 在协程中执行HTTP请求 + go func() { + resp, err := client.Do(req) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{false, err}: + } + return + } + defer resp.Body.Close() + + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{resp.StatusCode == 200, nil}: + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.err + case <-ctx.Done(): + return false, ctx.Err() + } +} + +// saveElasticResult 保存Elasticsearch扫描结果 +func saveElasticResult(info *Common.HostInfo, target string, credential ElasticCredential, isUnauth bool) { + var successMsg string + var details map[string]interface{} + + if isUnauth { + successMsg = fmt.Sprintf("Elasticsearch服务 %s 无需认证", target) + details = map[string]interface{}{ + "port": info.Ports, + "service": "elasticsearch", + "type": "unauthorized-access", + } + } else { + successMsg = fmt.Sprintf("Elasticsearch服务 %s 爆破成功 用户名: %v 密码: %v", + target, credential.Username, credential.Password) + details = map[string]interface{}{ + "port": info.Ports, + "service": "elasticsearch", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + } + } + + Common.LogSuccess(successMsg) + + // 保存结果 + result := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(result) } diff --git a/Plugins/FTP.go b/Plugins/FTP.go index 75b983d..79251a9 100644 --- a/Plugins/FTP.go +++ b/Plugins/FTP.go @@ -1,146 +1,262 @@ package Plugins import ( + "context" "fmt" "github.com/jlaffaye/ftp" "github.com/shadow1ng/fscan/Common" "strings" + "sync" "time" ) -func FtpScan(info *Common.HostInfo) (tmperr error) { +// FtpCredential 表示一个FTP凭据 +type FtpCredential struct { + Username string + Password string +} + +// FtpScanResult 表示FTP扫描结果 +type FtpScanResult struct { + Success bool + Error error + Credential FtpCredential + Directories []string + IsAnonymous bool +} + +func FtpScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 首先尝试匿名登录 Common.LogDebug("尝试匿名登录...") + anonymousResult := tryFtpCredential(ctx, info, FtpCredential{"anonymous", ""}, Common.Timeout, Common.MaxRetries) - // 尝试匿名登录 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - success, dirs, err := FtpConn(info, "anonymous", "") - if success && err == nil { - Common.LogSuccess("匿名登录成功!") - - // 保存匿名登录结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "ftp", - "username": "anonymous", - "password": "", - "type": "anonymous-login", - "directories": dirs, - }, - } - Common.SaveResult(result) - return nil - } - errlog := fmt.Sprintf("ftp %s %v", target, err) - Common.LogError(errlog) - break + if anonymousResult.Success { + // 匿名登录成功 + saveFtpResult(info, target, anonymousResult) + return nil } - totalUsers := len(Common.Userdict["ftp"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - - tried := 0 - total := totalUsers * totalPass - - // 遍历用户名密码组合 + // 构建凭据列表 + var credentials []FtpCredential for _, user := range Common.Userdict["ftp"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, FtpCredential{ + Username: user, + Password: actualPass, + }) + } + } - var lastErr error + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["ftp"]), len(Common.Passwords), len(credentials))) - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + // 使用工作池并发扫描 + result := concurrentFtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 保存成功结果 + saveFtpResult(info, target, result) + return nil + } - done := make(chan struct { - success bool - dirs []string - err error - }, 1) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("FTP扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名登录 + return nil + } +} - go func(user, pass string) { - success, dirs, err := FtpConn(info, user, pass) - select { - case done <- struct { - success bool - dirs []string - err error - }{success, dirs, err}: - default: - } - }(user, pass) +// concurrentFtpScan 并发扫描FTP服务 +func concurrentFtpScan(ctx context.Context, info *Common.HostInfo, credentials []FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *FtpScanResult, 1) + workChan := make(chan FtpCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case result := <-done: - if result.success && result.err == nil { - successLog := fmt.Sprintf("FTP服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(successLog) - - // 保存爆破成功结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "ftp", - "username": user, - "password": pass, - "type": "weak-password", - "directories": result.dirs, - }, + case <-scanCtx.Done(): + return + default: + result := tryFtpCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - Common.SaveResult(vulnResult) - return nil + return } - lastErr = result.err - case <-time.After(time.Duration(Common.Timeout) * time.Second): - lastErr = fmt.Errorf("连接超时") + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("FTP并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryFtpCredential 尝试单个FTP凭据 +func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &FtpScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建结果通道 + resultChan := make(chan struct { + success bool + directories []string + err error + }, 1) + + // 在协程中尝试连接 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + go func() { + defer cancel() + success, dirs, err := FtpConn(info, credential.Username, credential.Password) + select { + case <-connCtx.Done(): + case resultChan <- struct { + success bool + directories []string + err error + }{success, dirs, err}: + } + }() + + // 等待结果或超时 + var success bool + var dirs []string + var err error + + select { + case result := <-resultChan: + success = result.success + dirs = result.directories + err = result.err + case <-connCtx.Done(): + if ctx.Err() != nil { + // 全局超时 + return &FtpScanResult{ + Success: false, + Error: ctx.Err(), + Credential: credential, + } + } + // 单个连接超时 + err = fmt.Errorf("连接超时") + } + + if success { + isAnonymous := credential.Username == "anonymous" && credential.Password == "" + return &FtpScanResult{ + Success: true, + Credential: credential, + Directories: dirs, + IsAnonymous: isAnonymous, + } + } + + lastErr = err + if err != nil { + // 登录错误不需要重试 + if strings.Contains(err.Error(), "Login incorrect") { + break } - // 错误处理 - if lastErr != nil { - errlog := fmt.Sprintf("FTP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", - target, user, pass, lastErr) - Common.LogError(errlog) + // 连接数过多需要等待 + if strings.Contains(err.Error(), "too many connections") { + Common.LogDebug("连接数过多,等待5秒...") + time.Sleep(5 * time.Second) + continue + } - if strings.Contains(lastErr.Error(), "Login incorrect") { - break - } - - if strings.Contains(lastErr.Error(), "too many connections") { - Common.LogDebug("连接数过多,等待5秒...") - time.Sleep(5 * time.Second) - if retryCount < maxRetries-1 { - continue - } - } + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &FtpScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // FtpConn 建立FTP连接并尝试登录 @@ -179,6 +295,47 @@ func FtpConn(info *Common.HostInfo, user string, pass string) (success bool, dir return true, directories, nil } +// saveFtpResult 保存FTP扫描结果 +func saveFtpResult(info *Common.HostInfo, target string, result *FtpScanResult) { + var successMsg string + var details map[string]interface{} + + if result.IsAnonymous { + successMsg = fmt.Sprintf("FTP服务 %s 匿名登录成功!", target) + details = map[string]interface{}{ + "port": info.Ports, + "service": "ftp", + "username": "anonymous", + "password": "", + "type": "anonymous-login", + "directories": result.Directories, + } + } else { + successMsg = fmt.Sprintf("FTP服务 %s 成功爆破 用户名: %v 密码: %v", + target, result.Credential.Username, result.Credential.Password) + details = map[string]interface{}{ + "port": info.Ports, + "service": "ftp", + "username": result.Credential.Username, + "password": result.Credential.Password, + "type": "weak-password", + "directories": result.Directories, + } + } + + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(vulnResult) +} + // min 返回两个整数中的较小值 func min(a, b int) int { if a < b { diff --git a/Plugins/IMAP.go b/Plugins/IMAP.go index 533c9f3..db2362d 100644 --- a/Plugins/IMAP.go +++ b/Plugins/IMAP.go @@ -2,134 +2,266 @@ package Plugins import ( "bufio" + "context" "crypto/tls" "fmt" "github.com/shadow1ng/fscan/Common" "io" "net" "strings" + "sync" "time" ) +// IMAPCredential 表示一个IMAP凭据 +type IMAPCredential struct { + Username string + Password string +} + +// IMAPScanResult 表示IMAP扫描结果 +type IMAPScanResult struct { + Success bool + Error error + Credential IMAPCredential +} + // IMAPScan 主扫描函数 -func IMAPScan(info *Common.HostInfo) (tmperr error) { +func IMAPScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalUsers := len(Common.Userdict["imap"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - tried := 0 - total := totalUsers * totalPass + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + // 构建凭据列表 + var credentials []IMAPCredential for _, user := range Common.Userdict["imap"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, IMAPCredential{ + Username: user, + Password: actualPass, + }) + } + } - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["imap"]), len(Common.Passwords), len(credentials))) - done := make(chan struct { - success bool - err error - }, 1) + // 并发扫描 + result := concurrentIMAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveIMAPResult(info, target, result.Credential) + return nil + } - go func(user, pass string) { - success, err := IMAPConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("IMAP扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } +} - var err error +// concurrentIMAPScan 并发扫描IMAP服务 +func concurrentIMAPScan(ctx context.Context, info *Common.HostInfo, credentials []IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *IMAPScanResult, 1) + workChan := make(chan IMAPCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case result := <-done: - err = result.err - if result.success { - successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "imap", - "username": user, - "password": pass, - "type": "weak-password", - }, + case <-scanCtx.Done(): + return + default: + result := tryIMAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errMsg := fmt.Sprintf("IMAP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err) - Common.LogError(errMsg) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + return } } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("IMAP并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryIMAPCredential 尝试单个IMAP凭据 +func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &IMAPScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建单个连接超时的上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := IMAPConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &IMAPScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &IMAPScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // IMAPConn 连接测试函数 -func IMAPConn(info *Common.HostInfo, user string, pass string) (bool, error) { +func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { host, port := info.Host, info.Ports timeout := time.Duration(Common.Timeout) * time.Second addr := fmt.Sprintf("%s:%s", host, port) - // 尝试普通连接 - conn, err := net.DialTimeout("tcp", addr, timeout) - if err == nil { - if flag, err := tryIMAPAuth(conn, user, pass, timeout); err == nil { - return flag, nil + // 创建结果通道 + resultChan := make(chan struct { + success bool + err error + }, 1) + + // 在协程中尝试连接 + go func() { + // 先尝试普通连接 + dialer := &net.Dialer{Timeout: timeout} + conn, err := dialer.DialContext(ctx, "tcp", addr) + if err == nil { + flag, authErr := tryIMAPAuth(conn, user, pass, timeout) + conn.Close() + if authErr == nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{flag, nil}: + } + return + } } - conn.Close() - } - // 尝试TLS连接 - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - } - conn, err = tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, tlsConfig) - if err != nil { - return false, fmt.Errorf("连接失败: %v", err) - } - defer conn.Close() + // 如果普通连接失败或认证失败,尝试TLS连接 + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + } + tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig) + if tlsErr != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{false, fmt.Errorf("连接失败: %v", tlsErr)}: + } + return + } + defer tlsConn.Close() - return tryIMAPAuth(conn, user, pass, timeout) + flag, authErr := tryIMAPAuth(tlsConn, user, pass, timeout) + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{flag, authErr}: + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.err + case <-ctx.Done(): + return false, ctx.Err() + } } // tryIMAPAuth 尝试IMAP认证 @@ -167,3 +299,26 @@ func tryIMAPAuth(conn net.Conn, user string, pass string, timeout time.Duration) } } } + +// saveIMAPResult 保存IMAP扫描结果 +func saveIMAPResult(info *Common.HostInfo, target string, credential IMAPCredential) { + successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v", + target, credential.Username, credential.Password) + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "imap", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + }, + } + Common.SaveResult(vulnResult) +} diff --git a/Plugins/Kafka.go b/Plugins/Kafka.go index bd81312..a0023a2 100644 --- a/Plugins/Kafka.go +++ b/Plugins/Kafka.go @@ -1,134 +1,282 @@ package Plugins import ( + "context" "fmt" "github.com/IBM/sarama" "github.com/shadow1ng/fscan/Common" "strings" + "sync" "time" ) -func KafkaScan(info *Common.HostInfo) (tmperr error) { +// KafkaCredential 表示Kafka凭据 +type KafkaCredential struct { + Username string + Password string +} + +// KafkaScanResult 表示扫描结果 +type KafkaScanResult struct { + Success bool + IsUnauth bool + Error error + Credential KafkaCredential +} + +func KafkaScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - // 尝试无认证访问 + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 先尝试无认证访问 Common.LogDebug("尝试无认证访问...") - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1)) + unauthResult := tryKafkaCredential(ctx, info, KafkaCredential{"", ""}, Common.Timeout, Common.MaxRetries) + + if unauthResult.Success { + // 无认证访问成功 + Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target)) + + // 保存无认证访问结果 + result := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "kafka", + "type": "unauthorized-access", + }, } - flag, err := KafkaConn(info, "", "") - if flag && err == nil { - // 保存无认证访问结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "kafka", - "type": "unauthorized-access", - }, - } - Common.SaveResult(result) - Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target)) - return nil - } - if err != nil && Common.CheckErrs(err) != nil { - if retryCount < maxRetries-1 { - continue - } - return err - } - break + Common.SaveResult(result) + return nil } - totalUsers := len(Common.Userdict["kafka"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - - tried := 0 - total := totalUsers * totalPass - - // 遍历所有用户名密码组合 + // 构建凭据列表 + var credentials []KafkaCredential for _, user := range Common.Userdict["kafka"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, KafkaCredential{ + Username: user, + Password: actualPass, + }) + } + } - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["kafka"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentKafkaScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 保存爆破成功结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "kafka", + "type": "weak-password", + "username": result.Credential.Username, + "password": result.Credential.Password, + }, + } + Common.SaveResult(vulnResult) + Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s", + target, result.Credential.Username, result.Credential.Password)) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("Kafka扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了无认证 + return nil + } +} + +// concurrentKafkaScan 并发扫描Kafka服务 +func concurrentKafkaScan(ctx context.Context, info *Common.HostInfo, credentials []KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *KafkaScanResult, 1) + workChan := make(chan KafkaCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := tryKafkaCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } } + } + }() + } - done := make(chan struct { + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("Kafka并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryKafkaCredential 尝试单个Kafka凭据 +func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &KafkaScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建单个连接超时的上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + + // 在协程中执行Kafka连接 + resultChan := make(chan struct { + success bool + err error + }, 1) + + go func() { + success, err := KafkaConn(info, credential.Username, credential.Password) + select { + case <-connCtx.Done(): + // 连接超时或被取消 + case resultChan <- struct { success bool err error - }, 1) - - go func(user, pass string) { - success, err := KafkaConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) - - var err error - select { - case result := <-done: - err = result.err - if result.success && err == nil { - // 保存爆破成功结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "kafka", - "type": "weak-password", - "username": user, - "password": pass, - }, - } - Common.SaveResult(vulnResult) - Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s", target, user, pass)) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") + }{success, err}: + // 发送结果 } + }() - if err != nil { - Common.LogError(fmt.Sprintf("Kafka服务 %s 尝试失败 用户名: %s 密码: %s 错误: %v", - target, user, pass, err)) - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + // 等待结果或超时 + var success bool + var err error + + select { + case result := <-resultChan: + success = result.success + err = result.err + case <-connCtx.Done(): + if ctx.Err() != nil { + // 全局超时 + cancel() + return &KafkaScanResult{ + Success: false, + Error: ctx.Err(), + Credential: credential, } } - break + // 单个连接超时 + err = fmt.Errorf("连接超时") + } + + cancel() // 清理单个连接上下文 + + if success { + isUnauth := credential.Username == "" && credential.Password == "" + return &KafkaScanResult{ + Success: true, + IsUnauth: isUnauth, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 记录错误 + Common.LogError(fmt.Sprintf("Kafka尝试失败 用户名: %s 密码: %s 错误: %v", + credential.Username, credential.Password, err)) + + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &KafkaScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // KafkaConn 尝试 Kafka 连接 @@ -138,6 +286,8 @@ func KafkaConn(info *Common.HostInfo, user string, pass string) (bool, error) { config := sarama.NewConfig() config.Net.DialTimeout = timeout + config.Net.ReadTimeout = timeout + config.Net.WriteTimeout = timeout config.Net.TLS.Enable = false config.Version = sarama.V2_0_0_0 diff --git a/Plugins/LDAP.go b/Plugins/LDAP.go index dbc18e5..e798b49 100644 --- a/Plugins/LDAP.go +++ b/Plugins/LDAP.go @@ -1,166 +1,312 @@ package Plugins import ( + "context" "fmt" "github.com/go-ldap/ldap/v3" "github.com/shadow1ng/fscan/Common" + "net" "strings" + "sync" "time" ) -func LDAPScan(info *Common.HostInfo) (tmperr error) { +// LDAPCredential 表示一个LDAP凭据 +type LDAPCredential struct { + Username string + Password string +} + +// LDAPScanResult 表示LDAP扫描结果 +type LDAPScanResult struct { + Success bool + Error error + Credential LDAPCredential + IsAnonymous bool +} + +func LDAPScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - Common.LogDebug("尝试匿名访问...") + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() // 首先尝试匿名访问 - flag, err := LDAPConn(info, "", "") - if flag && err == nil { - // 记录匿名访问成功 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "ldap", - "type": "anonymous-access", - }, - } - Common.SaveResult(result) - Common.LogSuccess(fmt.Sprintf("LDAP服务 %s 匿名访问成功", target)) - return err + Common.LogDebug("尝试匿名访问...") + anonymousResult := tryLDAPCredential(ctx, info, LDAPCredential{"", ""}, Common.Timeout, 1) + + if anonymousResult.Success { + // 匿名访问成功 + saveLDAPResult(info, target, anonymousResult) + return nil } - totalUsers := len(Common.Userdict["ldap"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - - tried := 0 - total := totalUsers * totalPass - - // 遍历所有用户名密码组合 + // 构建凭据列表 + var credentials []LDAPCredential for _, user := range Common.Userdict["ldap"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, LDAPCredential{ + Username: user, + Password: actualPass, + }) + } + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["ldap"]), len(Common.Passwords), len(credentials))) - done := make(chan struct { - success bool - err error - }, 1) + // 使用工作池并发扫描 + result := concurrentLDAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveLDAPResult(info, target, result) + return nil + } - go func(user, pass string) { - success, err := LDAPConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("LDAP扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 + return nil + } +} - var err error +// concurrentLDAPScan 并发扫描LDAP服务 +func concurrentLDAPScan(ctx context.Context, info *Common.HostInfo, credentials []LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *LDAPScanResult, 1) + workChan := make(chan LDAPCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case result := <-done: - err = result.err - if result.success && err == nil { - // 记录成功爆破的凭据 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "ldap", - "username": user, - "password": pass, - "type": "weak-password", - }, + case <-scanCtx.Done(): + return + default: + result := tryLDAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - Common.SaveResult(vulnResult) - Common.LogSuccess(fmt.Sprintf("LDAP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass)) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errlog := fmt.Sprintf("LDAP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + return } } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("LDAP并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryLDAPCredential 尝试单个LDAP凭据 +func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &LDAPScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := LDAPConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + isAnonymous := credential.Username == "" && credential.Password == "" + return &LDAPScanResult{ + Success: true, + Credential: credential, + IsAnonymous: isAnonymous, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &LDAPScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } -func LDAPConn(info *Common.HostInfo, user string, pass string) (bool, error) { +// LDAPConn 尝试LDAP连接 +func LDAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { address := fmt.Sprintf("%s:%s", info.Host, info.Ports) - timeout := time.Duration(Common.Timeout) * time.Second - // 配置LDAP连接 - l, err := ldap.Dial("tcp", address) + // 创建拨号器并设置超时 + dialer := &net.Dialer{ + Timeout: time.Duration(Common.Timeout) * time.Second, + } + + // 使用上下文控制的拨号过程 + conn, err := dialer.DialContext(ctx, "tcp", address) if err != nil { return false, err } + + // 使用已连接的TCP连接创建LDAP连接 + l := ldap.NewConn(conn, false) defer l.Close() - // 设置超时 - l.SetTimeout(timeout) + // 在单独的协程中启动LDAP连接 + go l.Start() - // 尝试绑定 - if user != "" { - bindDN := fmt.Sprintf("cn=%s,dc=example,dc=com", user) - err = l.Bind(bindDN, pass) - } else { - err = l.UnauthenticatedBind("") + // 创建一个完成通道 + done := make(chan error, 1) + + // 在协程中进行绑定和搜索操作,确保可以被上下文取消 + go func() { + // 尝试绑定 + var err error + if user != "" { + // 使用更通用的绑定DN模式 + bindDN := fmt.Sprintf("cn=%s,dc=example,dc=com", user) + err = l.Bind(bindDN, pass) + } else { + // 匿名绑定 + err = l.UnauthenticatedBind("") + } + + if err != nil { + done <- err + return + } + + // 尝试简单搜索以验证权限 + searchRequest := ldap.NewSearchRequest( + "dc=example,dc=com", + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + "(objectClass=*)", + []string{"dn"}, + nil, + ) + + _, err = l.Search(searchRequest) + done <- err + }() + + // 等待操作完成或上下文取消 + select { + case err := <-done: + if err != nil { + return false, err + } + return true, nil + case <-ctx.Done(): + return false, ctx.Err() } - - if err != nil { - return false, err - } - - // 尝试简单搜索以验证权限 - searchRequest := ldap.NewSearchRequest( - "dc=example,dc=com", - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - "(objectClass=*)", - []string{"dn"}, - nil, - ) - - _, err = l.Search(searchRequest) - if err != nil { - return false, err - } - - return true, nil +} + +// saveLDAPResult 保存LDAP扫描结果 +func saveLDAPResult(info *Common.HostInfo, target string, result *LDAPScanResult) { + var successMsg string + var details map[string]interface{} + + if result.IsAnonymous { + successMsg = fmt.Sprintf("LDAP服务 %s 匿名访问成功", target) + details = map[string]interface{}{ + "port": info.Ports, + "service": "ldap", + "type": "anonymous-access", + } + } else { + successMsg = fmt.Sprintf("LDAP服务 %s 爆破成功 用户名: %v 密码: %v", + target, result.Credential.Username, result.Credential.Password) + details = map[string]interface{}{ + "port": info.Ports, + "service": "ldap", + "username": result.Credential.Username, + "password": result.Credential.Password, + "type": "weak-password", + } + } + + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(vulnResult) } diff --git a/Plugins/MSSQL.go b/Plugins/MSSQL.go index 6b23d43..22cc244 100644 --- a/Plugins/MSSQL.go +++ b/Plugins/MSSQL.go @@ -1,121 +1,208 @@ package Plugins import ( + "context" "database/sql" "fmt" _ "github.com/denisenkom/go-mssqldb" "github.com/shadow1ng/fscan/Common" "strings" + "sync" "time" ) +// MssqlCredential 表示一个MSSQL凭据 +type MssqlCredential struct { + Username string + Password string +} + +// MssqlScanResult 表示MSSQL扫描结果 +type MssqlScanResult struct { + Success bool + Error error + Credential MssqlCredential +} + // MssqlScan 执行MSSQL服务扫描 -func MssqlScan(info *Common.HostInfo) (tmperr error) { +func MssqlScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalUsers := len(Common.Userdict["mssql"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - tried := 0 - total := totalUsers * totalPass + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 遍历所有用户名密码组合 + // 构建凭据列表 + var credentials []MssqlCredential for _, user := range Common.Userdict["mssql"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, MssqlCredential{ + Username: user, + Password: actualPass, + }) + } + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["mssql"]), len(Common.Passwords), len(credentials))) - // 执行MSSQL连接 - done := make(chan struct { - success bool - err error - }, 1) + // 使用工作池并发扫描 + result := concurrentMssqlScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveMssqlResult(info, target, result.Credential) + return nil + } - go func(user, pass string) { - success, err := MssqlConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("MSSQL扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } +} - // 等待结果或超时 - var err error +// concurrentMssqlScan 并发扫描MSSQL服务 +func concurrentMssqlScan(ctx context.Context, info *Common.HostInfo, credentials []MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *MssqlScanResult, 1) + workChan := make(chan MssqlCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("MSSQL %s %v %v", target, user, pass) - Common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "mssql", - "username": user, - "password": pass, - "type": "weak-password", - }, + case <-scanCtx.Done(): + return + default: + result := tryMssqlCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errlog := fmt.Sprintf("MSSQL %s %v %v %v", target, user, pass, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + return } } + } + }() + } + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("MSSQL并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryMssqlCredential 尝试单个MSSQL凭据 +func tryMssqlCredential(ctx context.Context, info *Common.HostInfo, credential MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &MssqlScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建连接超时的上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := MssqlConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &MssqlScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &MssqlScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // MssqlConn 尝试MSSQL连接 -func MssqlConn(info *Common.HostInfo, user string, pass string) (bool, error) { +func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { host, port, username, password := info.Host, info.Ports, user, pass timeout := time.Duration(Common.Timeout) * time.Second // 构造连接字符串 connStr := fmt.Sprintf( - "server=%s;user id=%s;password=%s;port=%v;encrypt=disable;timeout=%v", - host, username, password, port, timeout, + "server=%s;user id=%s;password=%s;port=%v;encrypt=disable;", + host, username, password, port, ) // 建立数据库连接 @@ -129,11 +216,54 @@ func MssqlConn(info *Common.HostInfo, user string, pass string) (bool, error) { db.SetConnMaxLifetime(timeout) db.SetConnMaxIdleTime(timeout) db.SetMaxIdleConns(0) + db.SetMaxOpenConns(1) - // 测试连接 - if err = db.Ping(); err != nil { - return false, err + // 通过上下文执行ping操作,以支持超时控制 + pingCtx, pingCancel := context.WithTimeout(ctx, timeout) + defer pingCancel() + + errChan := make(chan error, 1) + go func() { + errChan <- db.PingContext(pingCtx) + }() + + // 等待ping结果或者超时 + select { + case err := <-errChan: + if err != nil { + return false, err + } + return true, nil + case <-ctx.Done(): + // 全局超时或取消 + return false, ctx.Err() + case <-pingCtx.Done(): + if pingCtx.Err() == context.DeadlineExceeded { + // 单个连接超时 + return false, fmt.Errorf("连接超时") + } + return false, pingCtx.Err() } - - return true, nil +} + +// saveMssqlResult 保存MSSQL扫描结果 +func saveMssqlResult(info *Common.HostInfo, target string, credential MssqlCredential) { + successMsg := fmt.Sprintf("MSSQL %s %v %v", target, credential.Username, credential.Password) + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "mssql", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + }, + } + Common.SaveResult(vulnResult) } diff --git a/Plugins/Memcached.go b/Plugins/Memcached.go index 71d988c..a6a5c99 100644 --- a/Plugins/Memcached.go +++ b/Plugins/Memcached.go @@ -1,46 +1,35 @@ package Plugins import ( + "context" "fmt" "github.com/shadow1ng/fscan/Common" "strings" "time" ) +// MemcachedScanResult 表示Memcached扫描结果 +type MemcachedScanResult struct { + Success bool + Error error + Stats string +} + // MemcachedScan 检测Memcached未授权访问 func MemcachedScan(info *Common.HostInfo) error { + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) - timeout := time.Duration(Common.Timeout) * time.Second + Common.LogDebug(fmt.Sprintf("开始扫描 Memcached %s", realhost)) - // 建立TCP连接 - client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout) - if err != nil { - return err - } - defer client.Close() + // 尝试连接并检查未授权访问 + result := tryMemcachedConnection(ctx, info, Common.Timeout) - // 设置超时时间 - if err := client.SetDeadline(time.Now().Add(timeout)); err != nil { - return err - } - - // 发送stats命令 - if _, err := client.Write([]byte("stats\n")); err != nil { - return err - } - - // 读取响应 - rev := make([]byte, 1024) - n, err := client.Read(rev) - if err != nil { - Common.LogError(fmt.Sprintf("Memcached %v:%v %v", info.Host, info.Ports, err)) - return err - } - - // 检查响应内容 - if strings.Contains(string(rev[:n]), "STAT") { - // 保存结果 - result := &Common.ScanResult{ + if result.Success { + // 保存成功结果 + scanResult := &Common.ScanResult{ Time: time.Now(), Type: Common.VULN, Target: info.Host, @@ -50,11 +39,122 @@ func MemcachedScan(info *Common.HostInfo) error { "service": "memcached", "type": "unauthorized-access", "description": "Memcached unauthorized access", + "stats": result.Stats, }, } - Common.SaveResult(result) + Common.SaveResult(scanResult) Common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", realhost)) } - return nil + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + if ctx.Err() == context.DeadlineExceeded { + Common.LogDebug("Memcached扫描全局超时") + return fmt.Errorf("全局超时") + } + default: + } + + Common.LogDebug(fmt.Sprintf("Memcached扫描完成: %s", realhost)) + return result.Error +} + +// tryMemcachedConnection 尝试连接Memcached并检查未授权访问 +func tryMemcachedConnection(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64) *MemcachedScanResult { + realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) + timeout := time.Duration(timeoutSeconds) * time.Second + + // 创建结果通道 + resultChan := make(chan *MemcachedScanResult, 1) + + // 创建连接上下文,带超时 + connCtx, connCancel := context.WithTimeout(ctx, timeout) + defer connCancel() + + // 在协程中尝试连接 + go func() { + // 构建结果结构 + result := &MemcachedScanResult{ + Success: false, + Error: nil, + Stats: "", + } + + // 建立TCP连接 + client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout) + if err != nil { + result.Error = err + select { + case <-connCtx.Done(): + case resultChan <- result: + } + return + } + defer client.Close() + + // 设置操作截止时间 + if err := client.SetDeadline(time.Now().Add(timeout)); err != nil { + result.Error = err + select { + case <-connCtx.Done(): + case resultChan <- result: + } + return + } + + // 发送stats命令 + if _, err := client.Write([]byte("stats\n")); err != nil { + result.Error = err + select { + case <-connCtx.Done(): + case resultChan <- result: + } + return + } + + // 读取响应 + rev := make([]byte, 1024) + n, err := client.Read(rev) + if err != nil { + result.Error = err + select { + case <-connCtx.Done(): + case resultChan <- result: + } + return + } + + // 检查响应是否包含统计信息 + response := string(rev[:n]) + if strings.Contains(response, "STAT") { + result.Success = true + result.Stats = response + } + + // 发送结果 + select { + case <-connCtx.Done(): + case resultChan <- result: + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result + case <-connCtx.Done(): + if ctx.Err() != nil { + // 全局上下文取消 + return &MemcachedScanResult{ + Success: false, + Error: ctx.Err(), + } + } + // 连接超时 + return &MemcachedScanResult{ + Success: false, + Error: fmt.Errorf("连接超时"), + } + } } diff --git a/Plugins/Modbus.go b/Plugins/Modbus.go index 3445280..0b8af4e 100644 --- a/Plugins/Modbus.go +++ b/Plugins/Modbus.go @@ -1,6 +1,7 @@ package Plugins import ( + "context" "encoding/binary" "fmt" "github.com/shadow1ng/fscan/Common" @@ -8,67 +9,180 @@ import ( "time" ) +// ModbusScanResult 表示 Modbus 扫描结果 +type ModbusScanResult struct { + Success bool + DeviceInfo string + Error error +} + // ModbusScan 执行 Modbus 服务扫描 func ModbusScan(info *Common.HostInfo) error { - host, port := info.Host, info.Ports - timeout := time.Duration(Common.Timeout) * time.Second + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + Common.LogDebug(fmt.Sprintf("开始 Modbus 扫描: %s", target)) - // 尝试建立连接 - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout) - if err != nil { - return err - } - defer conn.Close() + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 构造Modbus TCP请求包 - 读取设备ID - request := buildModbusRequest() - - // 设置读写超时 - conn.SetDeadline(time.Now().Add(timeout)) - - // 发送请求 - _, err = conn.Write(request) - if err != nil { - return fmt.Errorf("发送Modbus请求失败: %v", err) - } - - // 读取响应 - response := make([]byte, 256) - n, err := conn.Read(response) - if err != nil { - return fmt.Errorf("读取Modbus响应失败: %v", err) - } - - // 验证响应 - if isValidModbusResponse(response[:n]) { - // 获取设备信息 - deviceInfo := parseModbusResponse(response[:n]) + // 执行扫描 + result := tryModbusScan(ctx, info, Common.Timeout, Common.MaxRetries) + if result.Success { // 保存扫描结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": port, - "service": "modbus", - "type": "unauthorized-access", - "device_id": deviceInfo, - }, - } - Common.SaveResult(result) - - // 控制台输出 - Common.LogSuccess(fmt.Sprintf("Modbus服务 %v:%v 无认证访问", host, port)) - if deviceInfo != "" { - Common.LogSuccess(fmt.Sprintf("设备信息: %s", deviceInfo)) - } - + saveModbusResult(info, target, result) return nil } - return fmt.Errorf("非Modbus服务或访问被拒绝") + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("Modbus 扫描全局超时") + return fmt.Errorf("全局超时") + default: + if result.Error != nil { + Common.LogDebug(fmt.Sprintf("Modbus 扫描失败: %v", result.Error)) + return result.Error + } + Common.LogDebug("Modbus 扫描完成,未发现服务") + return nil + } +} + +// tryModbusScan 尝试单个 Modbus 扫描 +func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64, maxRetries int) *ModbusScanResult { + var lastErr error + host, port := info.Host, info.Ports + target := fmt.Sprintf("%s:%s", host, port) + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &ModbusScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试 Modbus 扫描: %s", retry+1, target)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建单个连接超时的上下文 + connCtx, connCancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + + // 创建结果通道 + resultChan := make(chan *ModbusScanResult, 1) + + // 在协程中执行扫描 + go func() { + // 尝试建立连接 + var d net.Dialer + conn, err := d.DialContext(connCtx, "tcp", target) + if err != nil { + select { + case <-connCtx.Done(): + case resultChan <- &ModbusScanResult{Success: false, Error: err}: + } + return + } + defer conn.Close() + + // 构造 Modbus TCP 请求包 - 读取设备ID + request := buildModbusRequest() + + // 设置读写超时 + conn.SetDeadline(time.Now().Add(time.Duration(timeoutSeconds) * time.Second)) + + // 发送请求 + _, err = conn.Write(request) + if err != nil { + select { + case <-connCtx.Done(): + case resultChan <- &ModbusScanResult{ + Success: false, + Error: fmt.Errorf("发送Modbus请求失败: %v", err), + }: + } + return + } + + // 读取响应 + response := make([]byte, 256) + n, err := conn.Read(response) + if err != nil { + select { + case <-connCtx.Done(): + case resultChan <- &ModbusScanResult{ + Success: false, + Error: fmt.Errorf("读取Modbus响应失败: %v", err), + }: + } + return + } + + // 验证响应 + if isValidModbusResponse(response[:n]) { + // 获取设备信息 + deviceInfo := parseModbusResponse(response[:n]) + select { + case <-connCtx.Done(): + case resultChan <- &ModbusScanResult{ + Success: true, + DeviceInfo: deviceInfo, + }: + } + return + } + + select { + case <-connCtx.Done(): + case resultChan <- &ModbusScanResult{ + Success: false, + Error: fmt.Errorf("非Modbus服务或访问被拒绝"), + }: + } + }() + + // 等待扫描结果或超时 + var result *ModbusScanResult + select { + case res := <-resultChan: + result = res + case <-connCtx.Done(): + if ctx.Err() != nil { + connCancel() + return &ModbusScanResult{ + Success: false, + Error: ctx.Err(), + } + } + result = &ModbusScanResult{ + Success: false, + Error: fmt.Errorf("连接超时"), + } + } + + connCancel() + + if result.Success { + return result + } + + lastErr = result.Error + if result.Error != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(result.Error); retryErr == nil { + break // 不需要重试的错误 + } + } + } + } + + return &ModbusScanResult{ + Success: false, + Error: lastErr, + } } // buildModbusRequest 构建Modbus TCP请求包 @@ -116,6 +230,45 @@ func parseModbusResponse(response []byte) string { return "" } + // 提取更多设备信息 unitID := response[6] - return fmt.Sprintf("Unit ID: %d", unitID) + funcCode := response[7] + + // 简单的设备信息提取,实际应用中可以提取更多信息 + info := fmt.Sprintf("Unit ID: %d, Function: 0x%02X", unitID, funcCode) + + // 如果是读取线圈响应,尝试解析线圈状态 + if funcCode == 0x01 && len(response) >= 10 { + byteCount := response[8] + if byteCount > 0 && len(response) >= 9+int(byteCount) { + coilValue := response[9] & 0x01 // 获取第一个线圈状态 + info += fmt.Sprintf(", Coil Status: %d", coilValue) + } + } + + return info +} + +// saveModbusResult 保存Modbus扫描结果 +func saveModbusResult(info *Common.HostInfo, target string, result *ModbusScanResult) { + // 保存扫描结果 + scanResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "modbus", + "type": "unauthorized-access", + "device_info": result.DeviceInfo, + }, + } + Common.SaveResult(scanResult) + + // 控制台输出 + Common.LogSuccess(fmt.Sprintf("Modbus服务 %s 无认证访问", target)) + if result.DeviceInfo != "" { + Common.LogSuccess(fmt.Sprintf("设备信息: %s", result.DeviceInfo)) + } } diff --git a/Plugins/Mongodb.go b/Plugins/Mongodb.go index 95d4b56..4066a6b 100644 --- a/Plugins/Mongodb.go +++ b/Plugins/Mongodb.go @@ -1,8 +1,11 @@ package Plugins import ( + "context" "fmt" "github.com/shadow1ng/fscan/Common" + "io" + "net" "strings" "time" ) @@ -14,85 +17,151 @@ func MongodbScan(info *Common.HostInfo) error { } target := fmt.Sprintf("%s:%v", info.Host, info.Ports) - isUnauth, err := MongodbUnauth(info) + Common.LogDebug(fmt.Sprintf("开始MongoDB扫描: %s", target)) - if err != nil { - errlog := fmt.Sprintf("MongoDB %v %v", target, err) - Common.LogError(errlog) - } else if isUnauth { - // 记录控制台输出 - Common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target)) + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 保存未授权访问结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "mongodb", - "type": "unauthorized-access", - "protocol": "mongodb", - }, + // 创建结果通道 + resultChan := make(chan struct { + isUnauth bool + err error + }, 1) + + // 在协程中执行扫描 + go func() { + isUnauth, err := MongodbUnauth(ctx, info) + select { + case <-ctx.Done(): + case resultChan <- struct { + isUnauth bool + err error + }{isUnauth, err}: } - Common.SaveResult(result) - } + }() - return err + // 等待结果或超时 + select { + case result := <-resultChan: + if result.err != nil { + errlog := fmt.Sprintf("MongoDB %v %v", target, result.err) + Common.LogError(errlog) + return result.err + } else if result.isUnauth { + // 记录控制台输出 + Common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target)) + + // 保存未授权访问结果 + scanResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "mongodb", + "type": "unauthorized-access", + "protocol": "mongodb", + }, + } + Common.SaveResult(scanResult) + } else { + Common.LogDebug(fmt.Sprintf("MongoDB %v 需要认证", target)) + } + return nil + case <-ctx.Done(): + Common.LogError(fmt.Sprintf("MongoDB扫描超时: %s", target)) + return fmt.Errorf("全局超时") + } } // MongodbUnauth 检测MongoDB未授权访问 -func MongodbUnauth(info *Common.HostInfo) (bool, error) { +func MongodbUnauth(ctx context.Context, info *Common.HostInfo) (bool, error) { msgPacket := createOpMsgPacket() queryPacket := createOpQueryPacket() realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) + Common.LogDebug(fmt.Sprintf("检测MongoDB未授权访问: %s", realhost)) // 尝试OP_MSG查询 - reply, err := checkMongoAuth(realhost, msgPacket) + Common.LogDebug("尝试使用OP_MSG协议") + reply, err := checkMongoAuth(ctx, realhost, msgPacket) if err != nil { + Common.LogDebug(fmt.Sprintf("OP_MSG查询失败: %v, 尝试使用OP_QUERY协议", err)) // 失败则尝试OP_QUERY查询 - reply, err = checkMongoAuth(realhost, queryPacket) + reply, err = checkMongoAuth(ctx, realhost, queryPacket) if err != nil { + Common.LogDebug(fmt.Sprintf("OP_QUERY查询也失败: %v", err)) return false, err } } // 检查响应结果 + Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply))) if strings.Contains(reply, "totalLinesWritten") { + Common.LogDebug("响应中包含totalLinesWritten,确认未授权访问") return true, nil } + Common.LogDebug("响应未包含预期内容,可能需要认证") return false, nil } // checkMongoAuth 检查MongoDB认证状态 -func checkMongoAuth(address string, packet []byte) (string, error) { - // 建立TCP连接 - conn, err := Common.WrapperTcpWithTimeout("tcp", address, time.Duration(Common.Timeout)*time.Second) +func checkMongoAuth(ctx context.Context, address string, packet []byte) (string, error) { + Common.LogDebug(fmt.Sprintf("建立MongoDB连接: %s", address)) + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second) + defer cancel() + + // 使用带超时的连接 + var d net.Dialer + conn, err := d.DialContext(connCtx, "tcp", address) if err != nil { - return "", err + return "", fmt.Errorf("连接失败: %v", err) } defer conn.Close() - // 设置超时时间 - if err := conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { - return "", err + // 检查上下文是否已取消 + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + // 设置读写超时 + if err := conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { + return "", fmt.Errorf("设置超时失败: %v", err) } // 发送查询包 + Common.LogDebug("发送查询包") if _, err := conn.Write(packet); err != nil { - return "", err + return "", fmt.Errorf("发送查询失败: %v", err) + } + + // 再次检查上下文是否已取消 + select { + case <-ctx.Done(): + return "", ctx.Err() + default: } // 读取响应 - reply := make([]byte, 1024) + Common.LogDebug("读取响应") + reply := make([]byte, 2048) count, err := conn.Read(reply) - if err != nil { - return "", err + if err != nil && err != io.EOF { + return "", fmt.Errorf("读取响应失败: %v", err) } + if count == 0 { + return "", fmt.Errorf("收到空响应") + } + + Common.LogDebug(fmt.Sprintf("成功接收响应,字节数: %d", count)) return string(reply[:count]), nil } diff --git a/Plugins/MySQL.go b/Plugins/MySQL.go index 4ba6abc..c9a3448 100644 --- a/Plugins/MySQL.go +++ b/Plugins/MySQL.go @@ -1,144 +1,306 @@ package Plugins import ( + "context" "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/shadow1ng/fscan/Common" "strings" + "sync" "time" ) +// MySQLCredential 表示一个MySQL凭据 +type MySQLCredential struct { + Username string + Password string +} + +// MySQLScanResult 表示MySQL扫描结果 +type MySQLScanResult struct { + Success bool + Error error + Credential MySQLCredential +} + // MysqlScan 执行MySQL服务扫描 -func MysqlScan(info *Common.HostInfo) (tmperr error) { +func MysqlScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalUsers := len(Common.Userdict["mysql"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - tried := 0 - total := totalUsers * totalPass + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 遍历所有用户名密码组合 + // 构建凭据列表 + var credentials []MySQLCredential for _, user := range Common.Userdict["mysql"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, MySQLCredential{ + Username: user, + Password: actualPass, + }) + } + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["mysql"]), len(Common.Passwords), len(credentials))) - done := make(chan struct { - success bool - err error - }, 1) + // 使用工作池并发扫描 + result := concurrentMySQLScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveMySQLResult(info, target, result.Credential) + return nil + } - go func(user, pass string) { - success, err := MysqlConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("MySQL扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } +} - var err error +// concurrentMySQLScan 并发扫描MySQL服务 +func concurrentMySQLScan(ctx context.Context, info *Common.HostInfo, credentials []MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *MySQLScanResult, 1) + workChan := make(chan MySQLCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("MySQL %s %v %v", target, user, pass) - Common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "mysql", - "username": user, - "password": pass, - "type": "weak-password", - }, + case <-scanCtx.Done(): + return + default: + result := tryMySQLCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - Common.SaveResult(vulnResult) - return nil + return } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("MySQL并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryMySQLCredential 尝试单个MySQL凭据 +func tryMySQLCredential(ctx context.Context, info *Common.HostInfo, credential MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &MySQLScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建独立的超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := MysqlConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &MySQLScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // Access denied 表示用户名或密码错误,无需重试 + if strings.Contains(err.Error(), "Access denied") { + break } - if err != nil { - errMsg := fmt.Sprintf("MySQL %s %v %v %v", target, user, pass, err) - Common.LogError(errMsg) - - if strings.Contains(err.Error(), "Access denied") { - break // 认证失败,尝试下一个密码 - } - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - tmperr = err - if !strings.Contains(err.Error(), "Access denied") { - continue - } - } - continue - } - break + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &MySQLScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // MysqlConn 尝试MySQL连接 -func MysqlConn(info *Common.HostInfo, user string, pass string) (bool, error) { +func MysqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { host, port, username, password := info.Host, info.Ports, user, pass timeout := time.Duration(Common.Timeout) * time.Second - // 构造连接字符串 + // 构造连接字符串,包含超时设置 connStr := fmt.Sprintf( "%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v", username, password, host, port, timeout, ) - // 建立数据库连接 - db, err := sql.Open("mysql", connStr) - if err != nil { - return false, err + // 创建结果通道 + resultChan := make(chan struct { + success bool + err error + }, 1) + + // 在协程中尝试连接 + go func() { + // 建立数据库连接 + db, err := sql.Open("mysql", connStr) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{false, err}: + } + return + } + defer db.Close() + + // 设置连接参数 + db.SetConnMaxLifetime(timeout) + db.SetConnMaxIdleTime(timeout) + db.SetMaxIdleConns(0) + + // 添加上下文支持 + conn, err := db.Conn(ctx) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{false, err}: + } + return + } + defer conn.Close() + + // 测试连接 + err = conn.PingContext(ctx) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{false, err}: + } + return + } + + // 连接成功 + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{true, nil}: + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.err + case <-ctx.Done(): + return false, ctx.Err() } - defer db.Close() - - // 设置连接参数 - db.SetConnMaxLifetime(timeout) - db.SetConnMaxIdleTime(timeout) - db.SetMaxIdleConns(0) - - // 测试连接 - if err = db.Ping(); err != nil { - return false, err - } - - // 连接成功,只返回结果,不打印日志 - return true, nil +} + +// saveMySQLResult 保存MySQL扫描结果 +func saveMySQLResult(info *Common.HostInfo, target string, credential MySQLCredential) { + successMsg := fmt.Sprintf("MySQL %s %v %v", target, credential.Username, credential.Password) + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "mysql", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + }, + } + Common.SaveResult(vulnResult) } diff --git a/Plugins/Oracle.go b/Plugins/Oracle.go index 50181e6..c5d6d15 100644 --- a/Plugins/Oracle.go +++ b/Plugins/Oracle.go @@ -1,119 +1,381 @@ package Plugins import ( + "context" "database/sql" "fmt" "github.com/shadow1ng/fscan/Common" _ "github.com/sijms/go-ora/v2" "strings" + "sync" "time" ) -func OracleScan(info *Common.HostInfo) (tmperr error) { +// OracleCredential 表示一个Oracle凭据 +type OracleCredential struct { + Username string + Password string +} + +// OracleScanResult 表示Oracle扫描结果 +type OracleScanResult struct { + Success bool + Error error + Credential OracleCredential + ServiceName string +} + +// 常见Oracle服务名列表 +var commonServiceNames = []string{"XE", "ORCL", "ORCLPDB1", "XEPDB1", "PDBORCL"} + +func OracleScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalUsers := len(Common.Userdict["oracle"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - tried := 0 - total := totalUsers * totalPass + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 遍历所有用户名密码组合 + // 构建常见高危凭据列表(优先测试) + highRiskCredentials := []OracleCredential{ + {Username: "SYS", Password: "123456"}, + {Username: "SYSTEM", Password: "123456"}, + {Username: "SYS", Password: "oracle"}, + {Username: "SYSTEM", Password: "oracle"}, + {Username: "SYS", Password: "password"}, + {Username: "SYSTEM", Password: "password"}, + {Username: "SYS", Password: "sys123"}, + {Username: "SYS", Password: "change_on_install"}, + {Username: "SYSTEM", Password: "manager"}, + } + + // 先尝试常见高危凭据 + Common.LogDebug("尝试常见高危凭据...") + for _, cred := range highRiskCredentials { + result := tryAllServiceNames(ctx, info, cred, Common.Timeout, 1) + if result != nil && result.Success { + saveOracleResult(info, target, result.Credential, result.ServiceName) + return nil + } + } + + // 构建完整凭据列表 + var credentials []OracleCredential for _, user := range Common.Userdict["oracle"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + // 转换用户名为大写,提高匹配率 + credentials = append(credentials, OracleCredential{ + Username: strings.ToUpper(user), + Password: actualPass, + }) + } + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["oracle"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentOracleScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveOracleResult(info, target, result.Credential, result.ServiceName) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("Oracle扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(highRiskCredentials))) + return nil + } +} + +// tryAllServiceNames 尝试所有常见服务名 +func tryAllServiceNames(ctx context.Context, info *Common.HostInfo, credential OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult { + for _, serviceName := range commonServiceNames { + result := tryOracleCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries) + if result.Success { + result.ServiceName = serviceName + return result + } + + // 对SYS用户尝试SYSDBA模式 + if strings.ToUpper(credential.Username) == "SYS" { + result = tryOracleSysCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries) + if result.Success { + result.ServiceName = serviceName + return result + } + } + } + return nil +} + +// concurrentOracleScan 并发扫描Oracle服务 +func concurrentOracleScan(ctx context.Context, info *Common.HostInfo, credentials []OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *OracleScanResult, 1) + workChan := make(chan OracleCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + // 尝试所有常见服务名 + result := tryAllServiceNames(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result != nil && result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } } + } + }() + } - // 执行Oracle连接 - done := make(chan struct { + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("Oracle并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryOracleCredential 尝试单个Oracle凭据 +func tryOracleCredential(ctx context.Context, info *Common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &OracleScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + + // 在协程中执行数据库连接 + resultChan := make(chan struct { + success bool + err error + }, 1) + + go func() { + success, err := OracleConn(connCtx, info, credential.Username, credential.Password, serviceName, false) + select { + case <-connCtx.Done(): + // 已超时或取消,不发送结果 + case resultChan <- struct { success bool err error - }, 1) + }{success, err}: + } + }() - go func(user, pass string) { - success, err := OracleConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) + // 等待结果或连接超时 + var success bool + var err error - // 等待结果或超时 - var err error - select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(successMsg) + select { + case result := <-resultChan: + success = result.success + err = result.err + case <-connCtx.Done(): + err = connCtx.Err() + } - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "oracle", - "username": user, - "password": pass, - "type": "weak-password", - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") + // 取消连接超时上下文 + cancel() + + if success { + return &OracleScanResult{ + Success: true, + Credential: credential, + ServiceName: serviceName, + } + } + + lastErr = err + if err != nil { + // 如果是认证错误,不需要重试 + if strings.Contains(err.Error(), "ORA-01017") { + break // 认证失败 } - // 处理错误情况 - if err != nil { - errMsg := fmt.Sprintf("Oracle %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err) - Common.LogError(errMsg) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue - } + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 } - break } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &OracleScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } +} + +// tryOracleSysCredential 尝试SYS用户SYSDBA模式连接 +func tryOracleSysCredential(ctx context.Context, info *Common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &OracleScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试SYS用户SYSDBA模式: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + + // 在协程中执行数据库连接 + resultChan := make(chan struct { + success bool + err error + }, 1) + + go func() { + success, err := OracleConn(connCtx, info, credential.Username, credential.Password, serviceName, true) + select { + case <-connCtx.Done(): + // 已超时或取消,不发送结果 + case resultChan <- struct { + success bool + err error + }{success, err}: + } + }() + + // 等待结果或连接超时 + var success bool + var err error + + select { + case result := <-resultChan: + success = result.success + err = result.err + case <-connCtx.Done(): + err = connCtx.Err() + } + + // 取消连接超时上下文 + cancel() + + if success { + return &OracleScanResult{ + Success: true, + Credential: credential, + ServiceName: serviceName, + } + } + + lastErr = err + if err != nil { + // 如果是认证错误,不需要重试 + if strings.Contains(err.Error(), "ORA-01017") { + break // 认证失败 + } + + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } + } + } + } + + return &OracleScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // OracleConn 尝试Oracle连接 -func OracleConn(info *Common.HostInfo, user string, pass string) (bool, error) { - host, port, username, password := info.Host, info.Ports, user, pass - timeout := time.Duration(Common.Timeout) * time.Second +func OracleConn(ctx context.Context, info *Common.HostInfo, user string, pass string, serviceName string, asSysdba bool) (bool, error) { + host, port := info.Host, info.Ports - // 构造连接字符串 - connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/orcl", - username, password, host, port) + // 构造连接字符串,添加更多参数 + connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/%s?connect_timeout=%d", + user, pass, host, port, serviceName, Common.Timeout) + + // 对SYS用户使用SYSDBA权限 + if asSysdba { + connStr += "&sysdba=1" + } // 建立数据库连接 db, err := sql.Open("oracle", connStr) @@ -123,14 +385,51 @@ func OracleConn(info *Common.HostInfo, user string, pass string) (bool, error) { defer db.Close() // 设置连接参数 - db.SetConnMaxLifetime(timeout) - db.SetConnMaxIdleTime(timeout) + db.SetConnMaxLifetime(time.Duration(Common.Timeout) * time.Second) + db.SetConnMaxIdleTime(time.Duration(Common.Timeout) * time.Second) db.SetMaxIdleConns(0) + db.SetMaxOpenConns(1) + + // 使用上下文测试连接 + pingCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second) + defer cancel() // 测试连接 - if err = db.Ping(); err != nil { + err = db.PingContext(pingCtx) + if err != nil { return false, err } + // 不需要额外的查询验证,连接成功即可 return true, nil } + +// saveOracleResult 保存Oracle扫描结果 +func saveOracleResult(info *Common.HostInfo, target string, credential OracleCredential, serviceName string) { + var successMsg string + if strings.ToUpper(credential.Username) == "SYS" { + successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s (可能需要SYSDBA权限)", + target, credential.Username, credential.Password, serviceName) + } else { + successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s", + target, credential.Username, credential.Password, serviceName) + } + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "oracle", + "username": credential.Username, + "password": credential.Password, + "service_name": serviceName, + "type": "weak-password", + }, + } + Common.SaveResult(vulnResult) +} diff --git a/Plugins/POP3.go b/Plugins/POP3.go index 45b6ce4..622c50b 100644 --- a/Plugins/POP3.go +++ b/Plugins/POP3.go @@ -2,152 +2,347 @@ package Plugins import ( "bufio" + "context" "crypto/tls" "fmt" "github.com/shadow1ng/fscan/Common" "net" "strings" + "sync" "time" ) -func POP3Scan(info *Common.HostInfo) (tmperr error) { +// POP3Credential 表示一个POP3凭据 +type POP3Credential struct { + Username string + Password string +} + +// POP3ScanResult 表示POP3扫描结果 +type POP3ScanResult struct { + Success bool + Error error + Credential POP3Credential + IsTLS bool +} + +func POP3Scan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalUsers := len(Common.Userdict["pop3"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - tried := 0 - total := totalUsers * totalPass + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 遍历所有用户名密码组合 + // 构建凭据列表 + var credentials []POP3Credential for _, user := range Common.Userdict["pop3"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, POP3Credential{ + Username: user, + Password: actualPass, + }) + } + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["pop3"]), len(Common.Passwords), len(credentials))) - done := make(chan struct { - success bool - err error - isTLS bool - }, 1) + // 使用工作池并发扫描,但需要限制速率 + result := concurrentPOP3Scan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + savePOP3Result(info, target, result) + return nil + } - go func(user, pass string) { - success, isTLS, err := POP3Conn(info, user, pass) - select { - case done <- struct { - success bool - err error - isTLS bool - }{success, err, isTLS}: - default: - } - }(user, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("POP3扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } +} - var err error +// concurrentPOP3Scan 并发扫描POP3服务(包含速率限制) +func concurrentPOP3Scan(ctx context.Context, info *Common.HostInfo, credentials []POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult { + // 不使用ModuleThreadNum控制并发数,必须单线程 + maxConcurrent := 1 + if maxConcurrent <= 0 { + maxConcurrent = 1 // POP3默认并发更低 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *POP3ScanResult, 1) + + // 创建限速通道,控制请求频率 + // 每次发送前需要从中获取令牌,确保请求间隔 + rateLimiter := make(chan struct{}, maxConcurrent) + + // 初始填充令牌 + for i := 0; i < maxConcurrent; i++ { + rateLimiter <- struct{}{} + } + + // 使用动态的请求间隔 + requestInterval := 1500 * time.Millisecond // 默认间隔1.5秒 + + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 创建任务队列 + taskQueue := make(chan POP3Credential, len(credentials)) + for _, cred := range credentials { + taskQueue <- cred + } + close(taskQueue) + + // 记录已处理的凭据数 + var processedCount int32 + processedCountMutex := &sync.Mutex{} + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func(workerID int) { + defer wg.Done() + + for credential := range taskQueue { select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v", target, user, pass) - if result.isTLS { - successMsg += " (TLS)" - } - Common.LogSuccess(successMsg) + case <-scanCtx.Done(): + return + case <-rateLimiter: + // 获取令牌,可以发送请求 + processedCountMutex.Lock() + processedCount++ + currentCount := processedCount + processedCountMutex.Unlock() - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "pop3", - "username": user, - "password": pass, - "type": "weak-password", - "tls": result.isTLS, - }, - } - Common.SaveResult(vulnResult) - return nil + Common.LogDebug(fmt.Sprintf("[%d/%d] 工作线程 %d 尝试: %s:%s", + currentCount, len(credentials), workerID, credential.Username, credential.Password)) + + result := tryPOP3Credential(scanCtx, info, credential, timeoutSeconds, maxRetries) + + // 尝试完成后添加延迟,然后归还令牌 + time.Sleep(requestInterval) + + // 未被取消的情况下归还令牌 + select { + case <-scanCtx.Done(): + // 如果已经取消,不再归还令牌 + default: + rateLimiter <- struct{}{} } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - if err != nil { - errMsg := fmt.Sprintf("POP3服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", - target, user, pass, err) - Common.LogError(errMsg) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - continue + return } } - break + } + }(i) + } + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("POP3并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryPOP3Credential 尝试单个POP3凭据 +func tryPOP3Credential(ctx context.Context, info *Common.HostInfo, credential POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &POP3ScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + // 重试间隔时间增加,避免触发服务器限制 + retryDelay := time.Duration(retry*2000) * time.Millisecond + time.Sleep(retryDelay) + } + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, isTLS, err := POP3Conn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &POP3ScanResult{ + Success: true, + Credential: credential, + IsTLS: isTLS, + } + } + + lastErr = err + if err != nil { + // 处理特定错误情况 + if strings.Contains(strings.ToLower(err.Error()), "too many connections") || + strings.Contains(strings.ToLower(err.Error()), "connection refused") || + strings.Contains(strings.ToLower(err.Error()), "timeout") { + // 服务器可能限制连接,增加等待时间 + waitTime := time.Duration((retry+1)*3000) * time.Millisecond + Common.LogDebug(fmt.Sprintf("服务器可能限制连接,等待 %v 后重试", waitTime)) + time.Sleep(waitTime) + continue + } + + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &POP3ScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } -func POP3Conn(info *Common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) { +// POP3Conn 尝试POP3连接 +func POP3Conn(ctx context.Context, info *Common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) { timeout := time.Duration(Common.Timeout) * time.Second addr := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 首先尝试普通连接 - conn, err := net.DialTimeout("tcp", addr, timeout) - if err == nil { - if flag, err := tryPOP3Auth(conn, user, pass, timeout); err == nil { - return flag, false, nil + // 创建结果通道 + resultChan := make(chan struct { + success bool + isTLS bool + err error + }, 1) + + // 在协程中尝试连接,支持取消 + go func() { + // 首先尝试普通连接 + dialer := &net.Dialer{ + Timeout: timeout, + // 增加KeepAlive设置,可能有助于处理一些服务器的限制 + KeepAlive: 30 * time.Second, + } + conn, err := dialer.DialContext(ctx, "tcp", addr) + if err == nil { + flag, authErr := tryPOP3Auth(conn, user, pass, timeout) + conn.Close() + if authErr == nil && flag { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + isTLS bool + err error + }{flag, false, nil}: + } + return + } } - conn.Close() - } - // 如果普通连接失败,尝试TLS连接 - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - } - conn, err = tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, tlsConfig) - if err != nil { - return false, false, fmt.Errorf("连接失败: %v", err) - } - defer conn.Close() + // 如果普通连接失败,尝试TLS连接 + select { + case <-ctx.Done(): + return + default: + } - success, err = tryPOP3Auth(conn, user, pass, timeout) - return success, true, err + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + } + tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig) + if tlsErr != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + isTLS bool + err error + }{false, false, fmt.Errorf("连接失败: %v", tlsErr)}: + } + return + } + defer tlsConn.Close() + + flag, authErr := tryPOP3Auth(tlsConn, user, pass, timeout) + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + isTLS bool + err error + }{flag, true, authErr}: + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.isTLS, result.err + case <-ctx.Done(): + return false, false, ctx.Err() + } } +// tryPOP3Auth 尝试POP3认证 func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) { reader := bufio.NewReader(conn) + + // 设置较长的超时时间以适应一些较慢的服务器 conn.SetDeadline(time.Now().Add(timeout)) // 读取欢迎信息 - _, err := reader.ReadString('\n') + response, err := reader.ReadString('\n') if err != nil { return false, fmt.Errorf("读取欢迎消息失败: %v", err) } + // 检查是否有错误信息 + if strings.Contains(strings.ToLower(response), "error") || + strings.Contains(strings.ToLower(response), "too many") { + return false, fmt.Errorf("服务器拒绝连接: %s", strings.TrimSpace(response)) + } + + // 发送用户名前等待一小段时间 + time.Sleep(300 * time.Millisecond) + // 发送用户名 conn.SetDeadline(time.Now().Add(timeout)) _, err = conn.Write([]byte(fmt.Sprintf("USER %s\r\n", user))) @@ -157,14 +352,17 @@ func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration) // 读取用户名响应 conn.SetDeadline(time.Now().Add(timeout)) - response, err := reader.ReadString('\n') + response, err = reader.ReadString('\n') if err != nil { return false, fmt.Errorf("读取用户名响应失败: %v", err) } if !strings.Contains(response, "+OK") { - return false, fmt.Errorf("用户名无效") + return false, fmt.Errorf("用户名无效: %s", strings.TrimSpace(response)) } + // 发送密码前等待一小段时间 + time.Sleep(300 * time.Millisecond) + // 发送密码 conn.SetDeadline(time.Now().Add(timeout)) _, err = conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", pass))) @@ -183,5 +381,34 @@ func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration) return true, nil } - return false, fmt.Errorf("认证失败") + return false, fmt.Errorf("认证失败: %s", strings.TrimSpace(response)) +} + +// savePOP3Result 保存POP3扫描结果 +func savePOP3Result(info *Common.HostInfo, target string, result *POP3ScanResult) { + tlsStatus := "" + if result.IsTLS { + tlsStatus = " (TLS)" + } + + successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v%s", + target, result.Credential.Username, result.Credential.Password, tlsStatus) + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "pop3", + "username": result.Credential.Username, + "password": result.Credential.Password, + "type": "weak-password", + "tls": result.IsTLS, + }, + } + Common.SaveResult(vulnResult) } diff --git a/Plugins/Postgres.go b/Plugins/Postgres.go index 2269ae9..44b2481 100644 --- a/Plugins/Postgres.go +++ b/Plugins/Postgres.go @@ -1,115 +1,205 @@ package Plugins import ( + "context" "database/sql" "fmt" _ "github.com/lib/pq" "github.com/shadow1ng/fscan/Common" "strings" + "sync" "time" ) +// PostgresCredential 表示一个PostgreSQL凭据 +type PostgresCredential struct { + Username string + Password string +} + +// PostgresScanResult 表示PostgreSQL扫描结果 +type PostgresScanResult struct { + Success bool + Error error + Credential PostgresCredential +} + // PostgresScan 执行PostgreSQL服务扫描 -func PostgresScan(info *Common.HostInfo) (tmperr error) { +func PostgresScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - maxRetries := Common.MaxRetries - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalUsers := len(Common.Userdict["postgresql"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - tried := 0 - total := totalUsers * totalPass + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + // 构建凭据列表 + var credentials []PostgresCredential for _, user := range Common.Userdict["postgresql"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, PostgresCredential{ + Username: user, + Password: actualPass, + }) + } + } - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["postgresql"]), len(Common.Passwords), len(credentials))) - done := make(chan struct { - success bool - err error - }, 1) + // 使用工作池并发扫描 + result := concurrentPostgresScan(ctx, info, credentials, Common.Timeout+10, Common.MaxRetries) + if result != nil { + // 记录成功结果 + savePostgresResult(info, target, result.Credential) + return nil + } - go func(user, pass string) { - success, err := PostgresConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("PostgreSQL扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } +} - var err error +// concurrentPostgresScan 并发扫描PostgreSQL服务 +func concurrentPostgresScan(ctx context.Context, info *Common.HostInfo, credentials []PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *PostgresScanResult, 1) + workChan := make(chan PostgresCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "postgresql", - "username": user, - "password": pass, - "type": "weak-password", - }, + case <-scanCtx.Done(): + return + default: + result := tryPostgresCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errMsg := fmt.Sprintf("PostgreSQL服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err) - Common.LogError(errMsg) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + return } } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("PostgreSQL并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryPostgresCredential 尝试单个PostgreSQL凭据 +func tryPostgresCredential(ctx context.Context, info *Common.HostInfo, credential PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &PostgresScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建单个连接超时的上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := PostgresConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &PostgresScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &PostgresScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // PostgresConn 尝试PostgreSQL连接 -func PostgresConn(info *Common.HostInfo, user string, pass string) (bool, error) { - timeout := time.Duration(Common.Timeout) * time.Second - +func PostgresConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) { // 构造连接字符串 connStr := fmt.Sprintf( - "postgres://%v:%v@%v:%v/postgres?sslmode=disable", - user, pass, info.Host, info.Ports, + "postgres://%v:%v@%v:%v/postgres?sslmode=disable&connect_timeout=%d", + user, pass, info.Host, info.Ports, Common.Timeout/1000, // 转换为秒 ) // 建立数据库连接 @@ -120,12 +210,45 @@ func PostgresConn(info *Common.HostInfo, user string, pass string) (bool, error) defer db.Close() // 设置连接参数 - db.SetConnMaxLifetime(timeout) + db.SetConnMaxLifetime(time.Duration(Common.Timeout) * time.Millisecond) + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(0) - // 测试连接 - if err = db.Ping(); err != nil { + // 使用上下文测试连接 + err = db.PingContext(ctx) + if err != nil { + return false, err + } + + // 简单查询测试权限 + var version string + err = db.QueryRowContext(ctx, "SELECT version()").Scan(&version) + if err != nil { return false, err } return true, nil } + +// savePostgresResult 保存PostgreSQL扫描结果 +func savePostgresResult(info *Common.HostInfo, target string, credential PostgresCredential) { + successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v", + target, credential.Username, credential.Password) + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "postgresql", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + }, + } + Common.SaveResult(vulnResult) +} diff --git a/Plugins/RDP.go b/Plugins/RDP.go index f1da6f6..caeceda 100644 --- a/Plugins/RDP.go +++ b/Plugins/RDP.go @@ -1,6 +1,7 @@ package Plugins import ( + "context" "errors" "fmt" "github.com/shadow1ng/fscan/Common" @@ -22,84 +23,200 @@ import ( "time" ) -// Brutelist 表示暴力破解的用户名密码组合 -type Brutelist struct { - user string - pass string +// RDPCredential 表示一个RDP凭据 +type RDPCredential struct { + Username string + Password string + Domain string +} + +// RDPScanResult 表示RDP扫描结果 +type RDPScanResult struct { + Success bool + Error error + Credential RDPCredential } // RdpScan 执行RDP服务扫描 -func RdpScan(info *Common.HostInfo) (tmperr error) { +func RdpScan(info *Common.HostInfo) error { defer func() { - recover() + if r := recover(); r != nil { + Common.LogError(fmt.Sprintf("RDP扫描panic: %v", r)) + } }() + if Common.DisableBrute { - return + return nil } port, _ := strconv.Atoi(info.Ports) - total := len(Common.Userdict["rdp"]) * len(Common.Passwords) - num := 0 target := fmt.Sprintf("%v:%v", info.Host, port) + Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - // 遍历用户名密码组合 + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 构建凭据列表 + var credentials []RDPCredential for _, user := range Common.Userdict["rdp"] { for _, pass := range Common.Passwords { - num++ - pass = strings.Replace(pass, "{user}", user, -1) - - // 尝试连接 - flag, err := RdpConn(info.Host, Common.Domain, user, pass, port, Common.Timeout) - - if flag && err == nil { - // 连接成功 - var result string - if Common.Domain != "" { - result = fmt.Sprintf("RDP %v Domain: %v\\%v Password: %v", target, Common.Domain, user, pass) - } else { - result = fmt.Sprintf("RDP %v Username: %v Password: %v", target, user, pass) - } - Common.LogSuccess(result) - - // 保存结果 - details := map[string]interface{}{ - "port": port, - "service": "rdp", - "username": user, - "password": pass, - "type": "weak-password", - } - if Common.Domain != "" { - details["domain"] = Common.Domain - } - - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - Common.SaveResult(vulnResult) - - return nil - } - - // 连接失败 - errlog := fmt.Sprintf("(%v/%v) RDP %v Username: %v Password: %v Error: %v", - num, total, target, user, pass, err) - Common.LogError(errlog) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, RDPCredential{ + Username: user, + Password: actualPass, + Domain: Common.Domain, + }) } } - return tmperr + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["rdp"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentRdpScan(ctx, info, credentials, port, Common.Timeout) + if result != nil { + // 记录成功结果 + saveRdpResult(info, target, port, result.Credential) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("RDP扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } +} + +// concurrentRdpScan 并发扫描RDP服务 +func concurrentRdpScan(ctx context.Context, info *Common.HostInfo, credentials []RDPCredential, port int, timeoutSeconds int64) *RDPScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *RDPScanResult, 1) + workChan := make(chan RDPCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := tryRdpCredential(scanCtx, info.Host, credential, port, timeoutSeconds) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("RDP并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryRdpCredential 尝试单个RDP凭据 +func tryRdpCredential(ctx context.Context, host string, credential RDPCredential, port int, timeoutSeconds int64) *RDPScanResult { + // 创建结果通道 + resultChan := make(chan *RDPScanResult, 1) + + // 在协程中进行连接尝试 + go func() { + success, err := RdpConn(host, credential.Domain, credential.Username, credential.Password, port, timeoutSeconds) + + select { + case <-ctx.Done(): + // 上下文已取消,不返回结果 + case resultChan <- &RDPScanResult{ + Success: success, + Error: err, + Credential: credential, + }: + // 成功发送结果 + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result + case <-ctx.Done(): + return &RDPScanResult{ + Success: false, + Error: ctx.Err(), + Credential: credential, + } + case <-time.After(time.Duration(timeoutSeconds) * time.Second): + // 单个连接超时 + return &RDPScanResult{ + Success: false, + Error: fmt.Errorf("连接超时"), + Credential: credential, + } + } } // RdpConn 尝试RDP连接 func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool, error) { defer func() { - recover() + if r := recover(); r != nil { + glog.Error("RDP连接panic:", r) + } }() + target := fmt.Sprintf("%s:%d", ip, port) // 创建RDP客户端 @@ -111,6 +228,43 @@ func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool, return true, nil } +// saveRdpResult 保存RDP扫描结果 +func saveRdpResult(info *Common.HostInfo, target string, port int, credential RDPCredential) { + var successMsg string + + if credential.Domain != "" { + successMsg = fmt.Sprintf("RDP %v Domain: %v\\%v Password: %v", + target, credential.Domain, credential.Username, credential.Password) + } else { + successMsg = fmt.Sprintf("RDP %v Username: %v Password: %v", + target, credential.Username, credential.Password) + } + + Common.LogSuccess(successMsg) + + // 保存结果 + details := map[string]interface{}{ + "port": port, + "service": "rdp", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + } + + if credential.Domain != "" { + details["domain"] = credential.Domain + } + + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(vulnResult) +} + // Client RDP客户端结构 type Client struct { Host string // 服务地址(ip:port) @@ -161,8 +315,25 @@ func (g *Client) Login(domain, user, pwd string, timeout int64) error { // 设置事件处理器 g.setupEventHandlers(wg, &breakFlag, &err) - wg.Wait() - return err + // 添加额外的超时保护 + connectionDone := make(chan struct{}) + go func() { + wg.Wait() + close(connectionDone) + }() + + select { + case <-connectionDone: + // 连接过程正常完成 + return err + case <-time.After(time.Duration(timeout) * time.Second): + // 超时 + if !breakFlag { + breakFlag = true + wg.Done() + } + return fmt.Errorf("连接超时") + } } // initProtocolStack 初始化RDP协议栈 diff --git a/Plugins/RabbitMQ.go b/Plugins/RabbitMQ.go index 4a71843..efc674b 100644 --- a/Plugins/RabbitMQ.go +++ b/Plugins/RabbitMQ.go @@ -1,205 +1,308 @@ package Plugins import ( + "context" "fmt" amqp "github.com/rabbitmq/amqp091-go" "github.com/shadow1ng/fscan/Common" "net" "strings" + "sync" "time" ) +// RabbitMQCredential 表示一个RabbitMQ凭据 +type RabbitMQCredential struct { + Username string + Password string +} + +// RabbitMQScanResult 表示扫描结果 +type RabbitMQScanResult struct { + Success bool + Error error + Credential RabbitMQCredential + ErrorMsg string // 保存详细的错误信息 +} + // RabbitMQScan 执行 RabbitMQ 服务扫描 -func RabbitMQScan(info *Common.HostInfo) (tmperr error) { +func RabbitMQScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - Common.LogDebug("尝试默认账号 guest/guest") + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() // 先测试默认账号 guest/guest - user, pass := "guest", "guest" - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试默认账号: guest/guest", retryCount+1)) - } + Common.LogDebug("尝试默认账号 guest/guest") + defaultCredential := RabbitMQCredential{Username: "guest", Password: "guest"} + defaultResult := tryRabbitMQCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries) - done := make(chan struct { - success bool - err error - }, 1) - - go func() { - success, err := RabbitMQConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }() - - var err error - select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "rabbitmq", - "username": user, - "password": pass, - "type": "weak-password", - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errlog := fmt.Sprintf("RabbitMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", - target, user, pass, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue - } - } - break + if defaultResult.Success { + saveRabbitMQResult(info, target, defaultResult.Credential) + return nil + } else if defaultResult.Error != nil { + // 打印默认账号的详细错误信息 + Common.LogDebug(fmt.Sprintf("默认账号 guest/guest 失败,详细错误: %s", defaultResult.ErrorMsg)) } - totalUsers := len(Common.Userdict["rabbitmq"]) - totalPass := len(Common.Passwords) - total := totalUsers * totalPass - tried := 0 - - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - - // 遍历其他用户名密码组合 + // 构建其他凭据列表 + var credentials []RabbitMQCredential for _, user := range Common.Userdict["rabbitmq"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, RabbitMQCredential{ + Username: user, + Password: actualPass, + }) + } + } - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["rabbitmq"]), len(Common.Passwords), len(credentials))) - done := make(chan struct { - success bool - err error - }, 1) + // 使用工作池并发扫描 + result := concurrentRabbitMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveRabbitMQResult(info, target, result.Credential) + return nil + } - go func(user, pass string) { - success, err := RabbitMQConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{success, err}: - default: - } - }(user, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("RabbitMQ扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认账号 + return nil + } +} - var err error +// concurrentRabbitMQScan 并发扫描RabbitMQ服务 +func concurrentRabbitMQScan(ctx context.Context, info *Common.HostInfo, credentials []RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *RabbitMQScanResult, 1) + workChan := make(chan RabbitMQCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v", - target, user, pass) - Common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "rabbitmq", - "username": user, - "password": pass, - "type": "weak-password", - }, + case <-scanCtx.Done(): + return + default: + result := tryRabbitMQCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errlog := fmt.Sprintf("RabbitMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", - target, user, pass, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + return } } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("RabbitMQ并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryRabbitMQCredential 尝试单个RabbitMQ凭据 +func tryRabbitMQCredential(ctx context.Context, info *Common.HostInfo, credential RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult { + var lastErr error + var errorMsg string + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &RabbitMQScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + ErrorMsg: "全局超时", + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err, detailErr := RabbitMQConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + return &RabbitMQScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err + errorMsg = detailErr + + // 打印详细的错误信息,包括所有原始错误信息 + Common.LogDebug(fmt.Sprintf("凭据 %s:%s 失败,错误详情: %s", + credential.Username, credential.Password, errorMsg)) + + if err != nil { + // 可以根据错误信息类型来决定是否需要重试 + // 例如,如果错误是认证错误,则无需重试 + if strings.Contains(errorMsg, "ACCESS_REFUSED") { + Common.LogDebug("认证错误,无需重试") + break + } + + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried+1)) - return tmperr + return &RabbitMQScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + ErrorMsg: errorMsg, + } } // RabbitMQConn 尝试 RabbitMQ 连接 -func RabbitMQConn(info *Common.HostInfo, user string, pass string) (bool, error) { +func RabbitMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error, string) { host, port := info.Host, info.Ports - timeout := time.Duration(Common.Timeout) * time.Second // 构造 AMQP URL amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%s/", user, pass, host, port) - // 配置连接 - config := amqp.Config{ - Dial: func(network, addr string) (net.Conn, error) { - return net.DialTimeout(network, addr, timeout) + // 创建结果通道 + resultChan := make(chan struct { + success bool + err error + detailErr string + }, 1) + + // 在协程中尝试连接 + go func() { + // 配置连接 + config := amqp.Config{ + Dial: func(network, addr string) (net.Conn, error) { + dialer := &net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second} + return dialer.DialContext(ctx, network, addr) + }, + } + + // 尝试连接 + conn, err := amqp.DialConfig(amqpURL, config) + + if err != nil { + detailErr := err.Error() + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + detailErr string + }{false, err, detailErr}: + } + return + } + defer conn.Close() + + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + detailErr string + }{true, nil, ""}: + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.err, result.detailErr + case <-ctx.Done(): + return false, ctx.Err(), ctx.Err().Error() + } +} + +// saveRabbitMQResult 保存RabbitMQ扫描结果 +func saveRabbitMQResult(info *Common.HostInfo, target string, credential RabbitMQCredential) { + successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v", + target, credential.Username, credential.Password) + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "rabbitmq", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", }, } - - // 尝试连接 - conn, err := amqp.DialConfig(amqpURL, config) - if err != nil { - return false, err - } - defer conn.Close() - - // 如果成功连接 - if conn != nil { - return true, nil - } - - return false, fmt.Errorf("认证失败") + Common.SaveResult(vulnResult) } diff --git a/Plugins/Rsync.go b/Plugins/Rsync.go index e77dac3..8917769 100644 --- a/Plugins/Rsync.go +++ b/Plugins/Rsync.go @@ -1,275 +1,483 @@ package Plugins import ( + "context" "fmt" "github.com/shadow1ng/fscan/Common" "net" "strings" + "sync" "time" ) -func RsyncScan(info *Common.HostInfo) (tmperr error) { - if Common.DisableBrute { - return - } - - maxRetries := Common.MaxRetries - target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - Common.LogDebug("尝试匿名访问...") - - // 首先测试匿名访问 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试匿名访问", retryCount+1)) - } - - flag, err := RsyncConn(info, "", "") - if flag && err == nil { - Common.LogSuccess(fmt.Sprintf("Rsync服务 %s 匿名访问成功", target)) - - // 保存匿名访问结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "rsync", - "type": "anonymous-access", - }, - } - Common.SaveResult(result) - return err - } - - if err != nil { - Common.LogError(fmt.Sprintf("Rsync服务 %s 匿名访问失败: %v", target, err)) - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - return err - } - continue - } - } - break - } - - totalUsers := len(Common.Userdict["rsync"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - - tried := 0 - total := totalUsers * totalPass - - for _, user := range Common.Userdict["rsync"] { - for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) - - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) - } - - done := make(chan struct { - success bool - err error - }, 1) - - go func(user, pass string) { - flag, err := RsyncConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{flag && err == nil, err}: - default: - } - }(user, pass) - - var err error - select { - case result := <-done: - err = result.err - if result.success { - Common.LogSuccess(fmt.Sprintf("Rsync服务 %s 爆破成功 用户名: %v 密码: %v", - target, user, pass)) - - // 保存爆破成功结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "rsync", - "type": "weak-password", - "username": user, - "password": pass, - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") - } - - if err != nil { - Common.LogError(fmt.Sprintf("Rsync服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", - target, user, pass, err)) - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue - } - } - break - } - } - } - - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr +// RsyncCredential 表示一个Rsync凭据 +type RsyncCredential struct { + Username string + Password string } -func RsyncConn(info *Common.HostInfo, user string, pass string) (bool, error) { +// RsyncScanResult 表示Rsync扫描结果 +type RsyncScanResult struct { + Success bool + Error error + Credential RsyncCredential + IsAnonymous bool + ModuleName string +} + +func RsyncScan(info *Common.HostInfo) error { + if Common.DisableBrute { + return nil + } + + target := fmt.Sprintf("%v:%v", info.Host, info.Ports) + Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 首先尝试匿名访问 + Common.LogDebug("尝试匿名访问...") + anonymousResult := tryRsyncCredential(ctx, info, RsyncCredential{"", ""}, Common.Timeout, Common.MaxRetries) + + if anonymousResult.Success { + // 匿名访问成功 + saveRsyncResult(info, target, anonymousResult) + return nil + } + + // 构建凭据列表 + var credentials []RsyncCredential + for _, user := range Common.Userdict["rsync"] { + for _, pass := range Common.Passwords { + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, RsyncCredential{ + Username: user, + Password: actualPass, + }) + } + } + + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["rsync"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentRsyncScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 保存成功结果 + saveRsyncResult(info, target, result) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("Rsync扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 + return nil + } +} + +// concurrentRsyncScan 并发扫描Rsync服务 +func concurrentRsyncScan(ctx context.Context, info *Common.HostInfo, credentials []RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *RsyncScanResult, 1) + workChan := make(chan RsyncCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := tryRsyncCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("Rsync并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryRsyncCredential 尝试单个Rsync凭据 +func tryRsyncCredential(ctx context.Context, info *Common.HostInfo, credential RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &RsyncScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, moduleName, err := RsyncConn(connCtx, info, credential.Username, credential.Password) + cancel() + + if success { + isAnonymous := credential.Username == "" && credential.Password == "" + return &RsyncScanResult{ + Success: true, + Credential: credential, + IsAnonymous: isAnonymous, + ModuleName: moduleName, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } + } + } + } + + return &RsyncScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } +} + +// RsyncConn 尝试Rsync连接 +func RsyncConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, string, error) { host, port := info.Host, info.Ports timeout := time.Duration(Common.Timeout) * time.Second + // 设置带有上下文的拨号器 + dialer := &net.Dialer{ + Timeout: timeout, + } + // 建立连接 - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout) + conn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", host, port)) if err != nil { - return false, err + return false, "", err } defer conn.Close() - buffer := make([]byte, 1024) + // 创建结果通道用于超时控制 + resultChan := make(chan struct { + success bool + moduleName string + err error + }, 1) - // 1. 读取服务器初始greeting - n, err := conn.Read(buffer) - if err != nil { - return false, err - } + // 在协程中处理连接,以支持上下文取消 + go func() { + buffer := make([]byte, 1024) - greeting := string(buffer[:n]) - if !strings.HasPrefix(greeting, "@RSYNCD:") { - return false, fmt.Errorf("不是Rsync服务") - } - - // 获取服务器版本号 - version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:")) - - // 2. 回应相同的版本号 - _, err = conn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version))) - if err != nil { - return false, err - } - - // 3. 选择模块 - 先列出可用模块 - _, err = conn.Write([]byte("#list\n")) - if err != nil { - return false, err - } - - // 4. 读取模块列表 - var moduleList strings.Builder - for { - n, err = conn.Read(buffer) + // 1. 读取服务器初始greeting + conn.SetReadDeadline(time.Now().Add(timeout)) + n, err := conn.Read(buffer) if err != nil { - break - } - chunk := string(buffer[:n]) - moduleList.WriteString(chunk) - if strings.Contains(chunk, "@RSYNCD: EXIT") { - break - } - } - - modules := strings.Split(moduleList.String(), "\n") - for _, module := range modules { - if strings.HasPrefix(module, "@RSYNCD") || module == "" { - continue - } - - // 获取模块名 - moduleName := strings.Fields(module)[0] - - // 5. 为每个模块创建新连接尝试认证 - authConn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout) - if err != nil { - continue - } - defer authConn.Close() - - // 重复初始握手 - _, err = authConn.Read(buffer) - if err != nil { - authConn.Close() - continue - } - - _, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version))) - if err != nil { - authConn.Close() - continue - } - - // 6. 选择模块 - _, err = authConn.Write([]byte(moduleName + "\n")) - if err != nil { - authConn.Close() - continue - } - - // 7. 等待认证挑战 - n, err = authConn.Read(buffer) - if err != nil { - authConn.Close() - continue - } - - authResponse := string(buffer[:n]) - if strings.Contains(authResponse, "@RSYNCD: OK") { - // 模块不需要认证 - if user == "" && pass == "" { - result := fmt.Sprintf("Rsync服务 %v:%v 模块:%v 无需认证", host, port, moduleName) - Common.LogSuccess(result) - return true, nil + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + moduleName string + err error + }{false, "", err}: } - } else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") { - if user != "" && pass != "" { - // 8. 发送认证信息 - authString := fmt.Sprintf("%s %s\n", user, pass) - _, err = authConn.Write([]byte(authString)) - if err != nil { - authConn.Close() - continue - } + return + } - // 9. 读取认证结果 - n, err = authConn.Read(buffer) - if err != nil { - authConn.Close() - continue - } + greeting := string(buffer[:n]) + if !strings.HasPrefix(greeting, "@RSYNCD:") { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + moduleName string + err error + }{false, "", fmt.Errorf("不是Rsync服务")}: + } + return + } - if !strings.Contains(string(buffer[:n]), "@ERROR") { - result := fmt.Sprintf("Rsync服务 %v:%v 模块:%v 认证成功 用户名: %v 密码: %v", - host, port, moduleName, user, pass) - Common.LogSuccess(result) - return true, nil - } + // 获取服务器版本号 + version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:")) + + // 2. 回应相同的版本号 + conn.SetWriteDeadline(time.Now().Add(timeout)) + _, err = conn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version))) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + moduleName string + err error + }{false, "", err}: + } + return + } + + // 3. 选择模块 - 先列出可用模块 + conn.SetWriteDeadline(time.Now().Add(timeout)) + _, err = conn.Write([]byte("#list\n")) + if err != nil { + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + moduleName string + err error + }{false, "", err}: + } + return + } + + // 4. 读取模块列表 + var moduleList strings.Builder + for { + // 检查上下文是否取消 + select { + case <-ctx.Done(): + return + default: + } + + conn.SetReadDeadline(time.Now().Add(timeout)) + n, err = conn.Read(buffer) + if err != nil { + break + } + chunk := string(buffer[:n]) + moduleList.WriteString(chunk) + if strings.Contains(chunk, "@RSYNCD: EXIT") { + break } } - authConn.Close() - } - return false, fmt.Errorf("认证失败或无可用模块") + modules := strings.Split(moduleList.String(), "\n") + for _, module := range modules { + if strings.HasPrefix(module, "@RSYNCD") || module == "" { + continue + } + + // 获取模块名 + moduleName := strings.Fields(module)[0] + + // 检查上下文是否取消 + select { + case <-ctx.Done(): + return + default: + } + + // 5. 为每个模块创建新连接尝试认证 + authConn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", host, port)) + if err != nil { + continue + } + defer authConn.Close() + + // 重复初始握手 + authConn.SetReadDeadline(time.Now().Add(timeout)) + _, err = authConn.Read(buffer) + if err != nil { + authConn.Close() + continue + } + + authConn.SetWriteDeadline(time.Now().Add(timeout)) + _, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version))) + if err != nil { + authConn.Close() + continue + } + + // 6. 选择模块 + authConn.SetWriteDeadline(time.Now().Add(timeout)) + _, err = authConn.Write([]byte(moduleName + "\n")) + if err != nil { + authConn.Close() + continue + } + + // 7. 等待认证挑战 + authConn.SetReadDeadline(time.Now().Add(timeout)) + n, err = authConn.Read(buffer) + if err != nil { + authConn.Close() + continue + } + + authResponse := string(buffer[:n]) + if strings.Contains(authResponse, "@RSYNCD: OK") { + // 模块不需要认证 + if user == "" && pass == "" { + authConn.Close() + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + moduleName string + err error + }{true, moduleName, nil}: + } + return + } + } else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") { + if user != "" && pass != "" { + // 8. 发送认证信息 + authString := fmt.Sprintf("%s %s\n", user, pass) + authConn.SetWriteDeadline(time.Now().Add(timeout)) + _, err = authConn.Write([]byte(authString)) + if err != nil { + authConn.Close() + continue + } + + // 9. 读取认证结果 + authConn.SetReadDeadline(time.Now().Add(timeout)) + n, err = authConn.Read(buffer) + if err != nil { + authConn.Close() + continue + } + + if !strings.Contains(string(buffer[:n]), "@ERROR") { + authConn.Close() + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + moduleName string + err error + }{true, moduleName, nil}: + } + return + } + } + } + authConn.Close() + } + + // 如果执行到这里,没有找到成功的认证 + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + moduleName string + err error + }{false, "", fmt.Errorf("认证失败或无可用模块")}: + } + }() + + // 等待结果或上下文取消 + select { + case result := <-resultChan: + return result.success, result.moduleName, result.err + case <-ctx.Done(): + return false, "", ctx.Err() + } +} + +// saveRsyncResult 保存Rsync扫描结果 +func saveRsyncResult(info *Common.HostInfo, target string, result *RsyncScanResult) { + var successMsg string + var details map[string]interface{} + + if result.IsAnonymous { + successMsg = fmt.Sprintf("Rsync服务 %s 匿名访问成功 模块: %s", target, result.ModuleName) + details = map[string]interface{}{ + "port": info.Ports, + "service": "rsync", + "type": "anonymous-access", + "module": result.ModuleName, + } + } else { + successMsg = fmt.Sprintf("Rsync服务 %s 爆破成功 用户名: %v 密码: %v 模块: %s", + target, result.Credential.Username, result.Credential.Password, result.ModuleName) + details = map[string]interface{}{ + "port": info.Ports, + "service": "rsync", + "type": "weak-password", + "username": result.Credential.Username, + "password": result.Credential.Password, + "module": result.ModuleName, + } + } + + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(vulnResult) } diff --git a/Plugins/SMB.go b/Plugins/SMB.go index 64f62e7..a49d7f5 100644 --- a/Plugins/SMB.go +++ b/Plugins/SMB.go @@ -1,78 +1,258 @@ package Plugins import ( + "context" "fmt" "github.com/shadow1ng/fscan/Common" "github.com/stacktitan/smb/smb" "strings" + "sync" "time" ) -func SmbScan(info *Common.HostInfo) (tmperr error) { +// SmbCredential 表示一个SMB凭据 +type SmbCredential struct { + Username string + Password string +} + +// SmbScanResult 表示SMB扫描结果 +type SmbScanResult struct { + Success bool + Error error + Credential SmbCredential +} + +func SmbScan(info *Common.HostInfo) error { if Common.DisableBrute { return nil } target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - // 遍历所有用户 + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 构建凭据列表 + var credentials []SmbCredential for _, user := range Common.Userdict["smb"] { - // 遍历该用户的所有密码 for _, pass := range Common.Passwords { - pass = strings.Replace(pass, "{user}", user, -1) - - success, err := doWithTimeOut(info, user, pass) - if success { - // 构建结果消息 - var successMsg string - details := map[string]interface{}{ - "port": info.Ports, - "service": "smb", - "username": user, - "password": pass, - "type": "weak-password", - } - - if Common.Domain != "" { - successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, Common.Domain, user, pass) - details["domain"] = Common.Domain - } else { - successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, user, pass) - } - - // 记录成功日志 - Common.LogSuccess(successMsg) - - // 保存结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - Common.SaveResult(result) - return nil - } - - if err != nil { - errMsg := fmt.Sprintf("SMB认证失败 %s %s:%s %v", target, user, pass, err) - Common.LogError(errMsg) - - // 等待失败日志打印完成 - time.Sleep(100 * time.Millisecond) - - if strings.Contains(err.Error(), "账号锁定") { - // 账号锁定时跳过当前用户的剩余密码 - break // 跳出密码循环,继续下一个用户 - } - } + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, SmbCredential{ + Username: user, + Password: actualPass, + }) } } - return nil + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["smb"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentSmbScan(ctx, info, credentials, Common.Timeout) + if result != nil { + // 记录成功结果 + saveSmbResult(info, target, result.Credential) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("SMB扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } } +// concurrentSmbScan 并发扫描SMB服务 +func concurrentSmbScan(ctx context.Context, info *Common.HostInfo, credentials []SmbCredential, timeoutSeconds int64) *SmbScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *SmbScanResult, 1) + workChan := make(chan SmbCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 记录用户锁定状态,避免继续尝试已锁定的用户 + lockedUsers := make(map[string]bool) + var lockedMutex sync.Mutex + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + // 检查用户是否已锁定 + lockedMutex.Lock() + locked := lockedUsers[credential.Username] + lockedMutex.Unlock() + if locked { + Common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username)) + continue + } + + result := trySmbCredential(scanCtx, info, credential, timeoutSeconds) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } + + // 检查账号锁定错误 + if result.Error != nil && strings.Contains(result.Error.Error(), "账号锁定") { + lockedMutex.Lock() + lockedUsers[credential.Username] = true + lockedMutex.Unlock() + Common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username)) + } + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + // 检查用户是否已锁定 + lockedMutex.Lock() + locked := lockedUsers[cred.Username] + lockedMutex.Unlock() + if locked { + continue // 跳过已锁定用户 + } + + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("SMB并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// trySmbCredential 尝试单个SMB凭据 +func trySmbCredential(ctx context.Context, info *Common.HostInfo, credential SmbCredential, timeoutSeconds int64) *SmbScanResult { + // 创建单个连接超时上下文的结果通道 + resultChan := make(chan struct { + success bool + err error + }, 1) + + // 在协程中尝试连接 + go func() { + signal := make(chan struct{}, 1) + success, err := SmblConn(info, credential.Username, credential.Password, signal) + + select { + case <-ctx.Done(): + case resultChan <- struct { + success bool + err error + }{success, err}: + } + }() + + // 等待结果或超时 + select { + case result := <-resultChan: + return &SmbScanResult{ + Success: result.success, + Error: result.err, + Credential: credential, + } + case <-ctx.Done(): + return &SmbScanResult{ + Success: false, + Error: ctx.Err(), + Credential: credential, + } + case <-time.After(time.Duration(timeoutSeconds) * time.Second): + return &SmbScanResult{ + Success: false, + Error: fmt.Errorf("连接超时"), + Credential: credential, + } + } +} + +// saveSmbResult 保存SMB扫描结果 +func saveSmbResult(info *Common.HostInfo, target string, credential SmbCredential) { + // 构建结果消息 + var successMsg string + details := map[string]interface{}{ + "port": info.Ports, + "service": "smb", + "username": credential.Username, + "password": credential.Password, + "type": "weak-password", + } + + if Common.Domain != "" { + successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, Common.Domain, credential.Username, credential.Password) + details["domain"] = Common.Domain + } else { + successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, credential.Username, credential.Password) + } + + // 记录成功日志 + Common.LogSuccess(successMsg) + + // 保存结果 + result := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(result) +} + +// SmblConn 尝试建立SMB连接并认证 func SmblConn(info *Common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) { options := smb.Options{ Host: info.Host, @@ -116,34 +296,3 @@ func SmblConn(info *Common.HostInfo, user string, pass string, signal chan struc signal <- struct{}{} return false, err } - -func doWithTimeOut(info *Common.HostInfo, user string, pass string) (flag bool, err error) { - signal := make(chan struct{}, 1) - result := make(chan struct { - success bool - err error - }, 1) - - go func() { - success, err := SmblConn(info, user, pass, signal) - select { - case result <- struct { - success bool - err error - }{success, err}: - default: - } - }() - - select { - case r := <-result: - return r.success, r.err - case <-time.After(time.Duration(Common.Timeout) * time.Second): - select { - case r := <-result: - return r.success, r.err - default: - return false, fmt.Errorf("连接超时") - } - } -} diff --git a/Plugins/SMB2.go b/Plugins/SMB2.go index 5873e99..dc18eb2 100644 --- a/Plugins/SMB2.go +++ b/Plugins/SMB2.go @@ -1,146 +1,397 @@ package Plugins import ( + "context" "fmt" "github.com/shadow1ng/fscan/Common" "net" "os" "strings" + "sync" "time" "github.com/hirochachacha/go-smb2" ) +// Smb2Credential 表示一个SMB2凭据 +type Smb2Credential struct { + Username string + Password string + Hash []byte + IsHash bool +} + +// Smb2ScanResult 表示SMB2扫描结果 +type Smb2ScanResult struct { + Success bool + Error error + Credential Smb2Credential + Shares []string +} + // SmbScan2 执行SMB2服务的认证扫描,支持密码和哈希两种认证方式 -func SmbScan2(info *Common.HostInfo) (tmperr error) { +func SmbScan2(info *Common.HostInfo) error { if Common.DisableBrute { return nil } - // 使用哈希认证模式 + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + Common.LogDebug(fmt.Sprintf("开始SMB2扫描 %s", target)) + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() + + // 根据是否提供哈希选择认证模式 if len(Common.HashBytes) > 0 { - return smbHashScan(info) + return smbHashScan(ctx, info) } - // 使用密码认证模式 - return smbPasswordScan(info) + return smbPasswordScan(ctx, info) } -func smbPasswordScan(info *Common.HostInfo) error { +// smbPasswordScan 使用密码进行SMB2认证扫描 +func smbPasswordScan(ctx context.Context, info *Common.HostInfo) error { if Common.DisableBrute { return nil } - hasprint := false - - // 遍历每个用户 + // 构建凭据列表 + var credentials []Smb2Credential for _, user := range Common.Userdict["smb"] { - accountLocked := false // 添加账户锁定标志 - - // 遍历该用户的所有密码 for _, pass := range Common.Passwords { - if accountLocked { // 如果账户被锁定,跳过剩余密码 - break - } - - pass = strings.ReplaceAll(pass, "{user}", user) - - // 重试循环 - for retryCount := 0; retryCount < Common.MaxRetries; retryCount++ { - success, err, printed := Smb2Con(info, user, pass, []byte{}, hasprint) - - if printed { - hasprint = true - } - - if success { - logSuccessfulAuth(info, user, pass, []byte{}) - return nil - } - - if err != nil { - logFailedAuth(info, user, pass, []byte{}, err) - - // 检查是否账户锁定 - if strings.Contains(err.Error(), "account has been automatically locked") || - strings.Contains(err.Error(), "account has been locked") { - accountLocked = true // 设置锁定标志 - break - } - - // 其他登录失败情况 - if strings.Contains(err.Error(), "LOGIN_FAILED") || - strings.Contains(err.Error(), "Authentication failed") || - strings.Contains(err.Error(), "attempted logon is invalid") || - strings.Contains(err.Error(), "bad username or authentication") { - break - } - - if retryCount < Common.MaxRetries-1 { - time.Sleep(time.Second * time.Duration(retryCount+2)) - continue - } - } - break - } + actualPass := strings.ReplaceAll(pass, "{user}", user) + credentials = append(credentials, Smb2Credential{ + Username: user, + Password: actualPass, + Hash: []byte{}, + IsHash: false, + }) } } - return nil + Common.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["smb"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + return concurrentSmb2Scan(ctx, info, credentials) } -func smbHashScan(info *Common.HostInfo) error { +// smbHashScan 使用哈希进行SMB2认证扫描 +func smbHashScan(ctx context.Context, info *Common.HostInfo) error { if Common.DisableBrute { return nil } - hasprint := false - - // 遍历每个用户 + // 构建凭据列表 + var credentials []Smb2Credential for _, user := range Common.Userdict["smb"] { - // 遍历该用户的所有hash for _, hash := range Common.HashBytes { - // 重试循环 - for retryCount := 0; retryCount < Common.MaxRetries; retryCount++ { - success, err, printed := Smb2Con(info, user, "", hash, hasprint) - - if printed { - hasprint = true - } - - if success { - logSuccessfulAuth(info, user, "", hash) - return nil - } - - if err != nil { - logFailedAuth(info, user, "", hash, err) - - // 检查是否账户锁定 - if strings.Contains(err.Error(), "user account has been automatically locked") { - // 账户锁定,跳过该用户的剩余hash - break - } - - // 其他登录失败情况 - if strings.Contains(err.Error(), "LOGIN_FAILED") || - strings.Contains(err.Error(), "Authentication failed") || - strings.Contains(err.Error(), "attempted logon is invalid") || - strings.Contains(err.Error(), "bad username or authentication") { - break - } - - if retryCount < Common.MaxRetries-1 { - time.Sleep(time.Second * time.Duration(retryCount+1)) - continue - } - } - break - } + credentials = append(credentials, Smb2Credential{ + Username: user, + Password: "", + Hash: hash, + IsHash: true, + }) } } - return nil + Common.LogDebug(fmt.Sprintf("开始SMB2哈希认证扫描 (总用户数: %d, 总哈希数: %d, 总组合数: %d)", + len(Common.Userdict["smb"]), len(Common.HashBytes), len(credentials))) + + // 使用工作池并发扫描 + return concurrentSmb2Scan(ctx, info, credentials) +} + +// concurrentSmb2Scan 并发扫描SMB2服务 +func concurrentSmb2Scan(ctx context.Context, info *Common.HostInfo, credentials []Smb2Credential) error { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *Smb2ScanResult, 1) + workChan := make(chan Smb2Credential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 记录共享信息是否已打印和锁定的用户 + var ( + sharesPrinted bool + lockedUsers = make(map[string]bool) + mutex sync.Mutex + ) + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + // 检查用户是否已锁定 + mutex.Lock() + locked := lockedUsers[credential.Username] + currentSharesPrinted := sharesPrinted + mutex.Unlock() + + if locked { + Common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username)) + continue + } + + // 尝试凭据 + result := trySmb2Credential(scanCtx, info, credential, currentSharesPrinted) + + // 更新共享信息打印状态 + if result.Shares != nil && len(result.Shares) > 0 && !currentSharesPrinted { + mutex.Lock() + sharesPrinted = true + mutex.Unlock() + + // 打印共享信息 + logShareInfo(info, credential.Username, credential.Password, credential.Hash, result.Shares) + } + + // 检查认证成功 + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } + + // 检查账户锁定 + if result.Error != nil { + errMsg := result.Error.Error() + if strings.Contains(errMsg, "account has been automatically locked") || + strings.Contains(errMsg, "account has been locked") || + strings.Contains(errMsg, "user account has been automatically locked") { + + mutex.Lock() + lockedUsers[credential.Username] = true + mutex.Unlock() + + Common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username)) + } + } + } + } + }() + } + + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + // 检查用户是否已锁定 + mutex.Lock() + locked := lockedUsers[cred.Username] + mutex.Unlock() + + if locked { + continue // 跳过已锁定用户 + } + + if cred.IsHash { + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s Hash:%s", + i+1, len(credentials), cred.Username, Common.HashValue)) + } else { + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", + i+1, len(credentials), cred.Username, cred.Password)) + } + + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + // 记录成功结果 + logSuccessfulAuth(info, result.Credential.Username, + result.Credential.Password, result.Credential.Hash) + return nil + } + return nil + case <-ctx.Done(): + Common.LogDebug("SMB2扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return fmt.Errorf("全局超时") + } +} + +// trySmb2Credential 尝试单个SMB2凭据 +func trySmb2Credential(ctx context.Context, info *Common.HostInfo, credential Smb2Credential, hasprint bool) *Smb2ScanResult { + // 创建单个连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second) + defer cancel() + + // 在协程中尝试连接 + resultChan := make(chan struct { + success bool + shares []string + err error + }, 1) + + go func() { + success, err, shares := Smb2Con(connCtx, info, credential.Username, + credential.Password, credential.Hash, hasprint) + + select { + case <-connCtx.Done(): + case resultChan <- struct { + success bool + shares []string + err error + }{success, shares, err}: + } + }() + + // 等待结果或超时 + select { + case result := <-resultChan: + if result.success { + return &Smb2ScanResult{ + Success: true, + Credential: credential, + Shares: result.shares, + } + } + + // 失败时记录错误 + if result.err != nil { + logFailedAuth(info, credential.Username, credential.Password, credential.Hash, result.err) + } + + return &Smb2ScanResult{ + Success: false, + Error: result.err, + Credential: credential, + Shares: result.shares, + } + + case <-connCtx.Done(): + if ctx.Err() != nil { + // 全局超时 + return &Smb2ScanResult{ + Success: false, + Error: ctx.Err(), + Credential: credential, + } + } + // 单个连接超时 + err := fmt.Errorf("连接超时") + logFailedAuth(info, credential.Username, credential.Password, credential.Hash, err) + return &Smb2ScanResult{ + Success: false, + Error: err, + Credential: credential, + } + } +} + +// Smb2Con 尝试SMB2连接并进行认证,检查共享访问权限 +func Smb2Con(ctx context.Context, info *Common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, shares []string) { + // 建立TCP连接,使用上下文提供的超时控制 + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:445", info.Host)) + if err != nil { + return false, fmt.Errorf("连接失败: %v", err), nil + } + defer conn.Close() + + // 配置NTLM认证 + initiator := smb2.NTLMInitiator{ + User: user, + Domain: Common.Domain, + } + + // 设置认证方式(哈希或密码) + if len(hash) > 0 { + initiator.Hash = hash + } else { + initiator.Password = pass + } + + // 创建SMB2会话 + dialer := &smb2.Dialer{ + Initiator: &initiator, + } + + // 使用context设置超时 + session, err := dialer.Dial(conn) + if err != nil { + return false, fmt.Errorf("SMB2会话建立失败: %v", err), nil + } + defer session.Logoff() + + // 检查上下文是否已取消 + select { + case <-ctx.Done(): + return false, ctx.Err(), nil + default: + } + + // 获取共享列表 + sharesList, err := session.ListSharenames() + if err != nil { + return false, fmt.Errorf("获取共享列表失败: %v", err), nil + } + + // 再次检查上下文是否已取消 + select { + case <-ctx.Done(): + return false, ctx.Err(), sharesList + default: + } + + // 尝试访问C$共享以验证管理员权限 + fs, err := session.Mount("C$") + if err != nil { + return false, fmt.Errorf("挂载C$失败: %v", err), sharesList + } + defer fs.Umount() + + // 最后检查上下文是否已取消 + select { + case <-ctx.Done(): + return false, ctx.Err(), sharesList + default: + } + + // 尝试读取系统文件以验证权限 + path := `Windows\win.ini` + f, err := fs.OpenFile(path, os.O_RDONLY, 0666) + if err != nil { + return false, fmt.Errorf("访问系统文件失败: %v", err), sharesList + } + defer f.Close() + + return true, nil, sharesList } // logSuccessfulAuth 记录成功的认证 @@ -198,69 +449,6 @@ func logFailedAuth(info *Common.HostInfo, user, pass string, hash []byte, err er Common.LogError(errlog) } -// Smb2Con 尝试SMB2连接并进行认证,检查共享访问权限 -func Smb2Con(info *Common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, flag2 bool) { - // 建立TCP连接 - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:445", info.Host), - time.Duration(Common.Timeout)*time.Second) - if err != nil { - return false, fmt.Errorf("连接失败: %v", err), false - } - defer conn.Close() - - // 配置NTLM认证 - initiator := smb2.NTLMInitiator{ - User: user, - Domain: Common.Domain, - } - - // 设置认证方式(哈希或密码) - if len(hash) > 0 { - initiator.Hash = hash - } else { - initiator.Password = pass - } - - // 创建SMB2会话 - d := &smb2.Dialer{ - Initiator: &initiator, - } - session, err := d.Dial(conn) - if err != nil { - return false, fmt.Errorf("SMB2会话建立失败: %v", err), false - } - defer session.Logoff() - - // 获取共享列表 - shares, err := session.ListSharenames() - if err != nil { - return false, fmt.Errorf("获取共享列表失败: %v", err), false - } - - // 打印共享信息(如果未打印过) - if !hasprint { - logShareInfo(info, user, pass, hash, shares) - flag2 = true - } - - // 尝试访问C$共享以验证管理员权限 - fs, err := session.Mount("C$") - if err != nil { - return false, fmt.Errorf("挂载C$失败: %v", err), flag2 - } - defer fs.Umount() - - // 尝试读取系统文件以验证权限 - path := `Windows\win.ini` - f, err := fs.OpenFile(path, os.O_RDONLY, 0666) - if err != nil { - return false, fmt.Errorf("访问系统文件失败: %v", err), flag2 - } - defer f.Close() - - return true, nil, flag2 -} - // logShareInfo 记录SMB共享信息 func logShareInfo(info *Common.HostInfo, user string, pass string, hash []byte, shares []string) { credential := pass diff --git a/Plugins/SMTP.go b/Plugins/SMTP.go index 6966a53..a03a27c 100644 --- a/Plugins/SMTP.go +++ b/Plugins/SMTP.go @@ -1,167 +1,278 @@ package Plugins import ( + "context" "fmt" "github.com/shadow1ng/fscan/Common" "net" "net/smtp" "strings" + "sync" "time" ) +// SmtpCredential 表示一个SMTP凭据 +type SmtpCredential struct { + Username string + Password string +} + +// SmtpScanResult 表示SMTP扫描结果 +type SmtpScanResult struct { + Success bool + Error error + Credential SmtpCredential + IsAnonymous bool +} + // SmtpScan 执行 SMTP 服务扫描 -func SmtpScan(info *Common.HostInfo) (tmperr error) { +func SmtpScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - Common.LogDebug("尝试匿名访问...") + + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() // 先测试匿名访问 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - flag, err := SmtpConn(info, "", "") - if flag && err == nil { - msg := fmt.Sprintf("SMTP服务 %s 允许匿名访问", target) - Common.LogSuccess(msg) + Common.LogDebug("尝试匿名访问...") + anonymousResult := trySmtpCredential(ctx, info, SmtpCredential{"", ""}, Common.Timeout, Common.MaxRetries) - // 保存匿名访问结果 - result := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "smtp", - "type": "anonymous-access", - "anonymous": true, - }, - } - Common.SaveResult(result) - return err - } - if err != nil { - errlog := fmt.Sprintf("smtp %s anonymous %v", target, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - return err - } - continue - } - } - break + if anonymousResult.Success { + // 匿名访问成功 + saveSmtpResult(info, target, anonymousResult) + return nil } - totalUsers := len(Common.Userdict["smtp"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - - tried := 0 - total := totalUsers * totalPass - - // 遍历所有用户名密码组合 + // 构建凭据列表 + var credentials []SmtpCredential for _, user := range Common.Userdict["smtp"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, SmtpCredential{ + Username: user, + Password: actualPass, + }) + } + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["smtp"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentSmtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveSmtpResult(info, target, result) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("SMTP扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 + return nil + } +} + +// concurrentSmtpScan 并发扫描SMTP服务 +func concurrentSmtpScan(ctx context.Context, info *Common.HostInfo, credentials []SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *SmtpScanResult, 1) + workChan := make(chan SmtpCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := trySmtpCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } } + } + }() + } - done := make(chan struct { + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("SMTP并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// trySmtpCredential 尝试单个SMTP凭据 +func trySmtpCredential(ctx context.Context, info *Common.HostInfo, credential SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &SmtpScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + + // 在协程中尝试连接 + resultChan := make(chan struct { + success bool + err error + }, 1) + + go func() { + success, err := SmtpConn(info, credential.Username, credential.Password, timeoutSeconds) + select { + case <-connCtx.Done(): + case resultChan <- struct { success bool err error - }, 1) - - go func(user, pass string) { - flag, err := SmtpConn(info, user, pass) - select { - case done <- struct { - success bool - err error - }{flag, err}: - default: - } - }(user, pass) - - var err error - select { - case result := <-done: - err = result.err - if result.success && err == nil { - msg := fmt.Sprintf("SMTP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass) - Common.LogSuccess(msg) - - // 保存成功爆破结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "smtp", - "type": "weak-password", - "username": user, - "password": pass, - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") + }{success, err}: } + }() - if err != nil { - errlog := fmt.Sprintf("SMTP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", - target, user, pass, err) - Common.LogError(errlog) + // 等待结果或超时 + var success bool + var err error - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + select { + case result := <-resultChan: + success = result.success + err = result.err + case <-connCtx.Done(): + cancel() + if ctx.Err() != nil { + // 全局超时 + return &SmtpScanResult{ + Success: false, + Error: ctx.Err(), + Credential: credential, } } - break + // 单个连接超时 + err = fmt.Errorf("连接超时") + } + + cancel() // 释放连接上下文 + + if success { + isAnonymous := credential.Username == "" && credential.Password == "" + return &SmtpScanResult{ + Success: true, + Credential: credential, + IsAnonymous: isAnonymous, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &SmtpScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // SmtpConn 尝试 SMTP 连接 -func SmtpConn(info *Common.HostInfo, user string, pass string) (bool, error) { +func SmtpConn(info *Common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) { host, port := info.Host, info.Ports - timeout := time.Duration(Common.Timeout) * time.Second + timeout := time.Duration(timeoutSeconds) * time.Second addr := fmt.Sprintf("%s:%s", host, port) - conn, err := net.DialTimeout("tcp", addr, timeout) + // 设置连接超时 + dialer := &net.Dialer{ + Timeout: timeout, + } + + conn, err := dialer.Dial("tcp", addr) if err != nil { return false, err } defer conn.Close() + // 设置读写超时 + conn.SetDeadline(time.Now().Add(timeout)) + client, err := smtp.NewClient(conn, host) if err != nil { return false, err } defer client.Close() + // 尝试认证 if user != "" { auth := smtp.PlainAuth("", user, pass, host) err = client.Auth(auth) @@ -170,6 +281,7 @@ func SmtpConn(info *Common.HostInfo, user string, pass string) (bool, error) { } } + // 尝试发送邮件(测试权限) err = client.Mail("test@test.com") if err != nil { return false, err @@ -177,3 +289,41 @@ func SmtpConn(info *Common.HostInfo, user string, pass string) (bool, error) { return true, nil } + +// saveSmtpResult 保存SMTP扫描结果 +func saveSmtpResult(info *Common.HostInfo, target string, result *SmtpScanResult) { + var successMsg string + var details map[string]interface{} + + if result.IsAnonymous { + successMsg = fmt.Sprintf("SMTP服务 %s 允许匿名访问", target) + details = map[string]interface{}{ + "port": info.Ports, + "service": "smtp", + "type": "anonymous-access", + "anonymous": true, + } + } else { + successMsg = fmt.Sprintf("SMTP服务 %s 爆破成功 用户名: %v 密码: %v", + target, result.Credential.Username, result.Credential.Password) + details = map[string]interface{}{ + "port": info.Ports, + "service": "smtp", + "type": "weak-password", + "username": result.Credential.Username, + "password": result.Credential.Password, + } + } + + Common.LogSuccess(successMsg) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(vulnResult) +} diff --git a/Plugins/Telnet.go b/Plugins/Telnet.go index bafb943..efa48b2 100644 --- a/Plugins/Telnet.go +++ b/Plugins/Telnet.go @@ -2,145 +2,280 @@ package Plugins import ( "bytes" + "context" "errors" "fmt" "github.com/shadow1ng/fscan/Common" "net" "regexp" "strings" + "sync" "time" ) +// TelnetCredential 表示一个Telnet凭据 +type TelnetCredential struct { + Username string + Password string +} + +// TelnetScanResult 表示Telnet扫描结果 +type TelnetScanResult struct { + Success bool + Error error + Credential TelnetCredential + NoAuth bool +} + // TelnetScan 执行Telnet服务扫描和密码爆破 -func TelnetScan(info *Common.HostInfo) (tmperr error) { +func TelnetScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalUsers := len(Common.Userdict["telnet"]) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass)) - tried := 0 - total := totalUsers * totalPass + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 遍历所有用户名密码组合 + // 构建凭据列表 + var credentials []TelnetCredential for _, user := range Common.Userdict["telnet"] { for _, pass := range Common.Passwords { - tried++ - pass = strings.Replace(pass, "{user}", user, -1) - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass)) + actualPass := strings.Replace(pass, "{user}", user, -1) + credentials = append(credentials, TelnetCredential{ + Username: user, + Password: actualPass, + }) + } + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass)) + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["telnet"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentTelnetScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveTelnetResult(info, target, result) + return nil + } + + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("Telnet扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + return nil + } +} + +// concurrentTelnetScan 并发扫描Telnet服务 +func concurrentTelnetScan(ctx context.Context, info *Common.HostInfo, credentials []TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *TelnetScanResult, 1) + workChan := make(chan TelnetCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { + select { + case <-scanCtx.Done(): + return + default: + result := tryTelnetCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success || result.NoAuth { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据或无需认证,取消其他工作 + default: + } + return + } } + } + }() + } - done := make(chan struct { + // 发送工作 + go func() { + for i, cred := range credentials { + select { + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) + workChan <- cred + } + } + close(workChan) + }() + + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && (result.Success || result.NoAuth) { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("Telnet并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryTelnetCredential 尝试单个Telnet凭据 +func tryTelnetCredential(ctx context.Context, info *Common.HostInfo, credential TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &TelnetScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 + } + + // 创建结果通道 + resultChan := make(chan struct { + success bool + noAuth bool + err error + }, 1) + + // 设置单个连接超时 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + go func() { + defer cancel() + noAuth, err := telnetConnWithContext(connCtx, info, credential.Username, credential.Password) + select { + case <-connCtx.Done(): + // 连接已超时或取消 + case resultChan <- struct { success bool noAuth bool err error - }, 1) - - go func(user, pass string) { - flag, err := telnetConn(info, user, pass) - select { - case done <- struct { - success bool - noAuth bool - err error - }{err == nil, flag, err}: - default: - } - }(user, pass) - - var err error - select { - case result := <-done: - err = result.err - if result.noAuth { - // 无需认证 - msg := fmt.Sprintf("Telnet服务 %s 无需认证", target) - Common.LogSuccess(msg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "telnet", - "type": "unauthorized-access", - }, - } - Common.SaveResult(vulnResult) - return nil - - } else if result.success { - // 成功爆破 - msg := fmt.Sprintf("Telnet服务 %s 用户名:%v 密码:%v", target, user, pass) - Common.LogSuccess(msg) - - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "telnet", - "type": "weak-password", - "username": user, - "password": pass, - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") + }{err == nil, noAuth, err}: } + }() - if err != nil { - errlog := fmt.Sprintf("Telnet连接失败 %s 用户名:%v 密码:%v 错误:%v", - target, user, pass, err) - Common.LogError(errlog) + // 等待结果或超时 + var success bool + var noAuth bool + var err error - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + select { + case result := <-resultChan: + success = result.success + noAuth = result.noAuth + err = result.err + case <-connCtx.Done(): + if ctx.Err() != nil { + // 全局超时 + return &TelnetScanResult{ + Success: false, + Error: ctx.Err(), + Credential: credential, } } - break + // 单个连接超时 + err = fmt.Errorf("连接超时") + } + + if noAuth { + return &TelnetScanResult{ + Success: false, + NoAuth: true, + Credential: credential, + } + } + + if success { + return &TelnetScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err + if err != nil { + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 + } } } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried)) - return tmperr + return &TelnetScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } -// telnetConn 尝试建立Telnet连接并进行身份验证 -func telnetConn(info *Common.HostInfo, user, pass string) (flag bool, err error) { - client := NewTelnet(info.Host, info.Ports) - - if err = client.Connect(); err != nil { +// telnetConnWithContext 带上下文的Telnet连接尝试 +func telnetConnWithContext(ctx context.Context, info *Common.HostInfo, user, pass string) (bool, error) { + // 创建TCP连接(使用上下文控制) + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", info.Host, info.Ports)) + if err != nil { return false, err } + + client := &TelnetClient{ + IPAddr: info.Host, + Port: info.Ports, + UserName: user, + Password: pass, + conn: conn, + } + + // 设置连接关闭 defer client.Close() - client.UserName = user - client.Password = pass + // 检查上下文是否已取消 + select { + case <-ctx.Done(): + return false, ctx.Err() + default: + } + + // 初始化连接 + client.init() + client.ServerType = client.MakeServerType() if client.ServerType == UnauthorizedAccess { @@ -151,119 +286,42 @@ func telnetConn(info *Common.HostInfo, user, pass string) (flag bool, err error) return false, err } -const ( - // 写入操作后的延迟时间 - TIME_DELAY_AFTER_WRITE = 300 * time.Millisecond +// saveTelnetResult 保存Telnet扫描结果 +func saveTelnetResult(info *Common.HostInfo, target string, result *TelnetScanResult) { + var successMsg string + var details map[string]interface{} - // Telnet基础控制字符 - IAC = byte(255) // 解释为命令(Interpret As Command) - DONT = byte(254) // 请求对方停止执行某选项 - DO = byte(253) // 请求对方执行某选项 - WONT = byte(252) // 拒绝执行某选项 - WILL = byte(251) // 同意执行某选项 + if result.NoAuth { + successMsg = fmt.Sprintf("Telnet服务 %s 无需认证", target) + details = map[string]interface{}{ + "port": info.Ports, + "service": "telnet", + "type": "unauthorized-access", + } + } else { + successMsg = fmt.Sprintf("Telnet服务 %s 用户名:%v 密码:%v", + target, result.Credential.Username, result.Credential.Password) + details = map[string]interface{}{ + "port": info.Ports, + "service": "telnet", + "type": "weak-password", + "username": result.Credential.Username, + "password": result.Credential.Password, + } + } - // 子协商相关控制字符 - SB = byte(250) // 子协商开始(Subnegotiation Begin) - SE = byte(240) // 子协商结束(Subnegotiation End) + Common.LogSuccess(successMsg) - // 特殊功能字符 - NULL = byte(0) // 空字符 - EOF = byte(236) // 文档结束 - SUSP = byte(237) // 暂停进程 - ABORT = byte(238) // 停止进程 - REOR = byte(239) // 记录结束 - - // 控制操作字符 - NOP = byte(241) // 无操作 - DM = byte(242) // 数据标记 - BRK = byte(243) // 中断 - IP = byte(244) // 中断进程 - AO = byte(245) // 终止输出 - AYT = byte(246) // 在线确认 - EC = byte(247) // 擦除字符 - EL = byte(248) // 擦除行 - GA = byte(249) // 继续进行 - - // Telnet协议选项代码 (来自arpa/telnet.h) - BINARY = byte(0) // 8位数据通道 - ECHO = byte(1) // 回显 - RCP = byte(2) // 准备重新连接 - SGA = byte(3) // 禁止继续 - NAMS = byte(4) // 近似消息大小 - STATUS = byte(5) // 状态查询 - TM = byte(6) // 时间标记 - RCTE = byte(7) // 远程控制传输和回显 - - // 输出协商选项 - NAOL = byte(8) // 输出行宽度协商 - NAOP = byte(9) // 输出页面大小协商 - NAOCRD = byte(10) // 回车处理协商 - NAOHTS = byte(11) // 水平制表符停止协商 - NAOHTD = byte(12) // 水平制表符处理协商 - NAOFFD = byte(13) // 换页符处理协商 - NAOVTS = byte(14) // 垂直制表符停止协商 - NAOVTD = byte(15) // 垂直制表符处理协商 - NAOLFD = byte(16) // 换行符处理协商 - - // 扩展功能选项 - XASCII = byte(17) // 扩展ASCII字符集 - LOGOUT = byte(18) // 强制登出 - BM = byte(19) // 字节宏 - DET = byte(20) // 数据输入终端 - SUPDUP = byte(21) // SUPDUP协议 - SUPDUPOUTPUT = byte(22) // SUPDUP输出 - SNDLOC = byte(23) // 发送位置 - - // 终端相关选项 - TTYPE = byte(24) // 终端类型 - EOR = byte(25) // 记录结束 - TUID = byte(26) // TACACS用户识别 - OUTMRK = byte(27) // 输出标记 - TTYLOC = byte(28) // 终端位置编号 - VT3270REGIME = byte(29) // 3270体制 - - // 通信控制选项 - X3PAD = byte(30) // X.3 PAD - NAWS = byte(31) // 窗口大小 - TSPEED = byte(32) // 终端速度 - LFLOW = byte(33) // 远程流控制 - LINEMODE = byte(34) // 行模式选项 - - // 环境与认证选项 - XDISPLOC = byte(35) // X显示位置 - OLD_ENVIRON = byte(36) // 旧环境变量 - AUTHENTICATION = byte(37) // 认证 - ENCRYPT = byte(38) // 加密选项 - NEW_ENVIRON = byte(39) // 新环境变量 - - // IANA分配的额外选项 - // http://www.iana.org/assignments/telnet-options - TN3270E = byte(40) // TN3270E - XAUTH = byte(41) // XAUTH - CHARSET = byte(42) // 字符集 - RSP = byte(43) // 远程串行端口 - COM_PORT_OPTION = byte(44) // COM端口控制 - SUPPRESS_LOCAL_ECHO = byte(45) // 禁止本地回显 - TLS = byte(46) // 启动TLS - KERMIT = byte(47) // KERMIT协议 - SEND_URL = byte(48) // 发送URL - FORWARD_X = byte(49) // X转发 - - // 特殊用途选项 - PRAGMA_LOGON = byte(138) // PRAGMA登录 - SSPI_LOGON = byte(139) // SSPI登录 - PRAGMA_HEARTBEAT = byte(140) // PRAGMA心跳 - EXOPL = byte(255) // 扩展选项列表 - NOOPT = byte(0) // 无选项 -) - -// 服务器类型常量定义 -const ( - Closed = iota // 连接关闭 - UnauthorizedAccess // 无需认证 - OnlyPassword // 仅需密码 - UsernameAndPassword // 需要用户名和密码 -) + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: details, + } + Common.SaveResult(vulnResult) +} // TelnetClient Telnet客户端结构体 type TelnetClient struct { @@ -276,28 +334,8 @@ type TelnetClient struct { ServerType int // 服务器类型 } -// NewTelnet 创建新的Telnet客户端实例 -func NewTelnet(addr, port string) *TelnetClient { - return &TelnetClient{ - IPAddr: addr, - Port: port, - UserName: "", - Password: "", - conn: nil, - LastResponse: "", - ServerType: Closed, - } -} - -// Connect 建立Telnet连接 -func (c *TelnetClient) Connect() error { - // 建立TCP连接,超时时间5秒 - conn, err := net.DialTimeout("tcp", c.Netloc(), 5*time.Second) - if err != nil { - return err - } - c.conn = conn - +// init 初始化Telnet连接 +func (c *TelnetClient) init() { // 启动后台goroutine处理服务器响应 go func() { for { @@ -328,8 +366,7 @@ func (c *TelnetClient) Connect() error { }() // 等待连接初始化完成 - time.Sleep(time.Second * 3) - return nil + time.Sleep(time.Second * 2) } // WriteContext 写入数据到Telnet连接 @@ -362,7 +399,9 @@ func (c *TelnetClient) Netloc() string { // Close 关闭Telnet连接 func (c *TelnetClient) Close() { - c.conn.Close() + if c.conn != nil { + c.conn.Close() + } } // SerializationResponse 解析Telnet响应数据 @@ -398,9 +437,11 @@ func (c *TelnetClient) SerializationResponse(responseBuf []byte) (displayBuf []b if ch == SB { displayBuf = append(displayBuf, responseBuf[:index]...) seIndex := bytes.IndexByte(responseBuf, SE) - commandList = append(commandList, responseBuf[index:seIndex]) - responseBuf = responseBuf[seIndex+1:] - continue + if seIndex != -1 && seIndex > index { + commandList = append(commandList, responseBuf[index:seIndex+1]) + responseBuf = responseBuf[seIndex+1:] + continue + } } break @@ -465,6 +506,8 @@ func (c *TelnetClient) MakeReply(command []byte) []byte { // read 从Telnet连接读取数据 func (c *TelnetClient) read() ([]byte, error) { var buf [2048]byte + // 设置读取超时为2秒 + _ = c.conn.SetReadDeadline(time.Now().Add(time.Second * 2)) n, err := c.conn.Read(buf[0:]) if err != nil { return nil, err @@ -481,6 +524,8 @@ func (c *TelnetClient) write(buf []byte) error { if err != nil { return err } + // 写入后短暂延迟,让服务器有时间处理 + time.Sleep(TIME_DELAY_AFTER_WRITE) return nil } @@ -503,7 +548,17 @@ func (c *TelnetClient) Login() error { // MakeServerType 通过分析服务器响应判断服务器类型 func (c *TelnetClient) MakeServerType() int { responseString := c.ReadContext() + + // 空响应情况 + if responseString == "" { + return Closed + } + response := strings.Split(responseString, "\n") + if len(response) == 0 { + return Closed + } + lastLine := strings.ToLower(response[len(response)-1]) // 检查是否需要用户名和密码 @@ -556,7 +611,7 @@ func (c *TelnetClient) loginForOnlyPassword() error { // 发送密码并等待响应 c.WriteContext(c.Password) - time.Sleep(time.Second * 3) + time.Sleep(time.Second * 2) // 验证登录结果 responseString := c.ReadContext() @@ -574,12 +629,12 @@ func (c *TelnetClient) loginForOnlyPassword() error { func (c *TelnetClient) loginForUsernameAndPassword() error { // 发送用户名 c.WriteContext(c.UserName) - time.Sleep(time.Second * 3) + time.Sleep(time.Second * 2) c.Clear() // 发送密码 c.WriteContext(c.Password) - time.Sleep(time.Second * 5) + time.Sleep(time.Second * 3) // 验证登录结果 responseString := c.ReadContext() @@ -640,12 +695,21 @@ func (c *TelnetClient) isLoginFailed(responseString string) bool { // isLoginSucceed 检查是否登录成功 func (c *TelnetClient) isLoginSucceed(responseString string) bool { + // 空响应视为失败 + if responseString == "" { + return false + } + // 获取最后一行响应 lines := strings.Split(responseString, "\n") + if len(lines) == 0 { + return false + } + lastLine := lines[len(lines)-1] // 检查命令提示符 - if regexp.MustCompile("^[#$].*").MatchString(lastLine) || + if regexp.MustCompile("^[#$>].*").MatchString(lastLine) || regexp.MustCompile("^<[a-zA-Z0-9_]+>.*").MatchString(lastLine) { return true } @@ -658,7 +722,7 @@ func (c *TelnetClient) isLoginSucceed(responseString string) bool { // 发送测试命令验证 c.Clear() c.WriteContext("?") - time.Sleep(time.Second * 3) + time.Sleep(time.Second * 2) responseString = c.ReadContext() // 检查响应长度 @@ -668,3 +732,38 @@ func (c *TelnetClient) isLoginSucceed(responseString string) bool { return false } + +// Telnet协议常量定义 +const ( + // 写入操作后的延迟时间 + TIME_DELAY_AFTER_WRITE = 300 * time.Millisecond + + // Telnet基础控制字符 + IAC = byte(255) // 解释为命令(Interpret As Command) + DONT = byte(254) // 请求对方停止执行某选项 + DO = byte(253) // 请求对方执行某选项 + WONT = byte(252) // 拒绝执行某选项 + WILL = byte(251) // 同意执行某选项 + + // 子协商相关控制字符 + SB = byte(250) // 子协商开始(Subnegotiation Begin) + SE = byte(240) // 子协商结束(Subnegotiation End) + + // 特殊功能字符 + NULL = byte(0) // 空字符 + EOF = byte(236) // 文档结束 + SUSP = byte(237) // 暂停进程 + ABORT = byte(238) // 停止进程 + REOR = byte(239) // 记录结束 + + // Telnet选项代码 + BINARY = byte(0) // 8位数据通道 + ECHO = byte(1) // 回显 + SGA = byte(3) // 禁止继续 + + // 服务器类型常量定义 + Closed = iota // 连接关闭 + UnauthorizedAccess // 无需认证 + OnlyPassword // 仅需密码 + UsernameAndPassword // 需要用户名和密码 +) diff --git a/Plugins/VNC.go b/Plugins/VNC.go index 0b1ecef..8f94d20 100644 --- a/Plugins/VNC.go +++ b/Plugins/VNC.go @@ -1,132 +1,274 @@ package Plugins import ( + "context" "fmt" "github.com/mitchellh/go-vnc" "github.com/shadow1ng/fscan/Common" "net" + "sync" "time" ) -func VncScan(info *Common.HostInfo) (tmperr error) { +// VncCredential 表示VNC凭据 +type VncCredential struct { + Password string +} + +// VncScanResult 表示VNC扫描结果 +type VncScanResult struct { + Success bool + Error error + Credential VncCredential +} + +func VncScan(info *Common.HostInfo) error { if Common.DisableBrute { - return + return nil } - maxRetries := Common.MaxRetries - modename := "vnc" target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - totalPass := len(Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", totalPass)) - tried := 0 + // 设置全局超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer cancel() - // 遍历所有密码 + // 构建密码列表 + var credentials []VncCredential for _, pass := range Common.Passwords { - tried++ - Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", tried, totalPass, pass)) + credentials = append(credentials, VncCredential{Password: pass}) + } - // 重试循环 - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - Common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retryCount+1, pass)) - } + Common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", len(credentials))) - done := make(chan struct { - success bool - err error - }, 1) + // 使用工作池并发扫描 + result := concurrentVncScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries) + if result != nil { + // 记录成功结果 + saveVncResult(info, target, result.Credential) + return nil + } - go func(pass string) { - success, err := VncConn(info, pass) + // 检查是否因为全局超时而退出 + select { + case <-ctx.Done(): + Common.LogDebug("VNC扫描全局超时") + return fmt.Errorf("全局超时") + default: + Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", len(credentials))) + return nil + } +} + +// concurrentVncScan 并发扫描VNC服务 +func concurrentVncScan(ctx context.Context, info *Common.HostInfo, credentials []VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult { + // 使用ModuleThreadNum控制并发数 + maxConcurrent := Common.ModuleThreadNum + if maxConcurrent <= 0 { + maxConcurrent = 10 // 默认值 + } + if maxConcurrent > len(credentials) { + maxConcurrent = len(credentials) + } + + // 创建工作池 + var wg sync.WaitGroup + resultChan := make(chan *VncScanResult, 1) + workChan := make(chan VncCredential, maxConcurrent) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() + + // 启动工作协程 + for i := 0; i < maxConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for credential := range workChan { select { - case done <- struct { - success bool - err error - }{success, err}: + case <-scanCtx.Done(): + return default: + result := tryVncCredential(scanCtx, info, credential, timeoutSeconds, maxRetries) + if result.Success { + select { + case resultChan <- result: + scanCancel() // 找到有效凭据,取消其他工作 + default: + } + return + } } - }(pass) + } + }() + } - var err error + // 发送工作 + go func() { + for i, cred := range credentials { select { - case result := <-done: - err = result.err - if result.success && err == nil { - // 连接成功 - successLog := fmt.Sprintf("%s://%s 密码: %v", modename, target, pass) - Common.LogSuccess(successLog) + case <-scanCtx.Done(): + break + default: + Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", i+1, len(credentials), cred.Password)) + workChan <- cred + } + } + close(workChan) + }() - // 保存结果 - vulnResult := &Common.ScanResult{ - Time: time.Now(), - Type: Common.VULN, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "vnc", - "password": pass, - "type": "weak-password", - }, - } - Common.SaveResult(vulnResult) - return nil - } - case <-time.After(time.Duration(Common.Timeout) * time.Second): - err = fmt.Errorf("连接超时") + // 等待结果或完成 + go func() { + wg.Wait() + close(resultChan) + }() + + // 获取结果,考虑全局超时 + select { + case result, ok := <-resultChan: + if ok && result != nil && result.Success { + return result + } + return nil + case <-ctx.Done(): + Common.LogDebug("VNC并发扫描全局超时") + scanCancel() // 确保取消所有未完成工作 + return nil + } +} + +// tryVncCredential 尝试单个VNC凭据 +func tryVncCredential(ctx context.Context, info *Common.HostInfo, credential VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult { + var lastErr error + + for retry := 0; retry < maxRetries; retry++ { + select { + case <-ctx.Done(): + return &VncScanResult{ + Success: false, + Error: fmt.Errorf("全局超时"), + Credential: credential, + } + default: + if retry > 0 { + Common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retry+1, credential.Password)) + time.Sleep(500 * time.Millisecond) // 重试前等待 } + // 创建连接超时上下文 + connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) + success, err := VncConn(connCtx, info, credential.Password) + cancel() + + if success { + return &VncScanResult{ + Success: true, + Credential: credential, + } + } + + lastErr = err if err != nil { - errlog := fmt.Sprintf("%s://%s 尝试密码: %v 错误: %v", - modename, target, pass, err) - Common.LogError(errlog) - - if retryErr := Common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue + // 检查是否需要重试 + if retryErr := Common.CheckErrs(err); retryErr == nil { + break // 不需要重试的错误 } } - break } } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", tried)) - return tmperr + return &VncScanResult{ + Success: false, + Error: lastErr, + Credential: credential, + } } // VncConn 尝试建立VNC连接 -func VncConn(info *Common.HostInfo, pass string) (flag bool, err error) { - flag = false +func VncConn(ctx context.Context, info *Common.HostInfo, pass string) (bool, error) { Host, Port := info.Host, info.Ports + timeout := time.Duration(Common.Timeout) * time.Second - // 建立TCP连接 - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port), - time.Duration(Common.Timeout)*time.Second) + // 使用带上下文的TCP连接 + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", Host, Port)) if err != nil { - return + return false, err } defer conn.Close() - // 配置VNC客户端 - config := &vnc.ClientConfig{ - Auth: []vnc.ClientAuth{ - &vnc.PasswordAuth{ - Password: pass, + // 设置读写超时 + if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil { + return false, err + } + + // 创建完成通道 + doneChan := make(chan struct { + success bool + err error + }, 1) + + // 在协程中处理VNC认证 + go func() { + // 配置VNC客户端 + config := &vnc.ClientConfig{ + Auth: []vnc.ClientAuth{ + &vnc.PasswordAuth{ + Password: pass, + }, }, + } + + // 尝试VNC认证 + client, err := vnc.Client(conn, config) + if err != nil { + select { + case <-ctx.Done(): + case doneChan <- struct { + success bool + err error + }{false, err}: + } + return + } + + // 认证成功 + defer client.Close() + select { + case <-ctx.Done(): + case doneChan <- struct { + success bool + err error + }{true, nil}: + } + }() + + // 等待认证结果或上下文取消 + select { + case result := <-doneChan: + return result.success, result.err + case <-ctx.Done(): + return false, ctx.Err() + } +} + +// saveVncResult 保存VNC扫描结果 +func saveVncResult(info *Common.HostInfo, target string, credential VncCredential) { + successLog := fmt.Sprintf("vnc://%s 密码: %v", target, credential.Password) + Common.LogSuccess(successLog) + + // 保存结果 + vulnResult := &Common.ScanResult{ + Time: time.Now(), + Type: Common.VULN, + Target: info.Host, + Status: "vulnerable", + Details: map[string]interface{}{ + "port": info.Ports, + "service": "vnc", + "password": credential.Password, + "type": "weak-password", }, } - - // 尝试VNC认证 - client, err := vnc.Client(conn, config) - if err == nil { - defer client.Close() - flag = true - } - - return + Common.SaveResult(vulnResult) } diff --git a/TestDocker/ActiveMQ/activemq.xml b/TestDocker/ActiveMQ/activemq.xml index c797097..6878c8e 100644 --- a/TestDocker/ActiveMQ/activemq.xml +++ b/TestDocker/ActiveMQ/activemq.xml @@ -11,7 +11,7 @@ - + diff --git a/TestDocker/ActiveMQ/users.properties b/TestDocker/ActiveMQ/users.properties index 3d4b836..fd3f940 100644 --- a/TestDocker/ActiveMQ/users.properties +++ b/TestDocker/ActiveMQ/users.properties @@ -1,4 +1,4 @@ -admin=123456 +admin=Aa123456789 test=test123 root=root123 system=admin123 \ No newline at end of file diff --git a/TestDocker/Kafka/docker-compose.yml b/TestDocker/Kafka/docker-compose.yml index bf7b3e5..3554a74 100644 --- a/TestDocker/Kafka/docker-compose.yml +++ b/TestDocker/Kafka/docker-compose.yml @@ -1,28 +1,22 @@ # docker-compose.yml version: '3' services: - zookeeper: - image: bitnami/zookeeper:latest - environment: - - ALLOW_ANONYMOUS_LOGIN=yes - ports: - - "2181:2181" - kafka: image: bitnami/kafka:latest ports: - "9092:9092" environment: - - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - KAFKA_CFG_LISTENERS=SASL_PLAINTEXT://:9092 + - KAFKA_CFG_NODE_ID=1 + - KAFKA_CFG_PROCESS_ROLES=broker,controller + - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093 + - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER + - KAFKA_CFG_LISTENERS=CONTROLLER://:9093,SASL_PLAINTEXT://:9092 - KAFKA_CFG_ADVERTISED_LISTENERS=SASL_PLAINTEXT://localhost:9092 - - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=SASL_PLAINTEXT:SASL_PLAINTEXT + - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT - KAFKA_CFG_SASL_ENABLED_MECHANISMS=PLAIN - KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=SASL_PLAINTEXT - KAFKA_OPTS=-Djava.security.auth.login.config=/opt/bitnami/kafka/config/kafka_jaas.conf - ALLOW_PLAINTEXT_LISTENER=yes volumes: - - ./kafka_jaas.conf:/opt/bitnami/kafka/config/kafka_jaas.conf - depends_on: - - zookeeper \ No newline at end of file + - ./kafka_jaas.conf:/opt/bitnami/kafka/config/kafka_jaas.conf \ No newline at end of file diff --git a/TestDocker/LDAP/Dockerfile b/TestDocker/LDAP/Dockerfile index 7d1f1ac..f4150b3 100644 --- a/TestDocker/LDAP/Dockerfile +++ b/TestDocker/LDAP/Dockerfile @@ -5,7 +5,7 @@ ENV LDAP_ORGANISATION="Example Inc" ENV LDAP_DOMAIN="example.com" ENV LDAP_BASE_DN="dc=example,dc=com" # 设置一个弱密码 -ENV LDAP_ADMIN_PASSWORD="123456" +ENV LDAP_ADMIN_PASSWORD="Aa123456789" # 允许匿名访问 ENV LDAP_READONLY_USER="true" ENV LDAP_READONLY_USER_USERNAME="readonly" diff --git a/TestDocker/Oracle/Dockerfile b/TestDocker/Oracle/Dockerfile index 892889a..584fd6c 100644 --- a/TestDocker/Oracle/Dockerfile +++ b/TestDocker/Oracle/Dockerfile @@ -1,16 +1,13 @@ # 使用Oracle官方容器镜像 -FROM container-registry.oracle.com/database/express:latest +FROM container-registry.oracle.com/database/express:21.3.0-xe # 设置环境变量 ENV ORACLE_PWD=123456 -ENV ORACLE_SID=XE -ENV ORACLE_PDB=XEPDB1 +ENV ORACLE_CHARACTERSET=AL32UTF8 # 开放1521端口 -EXPOSE 1521 +EXPOSE 1521 5500 # 健康检查 -HEALTHCHECK --interval=30s --timeout=3s \ - CMD sqlplus -L sys/123456@//localhost:1521/XE as sysdba << EOF - exit; - EOF \ No newline at end of file +HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 \ + CMD nc -z localhost 1521 || exit 1 \ No newline at end of file diff --git a/TestDocker/Rsync/README.txt b/TestDocker/Rsync/README.txt index 15af407..6298955 100644 --- a/TestDocker/Rsync/README.txt +++ b/TestDocker/Rsync/README.txt @@ -1,15 +1,2 @@ -docker pull cassandra:3.11 - -docker run -d --name cassandra-test \ - -e CASSANDRA_AUTHENTICATOR=AllowAllAuthenticator \ - -p 9042:9042 \ - -p 9160:9160 \ - cassandra:3.11 - -docker run -d --name cassandra-test \ - -e CASSANDRA_AUTHENTICATOR=PasswordAuthenticator \ - -e CASSANDRA_PASSWORD=123456 \ - -e CASSANDRA_USER=admin \ - -p 9042:9042 \ - -p 9160:9160 \ - cassandra:3.11 \ No newline at end of file +docker build -t rsync-test . +docker run -d --name rsync-server -p 873:873 rsync-test \ No newline at end of file diff --git a/TestDocker/Telnet/README.md b/TestDocker/Telnet/README.md index e69de29..3b26eb0 100644 --- a/TestDocker/Telnet/README.md +++ b/TestDocker/Telnet/README.md @@ -0,0 +1,2 @@ +docker build -t telnet-test . +docker run -d -p 23:23 --name telnet-server telnet-test \ No newline at end of file