fscan/plugins/services/mssql.go
ZacharyZcR e082e2bb59 refactor: 重组插件目录结构,提升管理直观性
将所有服务插件移动到plugins/services/目录下,使目录结构更加清晰直观:
• 创建plugins/services/目录统一管理服务扫描插件
• 添加init.go提供类型别名和函数导出
• 更新main.go导入路径
• 所有20个服务插件功能验证正常

新的目录结构更便于插件管理和维护。
2025-08-26 00:02:13 +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 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("未发现弱密码"),
}
}
// 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()
})
}