package services import ( "context" "fmt" "net" "strconv" "strings" "time" "github.com/gocql/gocql" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" ) // CassandraPlugin Cassandra数据库扫描插件 - 基于gocql库 type CassandraPlugin struct { name string ports []int } // NewCassandraPlugin 创建Cassandra插件 func NewCassandraPlugin() *CassandraPlugin { return &CassandraPlugin{ name: "cassandra", ports: []int{9042, 9160, 7000, 7001}, // CQL端口 + Thrift端口 + 集群通信端口 } } // GetName 实现Plugin接口 func (p *CassandraPlugin) GetName() string { return p.name } // GetPorts 实现Plugin接口 func (p *CassandraPlugin) GetPorts() []int { return p.ports } // Scan 执行Cassandra扫描 - CQL协议弱密码检测 func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 如果禁用暴力破解,只做服务识别 if common.DisableBrute { return p.identifyService(ctx, info) } // 生成测试凭据 credentials := GenerateCredentials("cassandra") if len(credentials) == 0 { // Cassandra默认凭据 credentials = []Credential{ {Username: "cassandra", Password: "cassandra"}, {Username: "admin", Password: "admin"}, {Username: "admin", Password: ""}, {Username: "root", Password: "root"}, {Username: "user", Password: "user"}, } } // 逐个测试凭据 for _, cred := range credentials { // 检查Context是否被取消 select { case <-ctx.Done(): return &ScanResult{ Success: false, Service: "cassandra", Error: ctx.Err(), } default: } // 测试凭据 if p.testCredential(ctx, info, cred) { // Cassandra认证成功 common.LogSuccess(i18n.GetText("cassandra_scan_success", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "cassandra", Username: cred.Username, Password: cred.Password, } } } // 所有凭据都失败 return &ScanResult{ Success: false, Service: "cassandra", Error: fmt.Errorf("未发现弱密码"), } } // testCredential 测试单个凭据 - 使用gocql进行CQL认证 func (p *CassandraPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { // 解析端口 port, err := strconv.Atoi(info.Ports) if err != nil { return false } // 创建Cassandra集群配置 cluster := gocql.NewCluster(info.Host) cluster.Port = port // 设置连接参数 timeout := time.Duration(common.Timeout) * time.Second cluster.Timeout = timeout cluster.ConnectTimeout = timeout cluster.ProtoVersion = 4 cluster.Consistency = gocql.One // 设置认证信息 if cred.Username != "" || cred.Password != "" { cluster.Authenticator = gocql.PasswordAuthenticator{ Username: cred.Username, Password: cred.Password, } } // 支持代理连接 if common.Socks5Proxy != "" { cluster.Dialer = &cassandraProxyDialer{timeout: timeout} } // 设置重试策略 cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 2} // 创建会话 session, err := p.createSessionWithTimeout(ctx, cluster) if err != nil { return false } defer session.Close() // 执行简单查询验证连接 return p.validateConnection(ctx, session) } // createSessionWithTimeout 在超时控制下创建Cassandra会话 func (p *CassandraPlugin) createSessionWithTimeout(ctx context.Context, cluster *gocql.ClusterConfig) (*gocql.Session, error) { // 创建会话通道以支持Context超时 sessionChan := make(chan struct { session *gocql.Session err error }, 1) // 在goroutine中创建会话 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}: } }() // 等待会话创建或Context取消 select { case result := <-sessionChan: return result.session, result.err case <-ctx.Done(): return nil, fmt.Errorf("创建会话超时: %v", ctx.Err()) } } // validateConnection 验证Cassandra连接 - 执行系统查询 func (p *CassandraPlugin) validateConnection(ctx context.Context, session *gocql.Session) bool { // 使用通道支持Context超时 resultChan := make(chan bool, 1) go func() { // 尝试执行系统查询 // 先尝试查询节点信息,如果失败再尝试本地信息 var dummy interface{} err := session.Query("SELECT peer FROM system.peers LIMIT 1").WithContext(ctx).Scan(&dummy) if err != nil { // 如果peer查询失败,尝试查询本地节点信息 err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&dummy) } select { case <-ctx.Done(): case resultChan <- (err == nil): } }() // 等待查询结果或Context取消 select { case success := <-resultChan: return success case <-ctx.Done(): return false } } // identifyService 服务识别 - 尝试连接Cassandra但不认证 func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 解析端口 port, err := strconv.Atoi(info.Ports) if err != nil { return &ScanResult{ Success: false, Service: "cassandra", Error: fmt.Errorf("无效的端口号: %s", info.Ports), } } // 创建基本集群配置(无认证) cluster := gocql.NewCluster(info.Host) cluster.Port = port timeout := time.Duration(common.Timeout) * time.Second cluster.Timeout = timeout cluster.ConnectTimeout = timeout cluster.ProtoVersion = 4 // 支持代理 if common.Socks5Proxy != "" { cluster.Dialer = &cassandraProxyDialer{timeout: timeout} } // 尝试创建会话 session, err := p.createSessionWithTimeout(ctx, cluster) if err != nil { // 检查错误类型,判断是否为认证问题 if p.isAuthenticationError(err) { // 认证错误说明服务存在但需要密码 common.LogSuccess(i18n.GetText("cassandra_service_identified", target, "Cassandra (需要认证)")) return &ScanResult{ Success: true, Service: "cassandra", Banner: "Cassandra (需要认证)", } } return &ScanResult{ Success: false, Service: "cassandra", Error: err, } } defer session.Close() // 连接成功,获取Cassandra版本信息 banner := p.getCassandraVersion(ctx, session) common.LogSuccess(i18n.GetText("cassandra_service_identified", target, banner)) return &ScanResult{ Success: true, Service: "cassandra", Banner: banner, } } // getCassandraVersion 获取Cassandra版本信息 func (p *CassandraPlugin) getCassandraVersion(ctx context.Context, session *gocql.Session) string { // 尝试查询系统版本信息 var releaseVersion string err := session.Query("SELECT release_version FROM system.local").WithContext(ctx).Scan(&releaseVersion) if err == nil && releaseVersion != "" { return fmt.Sprintf("Cassandra %s", releaseVersion) } // 如果无法获取版本,返回通用描述 return "Cassandra (版本未知)" } // isAuthenticationError 判断是否为认证错误 func (p *CassandraPlugin) isAuthenticationError(err error) bool { if err == nil { return false } errStr := err.Error() // 检查常见的认证错误信息 authErrors := []string{ "authentication", "password", "credentials", "unauthorized", "login", } for _, authErr := range authErrors { if strings.Contains(strings.ToLower(errStr), authErr) { return true } } return false } // cassandraProxyDialer 代理拨号器 type cassandraProxyDialer struct { timeout time.Duration } // DialContext 实现gocql.Dialer接口 func (d *cassandraProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { return common.WrapperTcpWithContext(ctx, network, addr) } // init 自动注册插件 func init() { RegisterPlugin("cassandra", func() Plugin { return NewCassandraPlugin() }) }