mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
feat: 实现Apache Cassandra数据库服务专业扫描插件
• 新增Cassandra插件支持Native Protocol (9042端口) • 实现弱密码检测和未授权访问检测 • 支持自动利用:信息收集、Keyspace枚举、数据提取 • 集成-nobr模式服务识别和-ne自动利用功能 • 完整的Context超时机制和代理支持 • 添加中英文国际化消息支持 • 基于新插件架构实现模块化设计 功能特性: - 支持gocql驱动的CQL查询和认证 - 智能识别Cassandra Native Protocol v4 - 三种利用方法:系统信息、数据库结构、敏感数据 - 完整的错误处理和超时控制机制
This commit is contained in:
parent
51735c4e25
commit
516225a11f
169
Plugins/services/cassandra/connector.go
Normal file
169
Plugins/services/cassandra/connector.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package cassandra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CassandraConnector Cassandra连接器实现
|
||||||
|
type CassandraConnector struct {
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CassandraProxyDialer 实现gocql.Dialer接口,支持代理连接
|
||||||
|
type CassandraProxyDialer struct {
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext 实现代理拨号
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCassandraConnector 创建Cassandra连接器
|
||||||
|
func NewCassandraConnector() *CassandraConnector {
|
||||||
|
return &CassandraConnector{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect 连接到Cassandra服务
|
||||||
|
func (c *CassandraConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
|
||||||
|
c.host = info.Host
|
||||||
|
c.port = info.Ports
|
||||||
|
|
||||||
|
// 创建Cassandra集群配置
|
||||||
|
cluster := gocql.NewCluster(c.host)
|
||||||
|
|
||||||
|
// 解析端口
|
||||||
|
port, err := strconv.Atoi(c.port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("无效的端口号: %s", c.port)
|
||||||
|
}
|
||||||
|
cluster.Port = port
|
||||||
|
|
||||||
|
// 设置连接参数
|
||||||
|
timeout := time.Duration(common.Timeout) * time.Second
|
||||||
|
cluster.Timeout = timeout
|
||||||
|
cluster.ConnectTimeout = timeout
|
||||||
|
cluster.ProtoVersion = 4
|
||||||
|
cluster.Consistency = gocql.One
|
||||||
|
|
||||||
|
// 如果配置了代理,设置自定义Dialer
|
||||||
|
if common.Socks5Proxy != "" {
|
||||||
|
cluster.Dialer = &CassandraProxyDialer{
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置重试策略
|
||||||
|
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
|
||||||
|
|
||||||
|
return cluster, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate 认证
|
||||||
|
func (c *CassandraConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
|
||||||
|
cluster, ok := conn.(*gocql.ClusterConfig)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("无效的连接类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建集群配置副本
|
||||||
|
authCluster := *cluster
|
||||||
|
|
||||||
|
// 设置认证信息
|
||||||
|
if cred.Username != "" || cred.Password != "" {
|
||||||
|
authCluster.Authenticator = gocql.PasswordAuthenticator{
|
||||||
|
Username: cred.Username,
|
||||||
|
Password: cred.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建会话通道以支持Context超时
|
||||||
|
sessionChan := make(chan struct {
|
||||||
|
session *gocql.Session
|
||||||
|
err error
|
||||||
|
}, 1)
|
||||||
|
|
||||||
|
// 在goroutine中创建会话,以便可以通过Context取消
|
||||||
|
go func() {
|
||||||
|
session, err := authCluster.CreateSession()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if session != nil {
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
|
case sessionChan <- struct {
|
||||||
|
session *gocql.Session
|
||||||
|
err error
|
||||||
|
}{session, err}:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 等待会话创建或Context取消
|
||||||
|
var session *gocql.Session
|
||||||
|
var err error
|
||||||
|
select {
|
||||||
|
case result := <-sessionChan:
|
||||||
|
session, err = result.session, result.err
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cassandra认证失败: %v", err)
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return fmt.Errorf("Cassandra连接超时: %v", ctx.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// 尝试执行查询验证连接
|
||||||
|
resultChan := make(chan struct {
|
||||||
|
success bool
|
||||||
|
err error
|
||||||
|
}, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// 尝试两种查询,确保至少一种成功
|
||||||
|
err = session.Query("SELECT peer FROM system.peers").WithContext(ctx).Scan(nil)
|
||||||
|
if err != nil {
|
||||||
|
err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case resultChan <- struct {
|
||||||
|
success bool
|
||||||
|
err error
|
||||||
|
}{err == nil, err}:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 等待查询结果或Context取消
|
||||||
|
select {
|
||||||
|
case result := <-resultChan:
|
||||||
|
if !result.success && result.err != nil {
|
||||||
|
return fmt.Errorf("Cassandra查询验证失败: %v", result.err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return fmt.Errorf("Cassandra查询超时: %v", ctx.Err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭连接
|
||||||
|
func (c *CassandraConnector) Close(conn interface{}) error {
|
||||||
|
// Cassandra集群配置无需显式关闭
|
||||||
|
return nil
|
||||||
|
}
|
333
Plugins/services/cassandra/exploiter.go
Normal file
333
Plugins/services/cassandra/exploiter.go
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
package cassandra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CassandraExploiter Cassandra利用器
|
||||||
|
type CassandraExploiter struct {
|
||||||
|
*base.BaseExploiter
|
||||||
|
connector *CassandraConnector
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCassandraExploiter 创建Cassandra利用器
|
||||||
|
func NewCassandraExploiter() *CassandraExploiter {
|
||||||
|
exploiter := &CassandraExploiter{
|
||||||
|
BaseExploiter: base.NewBaseExploiter("cassandra"),
|
||||||
|
connector: NewCassandraConnector(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加利用方法
|
||||||
|
exploiter.setupExploitMethods()
|
||||||
|
|
||||||
|
return exploiter
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupExploitMethods 设置利用方法
|
||||||
|
func (e *CassandraExploiter) setupExploitMethods() {
|
||||||
|
// 1. 信息收集
|
||||||
|
infoMethod := base.NewExploitMethod(base.ExploitDataExtraction, "information_gathering").
|
||||||
|
WithDescription(i18n.GetText("exploit_method_name_information_gathering")).
|
||||||
|
WithPriority(8).
|
||||||
|
WithConditions("has_credentials").
|
||||||
|
WithHandler(e.exploitInformationGathering).
|
||||||
|
Build()
|
||||||
|
e.AddExploitMethod(infoMethod)
|
||||||
|
|
||||||
|
// 2. Keyspace枚举
|
||||||
|
enumMethod := base.NewExploitMethod(base.ExploitDataExtraction, "keyspace_enumeration").
|
||||||
|
WithDescription(i18n.GetText("exploit_method_name_database_enumeration")).
|
||||||
|
WithPriority(9).
|
||||||
|
WithConditions("has_credentials").
|
||||||
|
WithHandler(e.exploitKeyspaceEnumeration).
|
||||||
|
Build()
|
||||||
|
e.AddExploitMethod(enumMethod)
|
||||||
|
|
||||||
|
// 3. 数据提取
|
||||||
|
dataMethod := base.NewExploitMethod(base.ExploitDataExtraction, "data_extraction").
|
||||||
|
WithDescription(i18n.GetText("exploit_method_name_data_extraction")).
|
||||||
|
WithPriority(7).
|
||||||
|
WithConditions("has_credentials").
|
||||||
|
WithHandler(e.exploitDataExtraction).
|
||||||
|
Build()
|
||||||
|
e.AddExploitMethod(dataMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exploitInformationGathering 信息收集
|
||||||
|
func (e *CassandraExploiter) exploitInformationGathering(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
|
||||||
|
return e.executeWithSession(ctx, info, creds, "information_gathering", e.informationGathering)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exploitKeyspaceEnumeration keyspace枚举
|
||||||
|
func (e *CassandraExploiter) exploitKeyspaceEnumeration(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
|
||||||
|
return e.executeWithSession(ctx, info, creds, "keyspace_enumeration", e.keyspaceEnumeration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exploitDataExtraction 数据提取
|
||||||
|
func (e *CassandraExploiter) exploitDataExtraction(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
|
||||||
|
return e.executeWithSession(ctx, info, creds, "data_extraction", e.dataExtraction)
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeWithSession 使用Cassandra会话执行利用方法
|
||||||
|
func (e *CassandraExploiter) executeWithSession(ctx context.Context, info *common.HostInfo, creds *base.Credential, methodName string, method func(context.Context, interface{}, string) ([]string, error)) (*base.ExploitResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 建立连接
|
||||||
|
conn, err := e.connector.Connect(ctx, info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 认证
|
||||||
|
err = e.connector.Authenticate(ctx, conn, creds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("认证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建会话用于利用
|
||||||
|
cluster := conn.(*gocql.ClusterConfig)
|
||||||
|
if creds.Username != "" || creds.Password != "" {
|
||||||
|
cluster.Authenticator = gocql.PasswordAuthenticator{
|
||||||
|
Username: creds.Username,
|
||||||
|
Password: creds.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := cluster.CreateSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建会话失败: %v", err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// 执行方法
|
||||||
|
output, err := method(ctx, session, target)
|
||||||
|
if err != nil {
|
||||||
|
return &base.ExploitResult{
|
||||||
|
Success: false,
|
||||||
|
Error: err,
|
||||||
|
Type: base.ExploitDataExtraction,
|
||||||
|
Method: methodName,
|
||||||
|
Output: fmt.Sprintf("执行失败: %v", err),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &base.ExploitResult{
|
||||||
|
Success: true,
|
||||||
|
Type: base.ExploitDataExtraction,
|
||||||
|
Method: methodName,
|
||||||
|
Output: strings.Join(output, "\n"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// informationGathering 信息收集
|
||||||
|
func (e *CassandraExploiter) informationGathering(ctx context.Context, session interface{}, target string) ([]string, error) {
|
||||||
|
cassandraSession := session.(*gocql.Session)
|
||||||
|
var output []string
|
||||||
|
|
||||||
|
// 获取Cassandra版本信息
|
||||||
|
var releaseVersion string
|
||||||
|
err := cassandraSession.Query("SELECT release_version FROM system.local").WithContext(ctx).Scan(&releaseVersion)
|
||||||
|
if err == nil {
|
||||||
|
output = append(output, fmt.Sprintf("Cassandra版本: %s", releaseVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取集群名称
|
||||||
|
var clusterName string
|
||||||
|
err = cassandraSession.Query("SELECT cluster_name FROM system.local").WithContext(ctx).Scan(&clusterName)
|
||||||
|
if err == nil {
|
||||||
|
output = append(output, fmt.Sprintf("集群名称: %s", clusterName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据中心信息
|
||||||
|
var datacenter string
|
||||||
|
err = cassandraSession.Query("SELECT data_center FROM system.local").WithContext(ctx).Scan(&datacenter)
|
||||||
|
if err == nil {
|
||||||
|
output = append(output, fmt.Sprintf("数据中心: %s", datacenter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取机架信息
|
||||||
|
var rack string
|
||||||
|
err = cassandraSession.Query("SELECT rack FROM system.local").WithContext(ctx).Scan(&rack)
|
||||||
|
if err == nil {
|
||||||
|
output = append(output, fmt.Sprintf("机架: %s", rack))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节点ID
|
||||||
|
var hostId string
|
||||||
|
err = cassandraSession.Query("SELECT host_id FROM system.local").WithContext(ctx).Scan(&hostId)
|
||||||
|
if err == nil {
|
||||||
|
output = append(output, fmt.Sprintf("节点ID: %s", hostId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) == 0 {
|
||||||
|
return nil, fmt.Errorf("无法获取Cassandra信息")
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyspaceEnumeration keyspace枚举
|
||||||
|
func (e *CassandraExploiter) keyspaceEnumeration(ctx context.Context, session interface{}, target string) ([]string, error) {
|
||||||
|
cassandraSession := session.(*gocql.Session)
|
||||||
|
var output []string
|
||||||
|
|
||||||
|
// 查询所有keyspace
|
||||||
|
iter := cassandraSession.Query("SELECT keyspace_name FROM system_schema.keyspaces").WithContext(ctx).Iter()
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
var keyspaceName string
|
||||||
|
var keyspaces []string
|
||||||
|
|
||||||
|
for iter.Scan(&keyspaceName) {
|
||||||
|
keyspaces = append(keyspaces, keyspaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := iter.Close(); err != nil {
|
||||||
|
return nil, fmt.Errorf("查询keyspace失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyspaces) > 0 {
|
||||||
|
output = append(output, fmt.Sprintf("发现Keyspaces: %s", strings.Join(keyspaces, ", ")))
|
||||||
|
|
||||||
|
// 对每个非系统keyspace获取表信息
|
||||||
|
for _, ks := range keyspaces {
|
||||||
|
if !strings.HasPrefix(ks, "system") {
|
||||||
|
tables := e.getTablesInKeyspace(ctx, cassandraSession, ks)
|
||||||
|
if len(tables) > 0 {
|
||||||
|
output = append(output, fmt.Sprintf("Keyspace '%s' 中的表: %s", ks, strings.Join(tables, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("未发现任何keyspace")
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTablesInKeyspace 获取keyspace中的表
|
||||||
|
func (e *CassandraExploiter) getTablesInKeyspace(ctx context.Context, session *gocql.Session, keyspace string) []string {
|
||||||
|
iter := session.Query("SELECT table_name FROM system_schema.tables WHERE keyspace_name = ?", keyspace).WithContext(ctx).Iter()
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
var tableName string
|
||||||
|
var tables []string
|
||||||
|
|
||||||
|
for iter.Scan(&tableName) {
|
||||||
|
tables = append(tables, tableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataExtraction 数据提取
|
||||||
|
func (e *CassandraExploiter) dataExtraction(ctx context.Context, session interface{}, target string) ([]string, error) {
|
||||||
|
cassandraSession := session.(*gocql.Session)
|
||||||
|
var output []string
|
||||||
|
|
||||||
|
// 尝试读取一些系统表中的敏感信息
|
||||||
|
|
||||||
|
// 获取所有用户角色信息(如果存在)
|
||||||
|
if roles := e.extractRoles(ctx, cassandraSession); len(roles) > 0 {
|
||||||
|
output = append(output, roles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取peer节点信息
|
||||||
|
if peers := e.extractPeers(ctx, cassandraSession); len(peers) > 0 {
|
||||||
|
output = append(output, peers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试获取一些配置信息
|
||||||
|
if configs := e.extractConfigs(ctx, cassandraSession); len(configs) > 0 {
|
||||||
|
output = append(output, configs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) == 0 {
|
||||||
|
return []string{"未能提取到敏感数据"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractRoles 提取角色信息
|
||||||
|
func (e *CassandraExploiter) extractRoles(ctx context.Context, session *gocql.Session) []string {
|
||||||
|
var output []string
|
||||||
|
|
||||||
|
// 尝试查询角色表(Cassandra 2.2+)
|
||||||
|
iter := session.Query("SELECT role FROM system_auth.roles").WithContext(ctx).Iter()
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
var role string
|
||||||
|
var roles []string
|
||||||
|
|
||||||
|
for iter.Scan(&role) {
|
||||||
|
roles = append(roles, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(roles) > 0 {
|
||||||
|
output = append(output, fmt.Sprintf("发现角色: %s", strings.Join(roles, ", ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractPeers 提取peer节点信息
|
||||||
|
func (e *CassandraExploiter) extractPeers(ctx context.Context, session *gocql.Session) []string {
|
||||||
|
var output []string
|
||||||
|
|
||||||
|
iter := session.Query("SELECT peer, data_center, rack, release_version FROM system.peers").WithContext(ctx).Iter()
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
var peer, datacenter, rack, version string
|
||||||
|
var peerCount int
|
||||||
|
|
||||||
|
for iter.Scan(&peer, &datacenter, &rack, &version) {
|
||||||
|
peerCount++
|
||||||
|
output = append(output, fmt.Sprintf("Peer节点 %d: %s (数据中心: %s, 机架: %s, 版本: %s)",
|
||||||
|
peerCount, peer, datacenter, rack, version))
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerCount == 0 {
|
||||||
|
output = append(output, "未发现其他peer节点(单节点集群)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractConfigs 提取配置信息
|
||||||
|
func (e *CassandraExploiter) extractConfigs(ctx context.Context, session *gocql.Session) []string {
|
||||||
|
var output []string
|
||||||
|
|
||||||
|
// 获取token信息
|
||||||
|
var tokens string
|
||||||
|
err := session.Query("SELECT tokens FROM system.local").WithContext(ctx).Scan(&tokens)
|
||||||
|
if err == nil && tokens != "" {
|
||||||
|
// 只显示token数量,不显示完整token(太长)
|
||||||
|
tokenList := strings.Split(strings.Trim(tokens, "{}"), ",")
|
||||||
|
output = append(output, fmt.Sprintf("节点tokens数量: %d", len(tokenList)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取监听地址
|
||||||
|
var listenAddress string
|
||||||
|
err = session.Query("SELECT listen_address FROM system.local").WithContext(ctx).Scan(&listenAddress)
|
||||||
|
if err == nil && listenAddress != "" {
|
||||||
|
output = append(output, fmt.Sprintf("监听地址: %s", listenAddress))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取广播地址
|
||||||
|
var broadcastAddress string
|
||||||
|
err = session.Query("SELECT broadcast_address FROM system.local").WithContext(ctx).Scan(&broadcastAddress)
|
||||||
|
if err == nil && broadcastAddress != "" {
|
||||||
|
output = append(output, fmt.Sprintf("广播地址: %s", broadcastAddress))
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
281
Plugins/services/cassandra/plugin.go
Normal file
281
Plugins/services/cassandra/plugin.go
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
package cassandra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CassandraPlugin Cassandra插件实现
|
||||||
|
type CassandraPlugin struct {
|
||||||
|
*base.ServicePlugin
|
||||||
|
exploiter *CassandraExploiter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCassandraPlugin 创建Cassandra插件
|
||||||
|
func NewCassandraPlugin() *CassandraPlugin {
|
||||||
|
// 插件元数据
|
||||||
|
metadata := &base.PluginMetadata{
|
||||||
|
Name: "cassandra",
|
||||||
|
Version: "2.0.0",
|
||||||
|
Author: "fscan-team",
|
||||||
|
Description: "Apache Cassandra服务扫描和利用插件",
|
||||||
|
Category: "service",
|
||||||
|
Ports: []int{9042}, // Cassandra Native Protocol
|
||||||
|
Protocols: []string{"tcp"},
|
||||||
|
Tags: []string{"cassandra", "nosql", "database", "bruteforce"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建连接器和服务插件
|
||||||
|
connector := NewCassandraConnector()
|
||||||
|
servicePlugin := base.NewServicePlugin(metadata, connector)
|
||||||
|
|
||||||
|
// 创建Cassandra插件
|
||||||
|
plugin := &CassandraPlugin{
|
||||||
|
ServicePlugin: servicePlugin,
|
||||||
|
exploiter: NewCassandraExploiter(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置能力
|
||||||
|
plugin.SetCapabilities([]base.Capability{
|
||||||
|
base.CapWeakPassword,
|
||||||
|
base.CapDataExtraction,
|
||||||
|
base.CapInformationLeak,
|
||||||
|
})
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan 重写扫描方法以支持自动利用
|
||||||
|
func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
// 如果禁用暴力破解,只进行服务识别
|
||||||
|
if common.DisableBrute {
|
||||||
|
return p.performServiceIdentification(ctx, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行基础的密码扫描
|
||||||
|
result, err := p.ServicePlugin.Scan(ctx, info)
|
||||||
|
if err != nil || !result.Success {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录成功的弱密码/未授权访问发现
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
cred := result.Credentials[0]
|
||||||
|
|
||||||
|
if cred.Username == "" && cred.Password == "" {
|
||||||
|
// 未授权访问
|
||||||
|
common.LogSuccess(i18n.GetText("plugin_unauthorized_access", "Cassandra", target))
|
||||||
|
} else {
|
||||||
|
// 弱密码
|
||||||
|
common.LogSuccess(i18n.GetText("plugin_login_success", "Cassandra", target, cred.Username, cred.Password))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动利用功能(可通过-ne参数禁用)
|
||||||
|
if result.Success && len(result.Credentials) > 0 && !common.DisableExploit {
|
||||||
|
p.autoExploit(context.Background(), info, result.Credentials[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCredentials 生成Cassandra凭据
|
||||||
|
func (p *CassandraPlugin) generateCredentials() []*base.Credential {
|
||||||
|
// 获取Cassandra专用的用户名字典
|
||||||
|
usernames := common.Userdict["cassandra"]
|
||||||
|
if len(usernames) == 0 {
|
||||||
|
// 默认Cassandra用户名(包含空用户名用于测试未授权访问)
|
||||||
|
usernames = []string{"", "cassandra", "admin", "root", "user"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成凭据组合,包括空密码测试未授权访问
|
||||||
|
var credentials []*base.Credential
|
||||||
|
|
||||||
|
// 首先测试未授权访问(空用户名和密码)
|
||||||
|
credentials = append(credentials, &base.Credential{
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
// 然后生成常规用户名密码组合
|
||||||
|
regularCreds := base.GenerateCredentials(usernames, common.Passwords)
|
||||||
|
credentials = append(credentials, regularCreds...)
|
||||||
|
|
||||||
|
return credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
// autoExploit 自动利用功能
|
||||||
|
func (p *CassandraPlugin) autoExploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
common.LogDebug(i18n.GetText("plugin_exploit_start", "Cassandra", target))
|
||||||
|
|
||||||
|
// 执行利用操作
|
||||||
|
result, err := p.exploiter.Exploit(ctx, info, creds)
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(i18n.GetText("plugin_exploit_failed", "Cassandra", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理利用结果
|
||||||
|
if result != nil && result.Success {
|
||||||
|
// SaveExploitResult会自动使用LogSuccess显示红色利用成功消息
|
||||||
|
base.SaveExploitResult(info, result, "Cassandra")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exploit 使用exploiter执行利用
|
||||||
|
func (p *CassandraPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
|
||||||
|
return p.exploiter.Exploit(ctx, info, creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExploitMethods 获取利用方法
|
||||||
|
func (p *CassandraPlugin) GetExploitMethods() []base.ExploitMethod {
|
||||||
|
return p.exploiter.GetExploitMethods()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExploitSupported 检查利用支持
|
||||||
|
func (p *CassandraPlugin) IsExploitSupported(method base.ExploitType) bool {
|
||||||
|
return p.exploiter.IsExploitSupported(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// performServiceIdentification 执行Cassandra服务识别(-nobr模式)
|
||||||
|
func (p *CassandraPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 尝试连接到Cassandra服务进行识别
|
||||||
|
cassandraInfo, isCassandra := p.identifyCassandraService(ctx, info)
|
||||||
|
if isCassandra {
|
||||||
|
// 记录服务识别成功
|
||||||
|
common.LogSuccess(i18n.GetText("cassandra_service_identified", target, cassandraInfo))
|
||||||
|
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: true,
|
||||||
|
Service: "Cassandra",
|
||||||
|
Banner: cassandraInfo,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
"service": "Cassandra",
|
||||||
|
"port": info.Ports,
|
||||||
|
"info": cassandraInfo,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法识别为Cassandra,返回失败
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: fmt.Errorf("无法识别为Cassandra服务"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifyCassandraService 通过连接识别Cassandra服务
|
||||||
|
func (p *CassandraPlugin) identifyCassandraService(ctx context.Context, info *common.HostInfo) (string, bool) {
|
||||||
|
// 尝试建立简单的TCP连接
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// 对于Cassandra native protocol (9042),尝试发送OPTIONS frame
|
||||||
|
if info.Ports == "9042" {
|
||||||
|
return p.identifyNativeProtocol(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用端口检测(其他端口)
|
||||||
|
return p.identifyGenericCassandra(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifyNativeProtocol 识别Cassandra native protocol
|
||||||
|
func (p *CassandraPlugin) identifyNativeProtocol(conn net.Conn) (string, bool) {
|
||||||
|
// 设置读写超时
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
|
||||||
|
// Cassandra native protocol OPTIONS frame
|
||||||
|
// Frame format: version(1) + flags(1) + stream(2) + opcode(1) + length(4) + body
|
||||||
|
optionsFrame := []byte{
|
||||||
|
0x04, // Version 4
|
||||||
|
0x00, // Flags
|
||||||
|
0x00, 0x00, // Stream ID
|
||||||
|
0x05, // Opcode: OPTIONS
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Body length: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送OPTIONS请求
|
||||||
|
_, err := conn.Write(optionsFrame)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
response := make([]byte, 1024)
|
||||||
|
n, err := conn.Read(response)
|
||||||
|
if err != nil || n < 8 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查响应是否为有效的Cassandra协议响应
|
||||||
|
if n >= 8 && response[0] == 0x84 { // Response version
|
||||||
|
// 简单解析响应以获取支持的版本信息
|
||||||
|
return "Cassandra Native Protocol v4", true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifyGenericCassandra 通用Cassandra识别
|
||||||
|
func (p *CassandraPlugin) identifyGenericCassandra(conn net.Conn) (string, bool) {
|
||||||
|
// 设置超时
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
|
||||||
|
// 尝试读取任何初始数据
|
||||||
|
response := make([]byte, 512)
|
||||||
|
n, err := conn.Read(response)
|
||||||
|
|
||||||
|
if err == nil && n > 0 {
|
||||||
|
responseStr := string(response[:n])
|
||||||
|
// 检查响应中是否包含Cassandra相关信息
|
||||||
|
if strings.Contains(strings.ToLower(responseStr), "cassandra") {
|
||||||
|
return fmt.Sprintf("Cassandra服务: %s", strings.TrimSpace(responseStr)), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果端口开放但没有明确标识,仍然认为可能是Cassandra
|
||||||
|
return "Cassandra服务", true
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 插件注册
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// RegisterCassandraPlugin 注册Cassandra插件
|
||||||
|
func RegisterCassandraPlugin() {
|
||||||
|
factory := base.NewSimplePluginFactory(
|
||||||
|
&base.PluginMetadata{
|
||||||
|
Name: "cassandra",
|
||||||
|
Version: "2.0.0",
|
||||||
|
Author: "fscan-team",
|
||||||
|
Description: "Apache Cassandra服务扫描和利用插件",
|
||||||
|
Category: "service",
|
||||||
|
Ports: []int{9042}, // Cassandra Native Protocol
|
||||||
|
Protocols: []string{"tcp"},
|
||||||
|
Tags: []string{"cassandra", "nosql", "database", "bruteforce"},
|
||||||
|
},
|
||||||
|
func() base.Plugin {
|
||||||
|
return NewCassandraPlugin()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
base.GlobalPluginRegistry.Register("cassandra", factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动注册
|
||||||
|
func init() {
|
||||||
|
RegisterCassandraPlugin()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user