fscan/plugins/services/smb.go
ZacharyZcR 1febb54fe6 refactor: 重构SMB和LDAP插件使用统一发包控制
- 修改SMB插件,在testCredential和identifyService中添加发包控制
- 修改LDAP插件,在connectLDAP中添加发包控制和包计数
- 统一包计数逻辑,确保TCP连接成功和失败都正确计数
- 保持现有功能不变,提升网络操作一致性
2025-09-02 11:48:52 +00:00

247 lines
5.8 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"
"fmt"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins"
"github.com/stacktitan/smb/smb"
)
// SmbPlugin SMB弱密码检测插件
type SmbPlugin struct {
plugins.BasePlugin
}
// NewSmbPlugin 创建SMB插件
func NewSmbPlugin() *SmbPlugin {
return &SmbPlugin{
BasePlugin: plugins.NewBasePlugin("smb"),
}
}
// GetName 实现Plugin接口
// GetPorts 实现Plugin接口
// Scan 执行SMB扫描
func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 检查端口
if info.Ports != "445" {
return &ScanResult{
Success: false,
Service: "smb",
Error: fmt.Errorf("SMB插件仅支持445端口"),
}
}
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 生成测试凭据
credentials := GenerateCredentials("smb")
if len(credentials) == 0 {
// SMB默认凭据
credentials = []Credential{
{Username: "", Password: ""},
{Username: "administrator", Password: ""},
{Username: "administrator", Password: "admin"},
{Username: "administrator", Password: "password"},
{Username: "administrator", Password: "123456"},
{Username: "admin", Password: "admin"},
{Username: "guest", Password: ""},
{Username: "root", Password: ""},
{Username: "root", Password: "root"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "smb",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// SMB认证成功
var successMsg string
if common.Domain != "" {
successMsg = fmt.Sprintf("SMB %s 弱密码 %s\\%s:%s", target, common.Domain, cred.Username, cred.Password)
} else {
successMsg = fmt.Sprintf("SMB %s 弱密码 %s:%s", target, cred.Username, cred.Password)
}
common.LogSuccess(successMsg)
return &ScanResult{
Success: true,
Service: "smb",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "smb",
Error: fmt.Errorf("未发现弱密码"),
}
}
// testCredential 测试单个凭据
func (p *SmbPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
common.LogError(fmt.Sprintf("SMB连接 %s:%s 受限: %s", info.Host, info.Ports, reason))
return false
}
options := smb.Options{
Host: info.Host,
Port: 445,
User: cred.Username,
Password: cred.Password,
Domain: common.Domain,
Workstation: "",
}
// 设置超时
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
// 在协程中执行连接测试
resultChan := make(chan bool, 1)
go func() {
session, err := smb.NewSession(options, false)
if err == nil {
defer session.Close()
common.IncrementTCPSuccessPacketCount()
resultChan <- session.IsAuthenticated
} else {
common.IncrementTCPFailedPacketCount()
resultChan <- false
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
return result
case <-timeoutCtx.Done():
return false
case <-ctx.Done():
return false
}
}
// getShares 获取共享列表
func (p *SmbPlugin) getShares(ctx context.Context, info *common.HostInfo, creds Credential) []string {
options := smb.Options{
Host: info.Host,
Port: 445,
User: creds.Username,
Password: creds.Password,
Domain: common.Domain,
Workstation: "",
}
session, err := smb.NewSession(options, false)
if err != nil {
return nil
}
defer session.Close()
if !session.IsAuthenticated {
return nil
}
// 简化实现,返回常见共享列表
// 原SMB库可能不支持ListShares这里使用模拟实现
commonShares := []string{"ADMIN$", "C$", "IPC$", "Users", "Public"}
return commonShares
}
// testShareAccess 测试共享访问
func (p *SmbPlugin) testShareAccess(ctx context.Context, info *common.HostInfo, creds Credential, shareName string) bool {
options := smb.Options{
Host: info.Host,
Port: 445,
User: creds.Username,
Password: creds.Password,
Domain: common.Domain,
Workstation: "",
}
session, err := smb.NewSession(options, false)
if err != nil {
return false
}
defer session.Close()
if !session.IsAuthenticated {
return false
}
// 简化实现,假设管理员用户可以访问管理员共享
if creds.Username == "administrator" && (shareName == "ADMIN$" || shareName == "C$") {
return true
}
// 其他情况返回false
return false
}
// identifyService 服务识别
func (p *SmbPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
// 检查发包限制
if canSend, reason := common.CanSendPacket(); !canSend {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogError(fmt.Sprintf("SMB识别 %s 受限: %s", target, reason))
return &ScanResult{
Success: false,
Service: "smb",
Error: fmt.Errorf("发包受限: %s", reason),
}
}
if p.testCredential(ctx, info, Credential{Username: "", Password: ""}) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
banner := "SMB文件共享服务"
common.LogSuccess(fmt.Sprintf("SMB %s %s", target, banner))
return &ScanResult{
Success: true,
Service: "smb",
Banner: banner,
}
}
return &ScanResult{
Success: false,
Service: "smb",
Error: fmt.Errorf("无法识别为SMB服务"),
}
}
// init 自动注册插件
func init() {
// 使用高效注册方式:直接传递端口信息,避免实例创建
RegisterPluginWithPorts("smb", func() Plugin {
return NewSmbPlugin()
}, []int{445})
}