fscan/plugins/cassandra.go
ZacharyZcR 678d750c8a refactor: 重构插件架构,实现单文件插件系统
将复杂的三文件插件架构(connector/exploiter/plugin)重构为简化的单文件插件架构,
大幅减少代码重复和维护成本,提升插件开发效率。

主要改进:
• 将每个服务插件从3个文件简化为1个文件
• 删除过度设计的工厂模式、适配器模式等抽象层
• 消除plugins/services/、plugins/adapters/、plugins/base/复杂目录结构
• 实现直接的插件注册机制,提升系统简洁性
• 保持完全向后兼容,所有扫描功能和输出格式不变

重构统计:
• 删除文件:100+个复杂架构文件
• 新增文件:20个简化的单文件插件
• 代码减少:每个插件减少60-80%代码量
• 功能增强:所有插件包含完整扫描和利用功能

已重构插件: MySQL, SSH, Redis, MongoDB, PostgreSQL, MSSQL, Oracle,
Neo4j, Memcached, RabbitMQ, ActiveMQ, Cassandra, FTP, Kafka, LDAP,
Rsync, SMTP, SNMP, Telnet, VNC

验证通过: 新系统编译运行正常,所有插件功能验证通过
2025-08-25 23:57:00 +08:00

320 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package plugins
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()
})
}