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

新的目录结构更便于插件管理和维护。
2025-08-26 00:02:13 +08:00

212 lines
4.9 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"
"net"
"regexp"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// MySQLPlugin MySQL数据库弱密码扫描插件 - 单文件实现
type MySQLPlugin struct {
name string
ports []int
}
// NewMySQLPlugin 创建MySQL插件
func NewMySQLPlugin() *MySQLPlugin {
return &MySQLPlugin{
name: "mysql",
ports: []int{3306, 3307, 33060},
}
}
// GetName 实现Plugin接口
func (p *MySQLPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *MySQLPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行MySQL扫描 - 核心功能实现
func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(info)
}
// 生成测试凭据
credentials := GenerateCredentials("mysql")
if len(credentials) == 0 {
return &ScanResult{
Success: false,
Service: "mysql",
Error: fmt.Errorf("没有可用的测试凭据"),
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "mysql",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// 弱密码发现成功
common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "mysql",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "mysql",
Error: fmt.Errorf("未发现弱密码"),
}
}
// testCredential 测试单个凭据 - 核心认证逻辑
func (p *MySQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
// 构建连接字符串
connStr := p.buildConnectionString(info.Host, info.Ports, cred.Username, cred.Password)
// 创建数据库连接
db, err := sql.Open("mysql", connStr)
if err != nil {
return false
}
defer db.Close()
// 设置连接超时
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// 测试连接 - 使用Context超时控制
err = db.PingContext(ctx)
return err == nil
}
// buildConnectionString 构建MySQL连接字符串
func (p *MySQLPlugin) buildConnectionString(host, port, username, password string) string {
// 支持SOCKS代理
if common.Socks5Proxy != "" {
// 如果使用代理,需要注册自定义拨号器
p.registerProxyDialer()
return fmt.Sprintf("%s:%s@tcp-proxy(%s:%s)/mysql?charset=utf8&timeout=%ds",
username, password, host, port, common.Timeout)
}
// 标准TCP连接
return fmt.Sprintf("%s:%s@tcp(%s:%s)/mysql?charset=utf8&timeout=%ds",
username, password, host, port, common.Timeout)
}
// identifyService 服务识别 - 不进行暴力破解时的功能
func (p *MySQLPlugin) identifyService(info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试连接获取握手包
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return &ScanResult{
Success: false,
Service: "mysql",
Error: err,
}
}
defer conn.Close()
// 读取MySQL握手包
if banner := p.readMySQLBanner(conn); banner != "" {
common.LogSuccess(i18n.GetText("mysql_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "mysql",
Banner: banner,
}
}
return &ScanResult{
Success: false,
Service: "mysql",
Error: fmt.Errorf("无法识别为MySQL服务"),
}
}
// readMySQLBanner 读取MySQL服务器握手包
func (p *MySQLPlugin) readMySQLBanner(conn net.Conn) string {
// 设置读取超时
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
// 读取握手包
handshake := make([]byte, 512)
n, err := conn.Read(handshake)
if err != nil || n < 10 {
return ""
}
// 检查MySQL协议版本通常是10
if handshake[4] != 10 {
return ""
}
// 提取版本字符串
versionStart := 5
versionEnd := versionStart
for versionEnd < n && handshake[versionEnd] != 0 {
versionEnd++
}
if versionEnd <= versionStart {
return ""
}
versionStr := string(handshake[versionStart:versionEnd])
// 验证版本字符串格式
if regexp.MustCompile(`\d+\.\d+`).MatchString(versionStr) {
return fmt.Sprintf("MySQL %s", versionStr)
}
return ""
}
// registerProxyDialer 注册SOCKS代理拨号器
func (p *MySQLPlugin) registerProxyDialer() {
// TODO: 实现代理拨号器注册
// 这里简化处理实际需要注册到MySQL驱动
}
// init 自动注册插件
func init() {
RegisterPlugin("mysql", func() Plugin {
return NewMySQLPlugin()
})
}