fscan/plugins/services/mssql.go
ZacharyZcR 6cf5719e8a refactor: 彻底清理插件系统,消除虚假利用功能
- 删除整个legacy插件系统(7794行代码)
- 完成所有插件向单文件架构迁移
- 移除19个插件的虚假Exploit功能,只保留真实利用:
  * Redis: 文件写入、SSH密钥注入、计划任务
  * SSH: 命令执行
  * MS17010: EternalBlue漏洞利用
- 统一插件接口,简化架构复杂度
- 清理临时文件和备份文件

重构效果:
- 代码行数: -7794行
- 插件文件数: 从3文件架构→单文件架构
- 真实利用插件: 从22个→3个
- 架构复杂度: 大幅简化
2025-08-26 11:43:48 +08:00

321 lines
8.0 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 services
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
_ "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// MSSQLPlugin Microsoft SQL Server数据库扫描和利用插件 - 包含数据库查询利用功能
type MSSQLPlugin struct {
name string
ports []int
}
// NewMSSQLPlugin 创建MSSQL插件
func NewMSSQLPlugin() *MSSQLPlugin {
return &MSSQLPlugin{
name: "mssql",
ports: []int{1433, 1434}, // MSSQL端口
}
}
// GetName 实现Plugin接口
func (p *MSSQLPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *MSSQLPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行MSSQL扫描 - 弱密码检测
func (p *MSSQLPlugin) 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("mssql")
if len(credentials) == 0 {
// MSSQL默认凭据
credentials = []Credential{
{Username: "sa", Password: ""},
{Username: "sa", Password: "sa"},
{Username: "sa", Password: "password"},
{Username: "sa", Password: "admin"},
{Username: "sa", Password: "123456"},
{Username: "administrator", Password: "administrator"},
{Username: "admin", Password: "admin"},
{Username: "mssql", Password: "mssql"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "mssql",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if db := p.testCredential(ctx, info, cred); db != nil {
db.Close() // 关闭测试连接
// MSSQL认证成功
common.LogSuccess(i18n.GetText("mssql_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "mssql",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "mssql",
Error: fmt.Errorf("未发现弱密码"),
}
}
// testCredential 测试单个凭据 - 返回数据库连接或nil
func (p *MSSQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *sql.DB {
// 构建连接字符串
connStr := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=master;connection timeout=%d",
info.Host, cred.Username, cred.Password, info.Ports, common.Timeout)
// 打开数据库连接
db, err := sql.Open("mssql", connStr)
if err != nil {
return nil
}
// 设置连接参数
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// 创建超时上下文
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
// 测试连接
err = db.PingContext(pingCtx)
if err != nil {
db.Close()
return nil
}
return db
}
// getVersion 获取MSSQL版本信息
func (p *MSSQLPlugin) getVersion(db *sql.DB) string {
var version string
err := db.QueryRow("SELECT @@VERSION").Scan(&version)
if err != nil {
return ""
}
// 只返回第一行版本信息
lines := strings.Split(version, "\n")
if len(lines) > 0 {
return strings.TrimSpace(lines[0])
}
return version
}
// getDatabases 获取数据库列表
func (p *MSSQLPlugin) getDatabases(db *sql.DB) []string {
query := "SELECT name FROM sys.databases WHERE database_id > 4" // 排除系统数据库
rows, err := db.Query(query)
if err != nil {
// 如果查询失败,尝试查询所有数据库
rows, err = db.Query("SELECT name FROM sys.databases")
if err != nil {
return nil
}
}
defer rows.Close()
var databases []string
for rows.Next() {
var dbName string
if err := rows.Scan(&dbName); err == nil {
databases = append(databases, dbName)
}
}
return databases
}
// getTables 获取指定数据库的表列表
func (p *MSSQLPlugin) getTables(db *sql.DB, database string) []string {
query := fmt.Sprintf("SELECT TABLE_NAME FROM %s.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", database)
rows, err := db.Query(query)
if err != nil {
return nil
}
defer rows.Close()
var tables []string
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err == nil {
tables = append(tables, tableName)
}
}
return tables
}
// getUsers 获取用户列表
func (p *MSSQLPlugin) getUsers(db *sql.DB) []string {
query := "SELECT name FROM sys.server_principals WHERE type IN ('S', 'U') AND is_disabled = 0"
rows, err := db.Query(query)
if err != nil {
// 尝试备用查询
rows, err = db.Query("SELECT loginname FROM master.dbo.syslogins")
if err != nil {
return nil
}
}
defer rows.Close()
var users []string
for rows.Next() {
var userName string
if err := rows.Scan(&userName); err == nil {
users = append(users, userName)
}
}
return users
}
// getPrivileges 获取用户权限信息
func (p *MSSQLPlugin) getPrivileges(db *sql.DB, username string) string {
var privileges strings.Builder
// 检查是否为sysadmin
var isSysadmin int
err := db.QueryRow("SELECT IS_SRVROLEMEMBER('sysadmin', ?)", username).Scan(&isSysadmin)
if err == nil {
if isSysadmin == 1 {
privileges.WriteString("sysadmin权限: YES\n")
} else {
privileges.WriteString("sysadmin权限: NO\n")
}
}
// 检查其他服务器角色
serverRoles := []string{"securityadmin", "serveradmin", "setupadmin", "processadmin", "diskadmin", "dbcreator", "bulkadmin"}
for _, role := range serverRoles {
var hasRole int
err := db.QueryRow(fmt.Sprintf("SELECT IS_SRVROLEMEMBER('%s', ?)", role), username).Scan(&hasRole)
if err == nil && hasRole == 1 {
privileges.WriteString(fmt.Sprintf("%s权限: YES\n", role))
}
}
return privileges.String()
}
// checkXpCmdshell 检查xp_cmdshell状态
func (p *MSSQLPlugin) checkXpCmdshell(db *sql.DB) string {
// 检查xp_cmdshell是否启用
var configValue int
err := db.QueryRow("SELECT CAST(value as int) FROM sys.configurations WHERE name = 'xp_cmdshell'").Scan(&configValue)
if err != nil {
return "无法检查xp_cmdshell状态"
}
if configValue == 1 {
// 尝试执行一个简单的命令测试
var result sql.NullString
err = db.QueryRow("EXEC xp_cmdshell 'echo test'").Scan(&result)
if err == nil && result.Valid {
return "✅ xp_cmdshell已启用支持系统命令执行"
}
return "⚠️ xp_cmdshell已启用但无法执行命令"
}
return "❌ xp_cmdshell未启用"
}
// identifyService 服务识别 - 检测MSSQL服务
func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接MSSQL服务
connStr := fmt.Sprintf("server=%s;user id=invalid;password=invalid;port=%s;database=master;connection timeout=%d",
info.Host, info.Ports, common.Timeout)
db, err := sql.Open("mssql", connStr)
if err != nil {
return &ScanResult{
Success: false,
Service: "mssql",
Error: err,
}
}
defer db.Close()
// 尝试连接(即使认证失败,也能识别服务)
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
err = db.PingContext(pingCtx)
var banner string
if err != nil && (strings.Contains(strings.ToLower(err.Error()), "login failed") ||
strings.Contains(strings.ToLower(err.Error()), "mssql") ||
strings.Contains(strings.ToLower(err.Error()), "sql server")) {
banner = "Microsoft SQL Server数据库服务"
} else if err == nil {
banner = "Microsoft SQL Server (连接成功)"
} else {
return &ScanResult{
Success: false,
Service: "mssql",
Error: fmt.Errorf("无法识别为MSSQL服务"),
}
}
common.LogSuccess(i18n.GetText("mssql_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "mssql",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("mssql", func() Plugin {
return NewMSSQLPlugin()
})
}