fscan/plugins/mssql.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

397 lines
10 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"
"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("未发现弱密码"),
}
}
// Exploit 执行MSSQL利用操作 - 实现数据库查询功能
func (p *MSSQLPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立MSSQL连接
db := p.testCredential(ctx, info, creds)
if db == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("MSSQL连接失败"),
}
}
defer db.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("MSSQL利用开始: %s (用户: %s)", target, creds.Username))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== MSSQL利用结果 - %s ===\n", target))
// 获取版本信息
if version := p.getVersion(db); version != "" {
output.WriteString(fmt.Sprintf("\n[版本信息]\n%s\n", version))
}
// 获取数据库列表
if databases := p.getDatabases(db); len(databases) > 0 {
output.WriteString(fmt.Sprintf("\n[数据库列表] (共%d个)\n", len(databases)))
for i, dbName := range databases {
if i >= 10 { // 限制显示前10个
output.WriteString("... (更多数据库)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", dbName))
}
}
// 获取表列表master数据库
if tables := p.getTables(db, "master"); len(tables) > 0 {
output.WriteString(fmt.Sprintf("\n[master数据库表] (共%d个)\n", len(tables)))
for i, table := range tables {
if i >= 5 { // 限制显示前5个表
output.WriteString("... (更多表)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", table))
}
}
// 获取用户列表
if users := p.getUsers(db); len(users) > 0 {
output.WriteString(fmt.Sprintf("\n[用户列表] (共%d个)\n", len(users)))
for i, user := range users {
if i >= 10 { // 限制显示前10个用户
output.WriteString("... (更多用户)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", user))
}
}
// 获取权限信息
if privileges := p.getPrivileges(db, creds.Username); privileges != "" {
output.WriteString(fmt.Sprintf("\n[用户权限]\n%s\n", privileges))
}
// 检查xp_cmdshell状态
if cmdshellStatus := p.checkXpCmdshell(db); cmdshellStatus != "" {
output.WriteString(fmt.Sprintf("\n[系统命令执行]\n%s\n", cmdshellStatus))
}
common.LogSuccess(fmt.Sprintf("MSSQL利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// 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()
})
}