mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 删除整个legacy插件系统(7794行代码) - 完成所有插件向单文件架构迁移 - 移除19个插件的虚假Exploit功能,只保留真实利用: * Redis: 文件写入、SSH密钥注入、计划任务 * SSH: 命令执行 * MS17010: EternalBlue漏洞利用 - 统一插件接口,简化架构复杂度 - 清理临时文件和备份文件 重构效果: - 代码行数: -7794行 - 插件文件数: 从3文件架构→单文件架构 - 真实利用插件: 从22个→3个 - 架构复杂度: 大幅简化
321 lines
8.0 KiB
Go
321 lines
8.0 KiB
Go
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()
|
||
})
|
||
} |