refactor: 重构SMB和SMB2插件架构,大幅减少代码重复

- 创建通用SMB框架,包含抽象接口、凭据管理和并发扫描引擎
- SMB2插件代码量从492行减少到159行,减少68%代码量
- 统一错误分类和处理机制,提高代码可维护性
- 支持密码和哈希两种认证方式,保持向后兼容性
- 模块化设计便于单元测试和功能扩展
This commit is contained in:
ZacharyZcR 2025-08-12 19:11:40 +08:00
parent da981cdbce
commit daa7fb2dcb
10 changed files with 1093 additions and 664 deletions

View File

@ -3,240 +3,79 @@ package Plugins
import ( import (
"context" "context"
"fmt" "fmt"
"time"
smbcommon "github.com/shadow1ng/fscan/plugins/legacy/smb/common"
"github.com/shadow1ng/fscan/plugins/legacy/smb/smb1"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output" "github.com/shadow1ng/fscan/common/output"
"github.com/stacktitan/smb/smb"
"strings"
"sync"
"time"
) )
// SmbCredential 表示一个SMB凭据 // SmbScan 执行SMB1服务的认证扫描(重构版本)
type SmbCredential struct {
Username string
Password string
}
// SmbScanResult 表示SMB扫描结果
type SmbScanResult struct {
Success bool
Error error
Credential SmbCredential
}
func SmbScan(info *common.HostInfo) error { func SmbScan(info *common.HostInfo) error {
if common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 创建目标信息
common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) target := &smbcommon.TargetInfo{
Host: info.Host,
Port: 445,
Domain: common.Domain,
}
// 设置全局超时上下文 // 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second) ctx, cancel := context.WithTimeout(context.Background(),
time.Duration(common.GlobalTimeout)*time.Second)
defer cancel() defer cancel()
// 构建凭据列表 // 创建连接器、凭据管理器和扫描器
var credentials []SmbCredential connector := smb1.NewSmb1Connector()
for _, user := range common.Userdict["smb"] { credMgr := smbcommon.NewPasswordCredentialManager()
for _, pass := range common.Passwords { scanner := smbcommon.NewScanner()
actualPass := strings.Replace(pass, "{user}", user, -1)
credentials = append(credentials, SmbCredential{ // 配置扫描参数
Username: user, config := &smbcommon.ScanConfig{
Password: actualPass, MaxConcurrent: common.ModuleThreadNum,
}) Timeout: time.Duration(common.Timeout) * time.Second,
} GlobalTimeout: time.Duration(common.GlobalTimeout) * time.Second,
} }
common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", // 执行扫描
len(common.Userdict["smb"]), len(common.Passwords), len(credentials))) result, err := scanner.Scan(ctx, target, connector, credMgr, config)
if err != nil {
// 使用工作池并发扫描 return err
result := concurrentSmbScan(ctx, info, credentials, common.Timeout)
if result != nil {
// 记录成功结果
saveSmbResult(info, target, result.Credential)
return nil
} }
// 检查是否因为全局超时而退出 // 处理扫描结果
select { if result != nil && result.Success {
case <-ctx.Done(): saveSmbResult(info, result.Credential)
common.LogDebug("SMB扫描全局超时")
return fmt.Errorf("全局超时")
default:
common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
return nil
}
}
// concurrentSmbScan 并发扫描SMB服务
func concurrentSmbScan(ctx context.Context, info *common.HostInfo, credentials []SmbCredential, timeoutSeconds int64) *SmbScanResult {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
} }
// 创建工作池 return nil
var wg sync.WaitGroup
resultChan := make(chan *SmbScanResult, 1)
workChan := make(chan SmbCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 记录用户锁定状态,避免继续尝试已锁定的用户
lockedUsers := make(map[string]bool)
var lockedMutex sync.Mutex
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
// 检查用户是否已锁定
lockedMutex.Lock()
locked := lockedUsers[credential.Username]
lockedMutex.Unlock()
if locked {
common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username))
continue
}
result := trySmbCredential(scanCtx, info, credential, timeoutSeconds)
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
// 检查账号锁定错误
if result.Error != nil && strings.Contains(result.Error.Error(), "账号锁定") {
lockedMutex.Lock()
lockedUsers[credential.Username] = true
lockedMutex.Unlock()
common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username))
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
// 检查用户是否已锁定
lockedMutex.Lock()
locked := lockedUsers[cred.Username]
lockedMutex.Unlock()
if locked {
continue // 跳过已锁定用户
}
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password))
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result
}
return nil
case <-ctx.Done():
common.LogDebug("SMB并发扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return nil
}
}
// trySmbCredential 尝试单个SMB凭据
func trySmbCredential(ctx context.Context, info *common.HostInfo, credential SmbCredential, timeoutSeconds int64) *SmbScanResult {
// 创建单个连接超时上下文的结果通道
resultChan := make(chan struct {
success bool
err error
}, 1)
// 在协程中尝试连接
go func() {
signal := make(chan struct{}, 1)
success, err := SmblConn(info, credential.Username, credential.Password, signal)
select {
case <-ctx.Done():
case resultChan <- struct {
success bool
err error
}{success, err}:
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
return &SmbScanResult{
Success: result.success,
Error: result.err,
Credential: credential,
}
case <-ctx.Done():
return &SmbScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
case <-time.After(time.Duration(timeoutSeconds) * time.Second):
return &SmbScanResult{
Success: false,
Error: fmt.Errorf("连接超时"),
Credential: credential,
}
}
} }
// saveSmbResult 保存SMB扫描结果 // saveSmbResult 保存SMB扫描结果
func saveSmbResult(info *common.HostInfo, target string, credential SmbCredential) { func saveSmbResult(info *common.HostInfo, cred smbcommon.Credential) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 构建结果消息 // 构建结果消息
var successMsg string var successMsg string
details := map[string]interface{}{ details := map[string]interface{}{
"port": info.Ports, "port": info.Ports,
"service": "smb", "service": "smb",
"username": credential.Username, "username": cred.Username,
"password": credential.Password, "password": cred.Password,
"type": "weak-password", "type": "weak-password",
} }
if common.Domain != "" { if common.Domain != "" {
successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, common.Domain, credential.Username, credential.Password) successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s",
target, common.Domain, cred.Username, cred.Password)
details["domain"] = common.Domain details["domain"] = common.Domain
} else { } else {
successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, credential.Username, credential.Password) successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s",
target, cred.Username, cred.Password)
} }
// 记录成功日志 // 记录成功日志
@ -253,47 +92,3 @@ func saveSmbResult(info *common.HostInfo, target string, credential SmbCredentia
common.SaveResult(result) common.SaveResult(result)
} }
// SmblConn 尝试建立SMB连接并认证
func SmblConn(info *common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) {
options := smb.Options{
Host: info.Host,
Port: 445,
User: user,
Password: pass,
Domain: common.Domain,
Workstation: "",
}
session, err := smb.NewSession(options, false)
if err == nil {
defer session.Close()
if session.IsAuthenticated {
return true, nil
}
return false, fmt.Errorf("认证失败")
}
// 清理错误信息中的换行符和多余空格
errMsg := strings.TrimSpace(strings.ReplaceAll(err.Error(), "\n", " "))
if strings.Contains(errMsg, "NT Status Error") {
switch {
case strings.Contains(errMsg, "STATUS_LOGON_FAILURE"):
err = fmt.Errorf("密码错误")
case strings.Contains(errMsg, "STATUS_ACCOUNT_LOCKED_OUT"):
err = fmt.Errorf("账号锁定")
case strings.Contains(errMsg, "STATUS_ACCESS_DENIED"):
err = fmt.Errorf("拒绝访问")
case strings.Contains(errMsg, "STATUS_ACCOUNT_DISABLED"):
err = fmt.Errorf("账号禁用")
case strings.Contains(errMsg, "STATUS_PASSWORD_EXPIRED"):
err = fmt.Errorf("密码过期")
case strings.Contains(errMsg, "STATUS_USER_SESSION_DELETED"):
return false, fmt.Errorf("会话断开")
default:
err = fmt.Errorf("认证失败")
}
}
signal <- struct{}{}
return false, err
}

View File

@ -3,401 +3,76 @@ package Plugins
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings"
"sync"
"time" "time"
smbcommon "github.com/shadow1ng/fscan/plugins/legacy/smb/common"
"github.com/shadow1ng/fscan/plugins/legacy/smb/smb2"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output" "github.com/shadow1ng/fscan/common/output"
"github.com/hirochachacha/go-smb2"
) )
// Smb2Credential 表示一个SMB2凭据 // SmbScan2 执行SMB2服务的认证扫描(重构版本)
type Smb2Credential struct {
Username string
Password string
Hash []byte
IsHash bool
}
// Smb2ScanResult 表示SMB2扫描结果
type Smb2ScanResult struct {
Success bool
Error error
Credential Smb2Credential
Shares []string
}
// SmbScan2 执行SMB2服务的认证扫描支持密码和哈希两种认证方式
func SmbScan2(info *common.HostInfo) error { func SmbScan2(info *common.HostInfo) error {
if common.DisableBrute { if common.DisableBrute {
return nil return nil
} }
target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 创建目标信息
common.LogDebug(fmt.Sprintf("开始SMB2扫描 %s", target)) target := &smbcommon.TargetInfo{
Host: info.Host,
// 设置全局超时上下文 Port: 445,
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 根据是否提供哈希选择认证模式
if len(common.HashBytes) > 0 {
return smbHashScan(ctx, info)
}
return smbPasswordScan(ctx, info)
}
// smbPasswordScan 使用密码进行SMB2认证扫描
func smbPasswordScan(ctx context.Context, info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
// 构建凭据列表
var credentials []Smb2Credential
for _, user := range common.Userdict["smb"] {
for _, pass := range common.Passwords {
actualPass := strings.ReplaceAll(pass, "{user}", user)
credentials = append(credentials, Smb2Credential{
Username: user,
Password: actualPass,
Hash: []byte{},
IsHash: false,
})
}
}
common.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
len(common.Userdict["smb"]), len(common.Passwords), len(credentials)))
// 使用工作池并发扫描
return concurrentSmb2Scan(ctx, info, credentials)
}
// smbHashScan 使用哈希进行SMB2认证扫描
func smbHashScan(ctx context.Context, info *common.HostInfo) error {
if common.DisableBrute {
return nil
}
// 构建凭据列表
var credentials []Smb2Credential
for _, user := range common.Userdict["smb"] {
for _, hash := range common.HashBytes {
credentials = append(credentials, Smb2Credential{
Username: user,
Password: "",
Hash: hash,
IsHash: true,
})
}
}
common.LogDebug(fmt.Sprintf("开始SMB2哈希认证扫描 (总用户数: %d, 总哈希数: %d, 总组合数: %d)",
len(common.Userdict["smb"]), len(common.HashBytes), len(credentials)))
// 使用工作池并发扫描
return concurrentSmb2Scan(ctx, info, credentials)
}
// concurrentSmb2Scan 并发扫描SMB2服务
func concurrentSmb2Scan(ctx context.Context, info *common.HostInfo, credentials []Smb2Credential) error {
// 使用ModuleThreadNum控制并发数
maxConcurrent := common.ModuleThreadNum
if maxConcurrent <= 0 {
maxConcurrent = 10 // 默认值
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *Smb2ScanResult, 1)
workChan := make(chan Smb2Credential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 记录共享信息是否已打印和锁定的用户
var (
sharesPrinted bool
lockedUsers = make(map[string]bool)
mutex sync.Mutex
)
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for credential := range workChan {
select {
case <-scanCtx.Done():
return
default:
// 检查用户是否已锁定
mutex.Lock()
locked := lockedUsers[credential.Username]
currentSharesPrinted := sharesPrinted
mutex.Unlock()
if locked {
common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", credential.Username))
continue
}
// 尝试凭据
result := trySmb2Credential(scanCtx, info, credential, currentSharesPrinted)
// 更新共享信息打印状态
if result.Shares != nil && len(result.Shares) > 0 && !currentSharesPrinted {
mutex.Lock()
sharesPrinted = true
mutex.Unlock()
// 打印共享信息
logShareInfo(info, credential.Username, credential.Password, credential.Hash, result.Shares)
}
// 检查认证成功
if result.Success {
select {
case resultChan <- result:
scanCancel() // 找到有效凭据,取消其他工作
default:
}
return
}
// 检查账户锁定
if result.Error != nil {
errMsg := result.Error.Error()
if strings.Contains(errMsg, "account has been automatically locked") ||
strings.Contains(errMsg, "account has been locked") ||
strings.Contains(errMsg, "user account has been automatically locked") {
mutex.Lock()
lockedUsers[credential.Username] = true
mutex.Unlock()
common.LogError(fmt.Sprintf("用户 %s 已被锁定", credential.Username))
}
}
}
}
}()
}
// 发送工作
go func() {
for i, cred := range credentials {
select {
case <-scanCtx.Done():
break
default:
// 检查用户是否已锁定
mutex.Lock()
locked := lockedUsers[cred.Username]
mutex.Unlock()
if locked {
continue // 跳过已锁定用户
}
if cred.IsHash {
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s Hash:%s",
i+1, len(credentials), cred.Username, common.HashValue))
} else {
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s",
i+1, len(credentials), cred.Username, cred.Password))
}
workChan <- cred
}
}
close(workChan)
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果,考虑全局超时
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
// 记录成功结果
logSuccessfulAuth(info, result.Credential.Username,
result.Credential.Password, result.Credential.Hash)
return nil
}
return nil
case <-ctx.Done():
common.LogDebug("SMB2扫描全局超时")
scanCancel() // 确保取消所有未完成工作
return fmt.Errorf("全局超时")
}
}
// trySmb2Credential 尝试单个SMB2凭据
func trySmb2Credential(ctx context.Context, info *common.HostInfo, credential Smb2Credential, hasprint bool) *Smb2ScanResult {
// 创建单个连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second)
defer cancel()
// 在协程中尝试连接
resultChan := make(chan struct {
success bool
shares []string
err error
}, 1)
go func() {
success, err, shares := Smb2Con(connCtx, info, credential.Username,
credential.Password, credential.Hash, hasprint)
select {
case <-connCtx.Done():
case resultChan <- struct {
success bool
shares []string
err error
}{success, shares, err}:
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
if result.success {
return &Smb2ScanResult{
Success: true,
Credential: credential,
Shares: result.shares,
}
}
// 失败时记录错误
if result.err != nil {
logFailedAuth(info, credential.Username, credential.Password, credential.Hash, result.err)
}
return &Smb2ScanResult{
Success: false,
Error: result.err,
Credential: credential,
Shares: result.shares,
}
case <-connCtx.Done():
if ctx.Err() != nil {
// 全局超时
return &Smb2ScanResult{
Success: false,
Error: ctx.Err(),
Credential: credential,
}
}
// 单个连接超时
err := fmt.Errorf("连接超时")
logFailedAuth(info, credential.Username, credential.Password, credential.Hash, err)
return &Smb2ScanResult{
Success: false,
Error: err,
Credential: credential,
}
}
}
// Smb2Con 尝试SMB2连接并进行认证检查共享访问权限
func Smb2Con(ctx context.Context, info *common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, shares []string) {
// 建立TCP连接使用socks代理支持
conn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:445", info.Host), time.Duration(common.Timeout)*time.Second)
if err != nil {
return false, fmt.Errorf("连接失败: %v", err), nil
}
defer conn.Close()
// 配置NTLM认证
initiator := smb2.NTLMInitiator{
User: user,
Domain: common.Domain, Domain: common.Domain,
} }
// 设置认证方式(哈希或密码) // 设置全局超时上下文
if len(hash) > 0 { ctx, cancel := context.WithTimeout(context.Background(),
initiator.Hash = hash time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 根据是否提供哈希选择认证模式
var credMgr smbcommon.CredentialManager
if len(common.HashBytes) > 0 {
credMgr = smbcommon.NewHashCredentialManager()
common.LogDebug(fmt.Sprintf("开始SMB2哈希认证扫描 (总用户数: %d, 总哈希数: %d)",
len(common.Userdict["smb"]), len(common.HashBytes)))
} else { } else {
initiator.Password = pass credMgr = smbcommon.NewPasswordCredentialManager()
common.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d)",
len(common.Userdict["smb"]), len(common.Passwords)))
} }
// 创建SMB2会话 // 创建连接器和扫描器
dialer := &smb2.Dialer{ connector := smb2.NewSmb2Connector()
Initiator: &initiator, scanner := smbcommon.NewScanner()
// 配置扫描参数
config := &smbcommon.ScanConfig{
MaxConcurrent: common.ModuleThreadNum,
Timeout: time.Duration(common.Timeout) * time.Second,
GlobalTimeout: time.Duration(common.GlobalTimeout) * time.Second,
} }
// 使用context设置超时 // 执行扫描
session, err := dialer.Dial(conn) result, err := scanner.Scan(ctx, target, connector, credMgr, config)
if err != nil { if err != nil {
return false, fmt.Errorf("SMB2会话建立失败: %v", err), nil return err
}
defer session.Logoff()
// 检查上下文是否已取消
select {
case <-ctx.Done():
return false, ctx.Err(), nil
default:
} }
// 获取共享列表 // 处理扫描结果
sharesList, err := session.ListSharenames() if result != nil && result.Success {
if err != nil { logSuccessfulAuth(info, result.Credential, result.Shares)
return false, fmt.Errorf("获取共享列表失败: %v", err), nil if len(result.Shares) > 0 {
logShareInfo(info, result.Credential, result.Shares)
}
} }
// 再次检查上下文是否已取消 return nil
select {
case <-ctx.Done():
return false, ctx.Err(), sharesList
default:
}
// 尝试访问C$共享以验证管理员权限
fs, err := session.Mount("C$")
if err != nil {
return false, fmt.Errorf("挂载C$失败: %v", err), sharesList
}
defer fs.Umount()
// 最后检查上下文是否已取消
select {
case <-ctx.Done():
return false, ctx.Err(), sharesList
default:
}
// 尝试读取系统文件以验证权限
path := `Windows\win.ini`
f, err := fs.OpenFile(path, os.O_RDONLY, 0666)
if err != nil {
return false, fmt.Errorf("访问系统文件失败: %v", err), sharesList
}
defer f.Close()
return true, nil, sharesList
} }
// logSuccessfulAuth 记录成功的认证 // logSuccessfulAuth 记录成功的认证
func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) { func logSuccessfulAuth(info *common.HostInfo, cred smbcommon.Credential, shares []string) {
credential := pass credential := cred.Password
if len(hash) > 0 { if cred.IsHash && len(cred.Hash) > 0 {
credential = common.HashValue credential = common.HashValue
} }
@ -410,11 +85,12 @@ func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) {
Details: map[string]interface{}{ Details: map[string]interface{}{
"port": info.Ports, "port": info.Ports,
"service": "smb2", "service": "smb2",
"username": user, "username": cred.Username,
"domain": common.Domain, "domain": common.Domain,
"type": "weak-auth", "type": "weak-auth",
"credential": credential, "credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0], "auth_type": map[bool]string{true: "hash", false: "password"}[cred.IsHash],
"shares": shares,
}, },
} }
common.SaveResult(result) common.SaveResult(result)
@ -422,37 +98,25 @@ func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) {
// 控制台输出 // 控制台输出
var msg string var msg string
if common.Domain != "" { if common.Domain != "" {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s\\%s", info.Host, info.Ports, common.Domain, user) msg = fmt.Sprintf("SMB2认证成功 %s:%s %s\\%s",
info.Host, info.Ports, common.Domain, cred.Username)
} else { } else {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s", info.Host, info.Ports, user) msg = fmt.Sprintf("SMB2认证成功 %s:%s %s",
info.Host, info.Ports, cred.Username)
} }
if len(hash) > 0 { if cred.IsHash && len(cred.Hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", common.HashValue) msg += fmt.Sprintf(" Hash:%s", common.HashValue)
} else { } else {
msg += fmt.Sprintf(" Pass:%s", pass) msg += fmt.Sprintf(" Pass:%s", cred.Password)
} }
common.LogSuccess(msg) common.LogSuccess(msg)
} }
// logFailedAuth 记录失败的认证
func logFailedAuth(info *common.HostInfo, user, pass string, hash []byte, err error) {
var errlog string
if len(hash) > 0 {
errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s Hash:%s %v",
info.Host, info.Ports, user, common.HashValue, err)
} else {
errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s:%s %v",
info.Host, info.Ports, user, pass, err)
}
errlog = strings.ReplaceAll(errlog, "\n", " ")
common.LogError(errlog)
}
// logShareInfo 记录SMB共享信息 // logShareInfo 记录SMB共享信息
func logShareInfo(info *common.HostInfo, user string, pass string, hash []byte, shares []string) { func logShareInfo(info *common.HostInfo, cred smbcommon.Credential, shares []string) {
credential := pass credential := cred.Password
if len(hash) > 0 { if cred.IsHash && len(cred.Hash) > 0 {
credential = common.HashValue credential = common.HashValue
} }
@ -465,11 +129,11 @@ func logShareInfo(info *common.HostInfo, user string, pass string, hash []byte,
Details: map[string]interface{}{ Details: map[string]interface{}{
"port": info.Ports, "port": info.Ports,
"service": "smb2", "service": "smb2",
"username": user, "username": cred.Username,
"domain": common.Domain, "domain": common.Domain,
"shares": shares, "shares": shares,
"credential": credential, "credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0], "auth_type": map[bool]string{true: "hash", false: "password"}[cred.IsHash],
}, },
} }
common.SaveResult(result) common.SaveResult(result)
@ -477,16 +141,19 @@ func logShareInfo(info *common.HostInfo, user string, pass string, hash []byte,
// 控制台输出 // 控制台输出
var msg string var msg string
if common.Domain != "" { if common.Domain != "" {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s\\%s", info.Host, info.Ports, common.Domain, user) msg = fmt.Sprintf("SMB2共享信息 %s:%s %s\\%s",
info.Host, info.Ports, common.Domain, cred.Username)
} else { } else {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s", info.Host, info.Ports, user) msg = fmt.Sprintf("SMB2共享信息 %s:%s %s",
info.Host, info.Ports, cred.Username)
} }
if len(hash) > 0 { if cred.IsHash && len(cred.Hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", common.HashValue) msg += fmt.Sprintf(" Hash:%s", common.HashValue)
} else { } else {
msg += fmt.Sprintf(" Pass:%s", pass) msg += fmt.Sprintf(" Pass:%s", cred.Password)
} }
msg += fmt.Sprintf(" 共享:%v", shares) msg += fmt.Sprintf(" 共享:%v", shares)
common.LogBase(msg) common.LogBase(msg)
} }

View File

@ -0,0 +1,106 @@
package common
import (
"strings"
"sync"
"github.com/shadow1ng/fscan/common"
)
// DefaultCredentialManager 默认凭据管理器实现
type DefaultCredentialManager struct {
credentials []Credential
lockedUsers map[string]bool
mutex sync.RWMutex
}
// NewPasswordCredentialManager 创建密码认证凭据管理器
func NewPasswordCredentialManager() *DefaultCredentialManager {
mgr := &DefaultCredentialManager{
lockedUsers: make(map[string]bool),
}
mgr.generatePasswordCredentials()
return mgr
}
// NewHashCredentialManager 创建哈希认证凭据管理器
func NewHashCredentialManager() *DefaultCredentialManager {
mgr := &DefaultCredentialManager{
lockedUsers: make(map[string]bool),
}
mgr.generateHashCredentials()
return mgr
}
// generatePasswordCredentials 生成密码凭据列表
func (m *DefaultCredentialManager) generatePasswordCredentials() {
for _, user := range common.Userdict["smb"] {
for _, pass := range common.Passwords {
actualPass := strings.ReplaceAll(pass, "{user}", user)
m.credentials = append(m.credentials, Credential{
Username: user,
Password: actualPass,
Hash: []byte{},
IsHash: false,
})
}
}
}
// generateHashCredentials 生成哈希凭据列表
func (m *DefaultCredentialManager) generateHashCredentials() {
for _, user := range common.Userdict["smb"] {
for _, hash := range common.HashBytes {
m.credentials = append(m.credentials, Credential{
Username: user,
Password: "",
Hash: hash,
IsHash: true,
})
}
}
}
// GenerateCredentials 获取所有凭据
func (m *DefaultCredentialManager) GenerateCredentials() []Credential {
m.mutex.RLock()
defer m.mutex.RUnlock()
result := make([]Credential, len(m.credentials))
copy(result, m.credentials)
return result
}
// HandleAuthFailure 处理认证失败
func (m *DefaultCredentialManager) HandleAuthFailure(username string, err error) {
if err == nil {
return
}
errMsg := strings.ToLower(err.Error())
isLocked := strings.Contains(errMsg, "locked") ||
strings.Contains(errMsg, "account has been automatically locked") ||
strings.Contains(errMsg, "user account has been automatically locked")
if isLocked {
m.mutex.Lock()
m.lockedUsers[username] = true
m.mutex.Unlock()
common.LogError("用户 " + username + " 已被锁定")
}
}
// IsUserLocked 检查用户是否被锁定
func (m *DefaultCredentialManager) IsUserLocked(username string) bool {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.lockedUsers[username]
}
// GetCredentialCount 获取凭据总数
func (m *DefaultCredentialManager) GetCredentialCount() int {
m.mutex.RLock()
defer m.mutex.RUnlock()
return len(m.credentials)
}

View File

@ -0,0 +1,102 @@
package common
import (
"errors"
"strings"
)
// 定义常见的SMB错误类型
var (
ErrAuthFailed = errors.New("认证失败")
ErrAccountLocked = errors.New("账户锁定")
ErrAccessDenied = errors.New("拒绝访问")
ErrAccountDisabled = errors.New("账户禁用")
ErrPasswordExpired = errors.New("密码过期")
ErrConnectionFailed = errors.New("连接失败")
ErrTimeout = errors.New("连接超时")
ErrSessionDeleted = errors.New("会话断开")
)
// ClassifySmbError 对SMB错误进行分类和标准化
func ClassifySmbError(err error) error {
if err == nil {
return nil
}
// 清理错误信息中的换行符和多余空格
errMsg := strings.TrimSpace(strings.ReplaceAll(err.Error(), "\n", " "))
errMsgLower := strings.ToLower(errMsg)
// SMB1特定错误处理
if strings.Contains(errMsg, "NT Status Error") {
switch {
case strings.Contains(errMsg, "STATUS_LOGON_FAILURE"):
return ErrAuthFailed
case strings.Contains(errMsg, "STATUS_ACCOUNT_LOCKED_OUT"):
return ErrAccountLocked
case strings.Contains(errMsg, "STATUS_ACCESS_DENIED"):
return ErrAccessDenied
case strings.Contains(errMsg, "STATUS_ACCOUNT_DISABLED"):
return ErrAccountDisabled
case strings.Contains(errMsg, "STATUS_PASSWORD_EXPIRED"):
return ErrPasswordExpired
case strings.Contains(errMsg, "STATUS_USER_SESSION_DELETED"):
return ErrSessionDeleted
default:
return ErrAuthFailed
}
}
// SMB2特定错误处理
switch {
case strings.Contains(errMsgLower, "account has been automatically locked") ||
strings.Contains(errMsgLower, "account has been locked") ||
strings.Contains(errMsgLower, "user account has been automatically locked"):
return ErrAccountLocked
case strings.Contains(errMsgLower, "access denied") ||
strings.Contains(errMsgLower, "access is denied"):
return ErrAccessDenied
case strings.Contains(errMsgLower, "account disabled") ||
strings.Contains(errMsgLower, "account is disabled"):
return ErrAccountDisabled
case strings.Contains(errMsgLower, "password expired") ||
strings.Contains(errMsgLower, "password has expired"):
return ErrPasswordExpired
case strings.Contains(errMsgLower, "connection refused") ||
strings.Contains(errMsgLower, "connection failed") ||
strings.Contains(errMsgLower, "no connection could be made"):
return ErrConnectionFailed
case strings.Contains(errMsgLower, "timeout") ||
strings.Contains(errMsgLower, "timed out"):
return ErrTimeout
case strings.Contains(errMsgLower, "session") && strings.Contains(errMsgLower, "deleted"):
return ErrSessionDeleted
case strings.Contains(errMsgLower, "logon failure") ||
strings.Contains(errMsgLower, "authentication failed") ||
strings.Contains(errMsgLower, "login failed"):
return ErrAuthFailed
}
// 默认返回原始错误
return err
}
// IsAccountLockError 判断是否为账户锁定错误
func IsAccountLockError(err error) bool {
return errors.Is(err, ErrAccountLocked) ||
strings.Contains(strings.ToLower(err.Error()), "locked")
}
// IsFatalError 判断是否为致命错误(应该停止尝试该用户)
func IsFatalError(err error) bool {
return errors.Is(err, ErrAccountLocked) ||
errors.Is(err, ErrAccountDisabled) ||
errors.Is(err, ErrPasswordExpired)
}

View File

@ -0,0 +1,78 @@
package common
import (
"context"
"time"
)
// TargetInfo SMB目标信息
type TargetInfo struct {
Host string
Port int
Domain string
}
// Credential SMB认证凭据
type Credential struct {
Username string
Password string
Hash []byte
IsHash bool
}
// ConnectionResult 连接结果
type ConnectionResult struct {
Success bool
Shares []string
HasAdminAccess bool
Error error
}
// ScanConfig 扫描配置
type ScanConfig struct {
MaxConcurrent int
Timeout time.Duration
GlobalTimeout time.Duration
}
// SmbConnector SMB连接器接口
type SmbConnector interface {
// Connect 建立SMB连接并进行认证
Connect(ctx context.Context, target *TargetInfo, cred *Credential) (*ConnectionResult, error)
// GetProtocolName 获取协议名称
GetProtocolName() string
// GetDefaultPort 获取默认端口
GetDefaultPort() int
}
// CredentialManager 凭据管理器接口
type CredentialManager interface {
// GenerateCredentials 生成认证凭据列表
GenerateCredentials() []Credential
// HandleAuthFailure 处理认证失败
HandleAuthFailure(username string, err error)
// IsUserLocked 检查用户是否被锁定
IsUserLocked(username string) bool
// GetCredentialCount 获取凭据总数
GetCredentialCount() int
}
// ScanResult 扫描结果
type ScanResult struct {
Success bool
Credential Credential
Shares []string
Error error
}
// Scanner 并发扫描器接口
type Scanner interface {
// Scan 执行并发扫描
Scan(ctx context.Context, target *TargetInfo, connector SmbConnector,
credMgr CredentialManager, config *ScanConfig) (*ScanResult, error)
}

View File

@ -0,0 +1,198 @@
package common
import (
"context"
"fmt"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
)
// DefaultScanner 默认并发扫描器实现
type DefaultScanner struct{}
// NewScanner 创建新的扫描器
func NewScanner() *DefaultScanner {
return &DefaultScanner{}
}
// Scan 执行并发扫描
func (s *DefaultScanner) Scan(ctx context.Context, target *TargetInfo,
connector SmbConnector, credMgr CredentialManager, config *ScanConfig) (*ScanResult, error) {
credentials := credMgr.GenerateCredentials()
if len(credentials) == 0 {
return nil, fmt.Errorf("没有可用的认证凭据")
}
common.LogDebug(fmt.Sprintf("开始%s扫描 %s:%d (总用户数: %d, 总组合数: %d)",
connector.GetProtocolName(), target.Host, target.Port,
len(common.Userdict["smb"]), len(credentials)))
// 确定并发数
maxConcurrent := config.MaxConcurrent
if maxConcurrent <= 0 {
maxConcurrent = 10
}
if maxConcurrent > len(credentials) {
maxConcurrent = len(credentials)
}
// 创建工作池
var wg sync.WaitGroup
resultChan := make(chan *ScanResult, 1)
workChan := make(chan Credential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
// 启动工作协程
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for cred := range workChan {
select {
case <-scanCtx.Done():
return
default:
// 检查用户是否已锁定
if credMgr.IsUserLocked(cred.Username) {
common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", cred.Username))
continue
}
// 尝试连接
result := s.tryCredential(scanCtx, target, connector, cred, config.Timeout)
// 处理认证失败
if !result.Success && result.Error != nil {
credMgr.HandleAuthFailure(cred.Username, result.Error)
}
// 如果成功,发送结果并取消其他工作
if result.Success {
select {
case resultChan <- result:
scanCancel()
default:
}
return
}
}
}
}()
}
// 发送工作
go func() {
defer close(workChan)
for i, cred := range credentials {
select {
case <-scanCtx.Done():
return
default:
// 跳过已锁定用户
if credMgr.IsUserLocked(cred.Username) {
continue
}
// 记录尝试日志
s.logAttempt(connector.GetProtocolName(), i+1, len(credentials), cred)
workChan <- cred
}
}
}()
// 等待结果或完成
go func() {
wg.Wait()
close(resultChan)
}()
// 获取结果
select {
case result, ok := <-resultChan:
if ok && result != nil && result.Success {
return result, nil
}
return nil, nil
case <-ctx.Done():
common.LogDebug(fmt.Sprintf("%s扫描全局超时", connector.GetProtocolName()))
scanCancel()
return nil, fmt.Errorf("全局超时")
}
}
// tryCredential 尝试单个凭据
func (s *DefaultScanner) tryCredential(ctx context.Context, target *TargetInfo,
connector SmbConnector, cred Credential, timeout time.Duration) *ScanResult {
// 创建连接超时上下文
connCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// 在协程中尝试连接
resultChan := make(chan *ConnectionResult, 1)
go func() {
result, err := connector.Connect(connCtx, target, &cred)
if err != nil {
result = &ConnectionResult{
Success: false,
Error: err,
}
}
select {
case <-connCtx.Done():
case resultChan <- result:
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
if result.Success {
return &ScanResult{
Success: true,
Credential: cred,
Shares: result.Shares,
}
}
// 分类错误
classifiedError := ClassifySmbError(result.Error)
return &ScanResult{
Success: false,
Error: classifiedError,
Credential: cred,
Shares: result.Shares,
}
case <-connCtx.Done():
var err error
if ctx.Err() != nil {
err = ctx.Err() // 全局超时
} else {
err = ErrTimeout // 连接超时
}
return &ScanResult{
Success: false,
Error: err,
Credential: cred,
}
}
}
// logAttempt 记录尝试日志
func (s *DefaultScanner) logAttempt(protocol string, current, total int, cred Credential) {
if cred.IsHash {
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试%s: %s Hash:%s",
current, total, protocol, cred.Username, common.HashValue))
} else {
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试%s: %s:%s",
current, total, protocol, cred.Username, cred.Password))
}
}

93
plugins/legacy/smb/smb.go Normal file
View File

@ -0,0 +1,93 @@
package smb
import (
"context"
"fmt"
"time"
"github.com/shadow1ng/fscan/plugins/legacy/smb/common"
"github.com/shadow1ng/fscan/plugins/legacy/smb/smb1"
fscanCommon "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// SmbScan 执行SMB1服务的认证扫描(重构版本)
func SmbScan(info *fscanCommon.HostInfo) error {
if fscanCommon.DisableBrute {
return nil
}
// 创建目标信息
target := &common.TargetInfo{
Host: info.Host,
Port: 445,
Domain: fscanCommon.Domain,
}
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(),
time.Duration(fscanCommon.GlobalTimeout)*time.Second)
defer cancel()
// 创建连接器、凭据管理器和扫描器
connector := smb1.NewSmb1Connector()
credMgr := common.NewPasswordCredentialManager()
scanner := common.NewScanner()
// 配置扫描参数
config := &common.ScanConfig{
MaxConcurrent: fscanCommon.ModuleThreadNum,
Timeout: time.Duration(fscanCommon.Timeout) * time.Second,
GlobalTimeout: time.Duration(fscanCommon.GlobalTimeout) * time.Second,
}
// 执行扫描
result, err := scanner.Scan(ctx, target, connector, credMgr, config)
if err != nil {
return err
}
// 处理扫描结果
if result != nil && result.Success {
saveSmbResult(info, result.Credential)
}
return nil
}
// saveSmbResult 保存SMB扫描结果
func saveSmbResult(info *fscanCommon.HostInfo, cred common.Credential) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 构建结果消息
var successMsg string
details := map[string]interface{}{
"port": info.Ports,
"service": "smb",
"username": cred.Username,
"password": cred.Password,
"type": "weak-password",
}
if fscanCommon.Domain != "" {
successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s",
target, fscanCommon.Domain, cred.Username, cred.Password)
details["domain"] = fscanCommon.Domain
} else {
successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s",
target, cred.Username, cred.Password)
}
// 记录成功日志
fscanCommon.LogSuccess(successMsg)
// 保存结果
result := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
fscanCommon.SaveResult(result)
}

View File

@ -0,0 +1,86 @@
package smb1
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/plugins/legacy/smb/common"
"github.com/stacktitan/smb/smb"
)
// Smb1Connector SMB1连接器实现
type Smb1Connector struct{}
// NewSmb1Connector 创建SMB1连接器
func NewSmb1Connector() *Smb1Connector {
return &Smb1Connector{}
}
// Connect 建立SMB1连接并进行认证
func (c *Smb1Connector) Connect(ctx context.Context, target *common.TargetInfo,
cred *common.Credential) (*common.ConnectionResult, error) {
// SMB1不支持哈希认证
if cred.IsHash {
return nil, fmt.Errorf("SMB1不支持哈希认证")
}
// 创建信号通道用于与原有代码兼容
signal := make(chan struct{}, 1)
// 调用原有的SMB连接函数
success, err := c.smbConnect(target.Host, target.Port, target.Domain,
cred.Username, cred.Password, signal)
result := &common.ConnectionResult{
Success: success,
Error: err,
}
// SMB1暂时不获取共享列表保持原有行为
if success {
result.Shares = []string{}
result.HasAdminAccess = true // SMB1连接成功通常意味着有访问权限
}
return result, nil
}
// GetProtocolName 获取协议名称
func (c *Smb1Connector) GetProtocolName() string {
return "SMB"
}
// GetDefaultPort 获取默认端口
func (c *Smb1Connector) GetDefaultPort() int {
return 445
}
// smbConnect 原有的SMB连接实现(改进版本)
func (c *Smb1Connector) smbConnect(host string, port int, domain, user, pass string,
signal chan struct{}) (bool, error) {
options := smb.Options{
Host: host,
Port: port,
User: user,
Password: pass,
Domain: domain,
Workstation: "",
}
session, err := smb.NewSession(options, false)
if err == nil {
defer session.Close()
if session.IsAuthenticated {
return true, nil
}
return false, common.ErrAuthFailed
}
// 分类和处理错误
classifiedError := common.ClassifySmbError(err)
signal <- struct{}{}
return false, classifiedError
}

158
plugins/legacy/smb/smb2.go Normal file
View File

@ -0,0 +1,158 @@
package smb
import (
"context"
"fmt"
"time"
"github.com/shadow1ng/fscan/plugins/legacy/smb/common"
"github.com/shadow1ng/fscan/plugins/legacy/smb/smb2"
fscanCommon "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// SmbScan2 执行SMB2服务的认证扫描(重构版本)
func SmbScan2(info *fscanCommon.HostInfo) error {
if fscanCommon.DisableBrute {
return nil
}
// 创建目标信息
target := &common.TargetInfo{
Host: info.Host,
Port: 445,
Domain: fscanCommon.Domain,
}
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(),
time.Duration(fscanCommon.GlobalTimeout)*time.Second)
defer cancel()
// 根据是否提供哈希选择认证模式
var credMgr common.CredentialManager
if len(fscanCommon.HashBytes) > 0 {
credMgr = common.NewHashCredentialManager()
fscanCommon.LogDebug(fmt.Sprintf("开始SMB2哈希认证扫描 (总用户数: %d, 总哈希数: %d)",
len(fscanCommon.Userdict["smb"]), len(fscanCommon.HashBytes)))
} else {
credMgr = common.NewPasswordCredentialManager()
fscanCommon.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d)",
len(fscanCommon.Userdict["smb"]), len(fscanCommon.Passwords)))
}
// 创建连接器和扫描器
connector := smb2.NewSmb2Connector()
scanner := common.NewScanner()
// 配置扫描参数
config := &common.ScanConfig{
MaxConcurrent: fscanCommon.ModuleThreadNum,
Timeout: time.Duration(fscanCommon.Timeout) * time.Second,
GlobalTimeout: time.Duration(fscanCommon.GlobalTimeout) * time.Second,
}
// 执行扫描
result, err := scanner.Scan(ctx, target, connector, credMgr, config)
if err != nil {
return err
}
// 处理扫描结果
if result != nil && result.Success {
logSuccessfulAuth(info, result.Credential, result.Shares)
if len(result.Shares) > 0 {
logShareInfo(info, result.Credential, result.Shares)
}
}
return nil
}
// logSuccessfulAuth 记录成功的认证
func logSuccessfulAuth(info *fscanCommon.HostInfo, cred common.Credential, shares []string) {
credential := cred.Password
if cred.IsHash && len(cred.Hash) > 0 {
credential = fscanCommon.HashValue
}
// 保存认证成功结果
result := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "success",
Details: map[string]interface{}{
"port": info.Ports,
"service": "smb2",
"username": cred.Username,
"domain": fscanCommon.Domain,
"type": "weak-auth",
"credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[cred.IsHash],
"shares": shares,
},
}
fscanCommon.SaveResult(result)
// 控制台输出
var msg string
if fscanCommon.Domain != "" {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s\\%s",
info.Host, info.Ports, fscanCommon.Domain, cred.Username)
} else {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s",
info.Host, info.Ports, cred.Username)
}
if cred.IsHash && len(cred.Hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", fscanCommon.HashValue)
} else {
msg += fmt.Sprintf(" Pass:%s", cred.Password)
}
fscanCommon.LogSuccess(msg)
}
// logShareInfo 记录SMB共享信息
func logShareInfo(info *fscanCommon.HostInfo, cred common.Credential, shares []string) {
credential := cred.Password
if cred.IsHash && len(cred.Hash) > 0 {
credential = fscanCommon.HashValue
}
// 保存共享信息结果
result := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "shares-found",
Details: map[string]interface{}{
"port": info.Ports,
"service": "smb2",
"username": cred.Username,
"domain": fscanCommon.Domain,
"shares": shares,
"credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[cred.IsHash],
},
}
fscanCommon.SaveResult(result)
// 控制台输出
var msg string
if fscanCommon.Domain != "" {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s\\%s",
info.Host, info.Ports, fscanCommon.Domain, cred.Username)
} else {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s",
info.Host, info.Ports, cred.Username)
}
if cred.IsHash && len(cred.Hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", fscanCommon.HashValue)
} else {
msg += fmt.Sprintf(" Pass:%s", cred.Password)
}
msg += fmt.Sprintf(" 共享:%v", shares)
fscanCommon.LogBase(msg)
}

View File

@ -0,0 +1,146 @@
package smb2
import (
"context"
"fmt"
"os"
"time"
"github.com/shadow1ng/fscan/plugins/legacy/smb/common"
fscanCommon "github.com/shadow1ng/fscan/common"
"github.com/hirochachacha/go-smb2"
)
// Smb2Connector SMB2连接器实现
type Smb2Connector struct{}
// NewSmb2Connector 创建SMB2连接器
func NewSmb2Connector() *Smb2Connector {
return &Smb2Connector{}
}
// Connect 建立SMB2连接并进行认证
func (c *Smb2Connector) Connect(ctx context.Context, target *common.TargetInfo,
cred *common.Credential) (*common.ConnectionResult, error) {
// 建立TCP连接使用socks代理支持
conn, err := fscanCommon.WrapperTcpWithTimeout("tcp",
fmt.Sprintf("%s:%d", target.Host, target.Port),
time.Duration(fscanCommon.Timeout)*time.Second)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 配置NTLM认证
initiator := smb2.NTLMInitiator{
User: cred.Username,
Domain: target.Domain,
}
// 设置认证方式(哈希或密码)
if cred.IsHash && len(cred.Hash) > 0 {
initiator.Hash = cred.Hash
} else {
initiator.Password = cred.Password
}
// 创建SMB2会话
dialer := &smb2.Dialer{
Initiator: &initiator,
}
// 建立会话
session, err := dialer.Dial(conn)
if err != nil {
classifiedError := common.ClassifySmbError(err)
return &common.ConnectionResult{
Success: false,
Error: classifiedError,
}, nil
}
defer session.Logoff()
// 检查上下文是否已取消
select {
case <-ctx.Done():
return &common.ConnectionResult{
Success: false,
Error: ctx.Err(),
}, nil
default:
}
// 获取共享列表
shares, err := session.ListSharenames()
if err != nil {
return &common.ConnectionResult{
Success: false,
Error: fmt.Errorf("获取共享列表失败: %v", err),
}, nil
}
// 检查上下文是否已取消
select {
case <-ctx.Done():
return &common.ConnectionResult{
Success: false,
Error: ctx.Err(),
Shares: shares,
}, nil
default:
}
// 尝试验证管理员权限
hasAdminAccess := c.validateAdminAccess(ctx, session)
return &common.ConnectionResult{
Success: true,
Shares: shares,
HasAdminAccess: hasAdminAccess,
}, nil
}
// GetProtocolName 获取协议名称
func (c *Smb2Connector) GetProtocolName() string {
return "SMB2"
}
// GetDefaultPort 获取默认端口
func (c *Smb2Connector) GetDefaultPort() int {
return 445
}
// validateAdminAccess 验证管理员权限
func (c *Smb2Connector) validateAdminAccess(ctx context.Context, session *smb2.Session) bool {
// 检查上下文
select {
case <-ctx.Done():
return false
default:
}
// 尝试挂载C$共享
fs, err := session.Mount("C$")
if err != nil {
return false
}
defer fs.Umount()
// 检查上下文
select {
case <-ctx.Done():
return false
default:
}
// 尝试读取系统文件以验证权限
path := `Windows\win.ini`
f, err := fs.OpenFile(path, os.O_RDONLY, 0666)
if err != nil {
return false
}
defer f.Close()
return true
}