mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

将复杂的三文件插件架构(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 验证通过: 新系统编译运行正常,所有插件功能验证通过
320 lines
7.8 KiB
Go
320 lines
7.8 KiB
Go
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()
|
||
})
|
||
} |