From a71092b514dc26a3c803c60d8d23c77a77b5bb3f Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 9 Aug 2025 11:46:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0Microsoft=20SQL=20Ser?= =?UTF-8?q?ver=E6=95=B0=E6=8D=AE=E5=BA=93=E4=B8=93=E4=B8=9A=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增MSSQL协议识别和弱密码检测 - 支持sa等管理员账户暴力破解 - 实现ServiceConnector三层架构模式 - 添加MSSQL专用国际化消息 - 支持SOCKS代理连接 - 自动注册到插件系统 --- Common/i18n/messages/plugins.go | 18 +++ Core/Registry.go | 1 + Plugins/services/mssql/connector.go | 210 ++++++++++++++++++++++++++++ Plugins/services/mssql/exploiter.go | 42 ++++++ Plugins/services/mssql/plugin.go | 199 ++++++++++++++++++++++++++ 5 files changed, 470 insertions(+) create mode 100644 Plugins/services/mssql/connector.go create mode 100644 Plugins/services/mssql/exploiter.go create mode 100644 Plugins/services/mssql/plugin.go diff --git a/Common/i18n/messages/plugins.go b/Common/i18n/messages/plugins.go index 0696212..9a4a371 100644 --- a/Common/i18n/messages/plugins.go +++ b/Common/i18n/messages/plugins.go @@ -700,4 +700,22 @@ var PluginMessages = map[string]map[string]string{ LangZH: "MongoDB认证失败: %v", LangEN: "MongoDB authentication failed: %v", }, + + // ========================= MSSQL插件消息 ========================= + "mssql_auth_success": { + LangZH: "MSSQL服务 %s 认证成功 %s:%s", + LangEN: "MSSQL service %s authentication successful %s:%s", + }, + "mssql_service_identified": { + LangZH: "MSSQL服务识别成功: %s - %s", + LangEN: "MSSQL service identified: %s - %s", + }, + "mssql_connection_failed": { + LangZH: "MSSQL连接失败: %v", + LangEN: "MSSQL connection failed: %v", + }, + "mssql_auth_failed": { + LangZH: "MSSQL认证失败 %s: %v", + LangEN: "MSSQL authentication failed %s: %v", + }, } \ No newline at end of file diff --git a/Core/Registry.go b/Core/Registry.go index 810f3d9..438b807 100644 --- a/Core/Registry.go +++ b/Core/Registry.go @@ -15,6 +15,7 @@ import ( _ "github.com/shadow1ng/fscan/plugins/services/memcached" _ "github.com/shadow1ng/fscan/plugins/services/modbus" _ "github.com/shadow1ng/fscan/plugins/services/mongodb" + _ "github.com/shadow1ng/fscan/plugins/services/mssql" _ "github.com/shadow1ng/fscan/plugins/services/mysql" _ "github.com/shadow1ng/fscan/plugins/services/redis" _ "github.com/shadow1ng/fscan/plugins/services/ssh" diff --git a/Plugins/services/mssql/connector.go b/Plugins/services/mssql/connector.go new file mode 100644 index 0000000..5d5abf4 --- /dev/null +++ b/Plugins/services/mssql/connector.go @@ -0,0 +1,210 @@ +package mssql + +import ( + "context" + "database/sql" + "fmt" + "net" + "strings" + "time" + + mssqlDriver "github.com/denisenkom/go-mssqldb" + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MSSQLProxyDialer 自定义MSSQL代理拨号器 +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) +} + +// MSSQLConnection MSSQL连接包装器 +type MSSQLConnection struct { + db *sql.DB + target string + info string +} + +// MSSQLConnector MSSQL连接器实现 +type MSSQLConnector struct{} + +// NewMSSQLConnector 创建MSSQL连接器 +func NewMSSQLConnector() *MSSQLConnector { + return &MSSQLConnector{} +} + +// Connect 连接到MSSQL服务器(不进行认证) +func (c *MSSQLConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + timeout := time.Duration(common.Timeout) * time.Second + + // 尝试建立连接但不进行认证,使用空凭据进行连接尝试 + db, dbInfo, err := c.createConnection(ctx, info.Host, info.Ports, "", "", timeout) + if err != nil { + // 检查是否是MSSQL服务相关错误 + if c.isMSSQLError(err) { + // 即使连接失败,但可以识别为MSSQL服务 + return &MSSQLConnection{ + db: nil, + target: target, + info: "Microsoft SQL Server (Service Detected)", + }, nil + } + return nil, err + } + + return &MSSQLConnection{ + db: db, + target: target, + info: dbInfo, + }, nil +} + +// Authenticate 使用凭据进行认证 +func (c *MSSQLConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { + mssqlConn, ok := conn.(*MSSQLConnection) + if !ok { + return fmt.Errorf("invalid connection type") + } + + // 解析目标地址 + parts := strings.Split(mssqlConn.target, ":") + if len(parts) != 2 { + return fmt.Errorf("invalid target format") + } + + host := parts[0] + port := parts[1] + timeout := time.Duration(common.Timeout) * time.Second + + // 使用提供的凭据创建新连接 + db, info, err := c.createConnection(ctx, host, port, cred.Username, cred.Password, timeout) + if err != nil { + return err + } + + // 更新连接信息 + if mssqlConn.db != nil { + mssqlConn.db.Close() + } + mssqlConn.db = db + mssqlConn.info = info + + return nil +} + +// Close 关闭连接 +func (c *MSSQLConnector) Close(conn interface{}) error { + if mssqlConn, ok := conn.(*MSSQLConnection); ok && mssqlConn.db != nil { + return mssqlConn.db.Close() + } + return nil +} + +// createConnection 创建MSSQL数据库连接 +func (c *MSSQLConnector) createConnection(ctx context.Context, host, port, username, password string, timeout time.Duration) (*sql.DB, string, error) { + // 构造连接字符串 + connStr := fmt.Sprintf( + "server=%s;user id=%s;password=%s;port=%s;encrypt=disable;timeout=%d", + host, username, password, port, int(timeout.Seconds()), + ) + + var db *sql.DB + var err error + + // 检查是否需要使用socks代理 + if common.Socks5Proxy != "" { + connector, connErr := mssqlDriver.NewConnector(connStr) + if connErr != nil { + return nil, "", connErr + } + + connector.Dialer = &MSSQLProxyDialer{ + timeout: timeout, + } + + db = sql.OpenDB(connector) + } else { + db, err = sql.Open("mssql", connStr) + if err != nil { + return nil, "", err + } + } + + // 设置连接参数 + db.SetConnMaxLifetime(timeout) + db.SetConnMaxIdleTime(timeout) + db.SetMaxIdleConns(0) + db.SetMaxOpenConns(1) + + // 创建ping上下文 + pingCtx, pingCancel := context.WithTimeout(ctx, timeout) + defer pingCancel() + + // 执行ping测试连接 + err = db.PingContext(pingCtx) + if err != nil { + db.Close() + return nil, "", err + } + + // 获取数据库信息 + info := c.getDatabaseInfo(db) + return db, info, nil +} + +// getDatabaseInfo 获取数据库版本信息 +func (c *MSSQLConnector) getDatabaseInfo(db *sql.DB) string { + query := "SELECT @@VERSION" + var version string + + err := db.QueryRow(query).Scan(&version) + if err != nil { + return "Microsoft SQL Server" + } + + // 提取版本信息的关键部分 + if strings.Contains(version, "Microsoft SQL Server") { + lines := strings.Split(version, "\n") + if len(lines) > 0 { + return strings.TrimSpace(lines[0]) + } + } + + return fmt.Sprintf("Microsoft SQL Server - %s", version) +} + +// isMSSQLError 检查是否是MSSQL相关错误 +func (c *MSSQLConnector) isMSSQLError(err error) bool { + if err == nil { + return false + } + + errorStr := strings.ToLower(err.Error()) + mssqlErrorIndicators := []string{ + "login failed", + "cannot open database", + "invalid object", + "mssql:", + "sql server", + "sqlserver:", + "database", + "authentication failed", + "server principal", + "user does not have permission", + "the login is from an untrusted domain", + } + + for _, indicator := range mssqlErrorIndicators { + if strings.Contains(errorStr, indicator) { + return true + } + } + + return false +} \ No newline at end of file diff --git a/Plugins/services/mssql/exploiter.go b/Plugins/services/mssql/exploiter.go new file mode 100644 index 0000000..2dd0a27 --- /dev/null +++ b/Plugins/services/mssql/exploiter.go @@ -0,0 +1,42 @@ +package mssql + +import ( + "context" + "fmt" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MSSQLExploiter MSSQL利用器实现 +type MSSQLExploiter struct{} + +// NewMSSQLExploiter 创建MSSQL利用器 +func NewMSSQLExploiter() *MSSQLExploiter { + return &MSSQLExploiter{} +} + +// Exploit 执行MSSQL利用 +func (e *MSSQLExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + // MSSQL插件主要用于服务识别和认证测试,不进行进一步利用 + return &base.ExploitResult{ + Success: false, + Error: fmt.Errorf("MSSQL插件不支持进一步利用"), + }, nil +} + +// GetExploitMethods 获取支持的利用方法 +func (e *MSSQLExploiter) GetExploitMethods() []base.ExploitMethod { + return []base.ExploitMethod{ + { + Name: "信息收集", + Type: base.ExploitDataExtraction, + Description: "收集MSSQL服务信息", + }, + } +} + +// IsExploitSupported 检查是否支持指定的利用类型 +func (e *MSSQLExploiter) IsExploitSupported(method base.ExploitType) bool { + return method == base.ExploitDataExtraction +} \ No newline at end of file diff --git a/Plugins/services/mssql/plugin.go b/Plugins/services/mssql/plugin.go new file mode 100644 index 0000000..3b79c51 --- /dev/null +++ b/Plugins/services/mssql/plugin.go @@ -0,0 +1,199 @@ +package mssql + +import ( + "context" + "fmt" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MSSQLPlugin MSSQL插件实现 +type MSSQLPlugin struct { + *base.ServicePlugin + exploiter *MSSQLExploiter +} + +// NewMSSQLPlugin 创建MSSQL插件 +func NewMSSQLPlugin() *MSSQLPlugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "mssql", + Version: "2.0.0", + Author: "fscan-team", + Description: "Microsoft SQL Server扫描和利用插件", + Category: "service", + Ports: []int{1433, 1434}, // 默认MSSQL端口 + Protocols: []string{"tcp"}, + Tags: []string{"mssql", "sqlserver", "database", "weak-password"}, + } + + // 创建连接器和服务插件 + connector := NewMSSQLConnector() + servicePlugin := base.NewServicePlugin(metadata, connector) + + // 创建MSSQL插件 + plugin := &MSSQLPlugin{ + ServicePlugin: servicePlugin, + exploiter: NewMSSQLExploiter(), + } + + // 设置能力 + plugin.SetCapabilities([]base.Capability{ + base.CapWeakPassword, + base.CapDataExtraction, + }) + + return plugin +} + +// Scan 重写扫描方法,进行MSSQL服务扫描 +func (p *MSSQLPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + // 如果禁用了暴力破解,只进行服务识别 + if common.DisableBrute { + return p.performServiceIdentification(ctx, info) + } + + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 生成凭据进行暴力破解 + credentials := p.generateCredentials() + + // 遍历凭据进行测试 + for _, cred := range credentials { + result, err := p.ScanCredential(ctx, info, cred) + if err == nil && result.Success { + // 认证成功 + common.LogSuccess(i18n.GetText("mssql_auth_success", target, cred.Username, cred.Password)) + + return &base.ScanResult{ + Success: true, + Service: "Microsoft SQL Server", + Credentials: []*base.Credential{cred}, + Banner: result.Banner, + Extra: map[string]interface{}{ + "service": "Microsoft SQL Server", + "port": info.Ports, + "username": cred.Username, + "password": cred.Password, + }, + }, nil + } + } + + // 所有凭据都失败,但可能识别到了MSSQL服务 + return p.performServiceIdentification(ctx, info) +} + +// generateCredentials 生成MSSQL凭据 +func (p *MSSQLPlugin) generateCredentials() []*base.Credential { + var credentials []*base.Credential + + // 获取MSSQL用户名字典 + usernames := common.Userdict["mssql"] + if len(usernames) == 0 { + usernames = []string{"sa", "admin", "administrator", "root", "mssql"} + } + + // 获取密码字典 + passwords := common.Passwords + if len(passwords) == 0 { + passwords = []string{"", "sa", "admin", "password", "123456", "root"} + } + + // 生成用户名密码组合 + for _, username := range usernames { + for _, password := range passwords { + // 替换密码中的用户名占位符 + actualPassword := strings.Replace(password, "{user}", username, -1) + + credentials = append(credentials, &base.Credential{ + Username: username, + Password: actualPassword, + }) + } + } + + return credentials +} + +// Exploit 使用exploiter执行利用 +func (p *MSSQLPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + return p.exploiter.Exploit(ctx, info, creds) +} + +// GetExploitMethods 获取利用方法 +func (p *MSSQLPlugin) GetExploitMethods() []base.ExploitMethod { + return p.exploiter.GetExploitMethods() +} + +// IsExploitSupported 检查利用支持 +func (p *MSSQLPlugin) IsExploitSupported(method base.ExploitType) bool { + return p.exploiter.IsExploitSupported(method) +} + +// performServiceIdentification 执行MSSQL服务识别(-nobr模式) +func (p *MSSQLPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 尝试识别MSSQL服务 + connector := NewMSSQLConnector() + conn, err := connector.Connect(ctx, info) + + if err == nil && conn != nil { + if mssqlConn, ok := conn.(*MSSQLConnection); ok { + // 记录服务识别成功 + common.LogSuccess(i18n.GetText("mssql_service_identified", target, mssqlConn.info)) + + connector.Close(conn) + return &base.ScanResult{ + Success: true, + Service: "Microsoft SQL Server", + Banner: mssqlConn.info, + Extra: map[string]interface{}{ + "service": "Microsoft SQL Server", + "port": info.Ports, + "info": mssqlConn.info, + }, + }, nil + } + } + + // 如果无法识别为MSSQL,返回失败 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("无法识别为MSSQL服务"), + }, nil +} + +// ============================================================================= +// 插件注册 +// ============================================================================= + +// RegisterMSSQLPlugin 注册MSSQL插件 +func RegisterMSSQLPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "mssql", + Version: "2.0.0", + Author: "fscan-team", + Description: "Microsoft SQL Server扫描和利用插件", + Category: "service", + Ports: []int{1433, 1434}, // 默认MSSQL端口 + Protocols: []string{"tcp"}, + Tags: []string{"mssql", "sqlserver", "database", "weak-password"}, + }, + func() base.Plugin { + return NewMSSQLPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("mssql", factory) +} + +// 自动注册 +func init() { + RegisterMSSQLPlugin() +} \ No newline at end of file