mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-07-12 20:32:40 +08:00
fix: 大型修复,增加超时和线程控制
This commit is contained in:
parent
b89e892f14
commit
a2c56ab106
@ -1,189 +1,318 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ActiveMQCredential 表示一个ActiveMQ凭据
|
||||
type ActiveMQCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// ActiveMQScanResult 表示扫描结果
|
||||
type ActiveMQScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential ActiveMQCredential
|
||||
}
|
||||
|
||||
func ActiveMQScan(info *Common.HostInfo) (tmperr error) {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 先尝试默认账户
|
||||
Common.LogDebug("尝试默认账户 admin:admin")
|
||||
|
||||
// 首先测试默认账户
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试默认账户", retryCount+1))
|
||||
}
|
||||
defaultCredential := ActiveMQCredential{Username: "admin", Password: "admin"}
|
||||
defaultResult := tryActiveCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
flag, err := ActiveMQConn(info, "admin", "admin")
|
||||
if flag {
|
||||
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: admin 密码: admin", target)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "activemq",
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("ActiveMQ服务 %s 默认账户尝试失败: %v", target, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
if defaultResult.Success {
|
||||
saveActiveMQSuccess(info, target, defaultResult.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["activemq"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
// 生成所有凭据组合
|
||||
credentials := generateActiveMQCredentials(Common.Userdict["activemq"], Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["activemq"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
for _, user := range Common.Userdict["activemq"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func(user, pass string) {
|
||||
flag, err := ActiveMQConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{flag, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success {
|
||||
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "activemq",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("ActiveMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentActiveMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveActiveMQSuccess(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("ActiveMQ扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认凭据
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ActiveMQConn 统一的连接测试函数
|
||||
func ActiveMQConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
// generateActiveMQCredentials 生成ActiveMQ的用户名密码组合
|
||||
func generateActiveMQCredentials(users, passwords []string) []ActiveMQCredential {
|
||||
var credentials []ActiveMQCredential
|
||||
for _, user := range users {
|
||||
for _, pass := range passwords {
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, ActiveMQCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
return credentials
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
// concurrentActiveMQScan 并发扫描ActiveMQ服务
|
||||
func concurrentActiveMQScan(ctx context.Context, info *Common.HostInfo, credentials []ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *ActiveMQScanResult, 1)
|
||||
workChan := make(chan ActiveMQCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryActiveCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("ActiveMQ并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryActiveCredential 尝试单个ActiveMQ凭据
|
||||
func tryActiveCredential(ctx context.Context, info *Common.HostInfo, credential ActiveMQCredential, timeoutSeconds int64, maxRetries int) *ActiveMQScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &ActiveMQScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建单个连接超时的上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := ActiveMQConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &ActiveMQScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ActiveMQScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// ActiveMQConn 尝试ActiveMQ连接
|
||||
func ActiveMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
addr := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
|
||||
// 使用上下文创建带超时的连接
|
||||
dialer := &net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// STOMP协议的CONNECT命令
|
||||
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass)
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 发送认证请求
|
||||
conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
if _, err := conn.Write([]byte(stompConnect)); err != nil {
|
||||
return false, err
|
||||
// 在协程中处理认证
|
||||
go func() {
|
||||
// STOMP协议的CONNECT命令
|
||||
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass)
|
||||
|
||||
// 发送认证请求
|
||||
conn.SetWriteDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
|
||||
if _, err := conn.Write([]byte(stompConnect)); err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
|
||||
respBuf := make([]byte, 1024)
|
||||
n, err := conn.Read(respBuf)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查认证结果
|
||||
response := string(respBuf[:n])
|
||||
|
||||
var success bool
|
||||
var resultErr error
|
||||
|
||||
if strings.Contains(response, "CONNECTED") {
|
||||
success = true
|
||||
resultErr = nil
|
||||
} else if strings.Contains(response, "Authentication failed") || strings.Contains(response, "ERROR") {
|
||||
success = false
|
||||
resultErr = fmt.Errorf("认证失败")
|
||||
} else {
|
||||
success = false
|
||||
resultErr = fmt.Errorf("未知响应: %s", response)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, resultErr}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待认证结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.err
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
respBuf := make([]byte, 1024)
|
||||
n, err := conn.Read(respBuf)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 检查认证结果
|
||||
response := string(respBuf[:n])
|
||||
|
||||
if strings.Contains(response, "CONNECTED") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if strings.Contains(response, "Authentication failed") || strings.Contains(response, "ERROR") {
|
||||
return false, fmt.Errorf("认证失败")
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("未知响应: %s", response)
|
||||
}
|
||||
|
||||
// saveActiveMQSuccess 记录并保存ActiveMQ成功结果
|
||||
func saveActiveMQSuccess(info *Common.HostInfo, target string, credential ActiveMQCredential) {
|
||||
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v",
|
||||
target, credential.Username, credential.Password)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "activemq",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
}
|
||||
|
@ -1,154 +1,225 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CassandraCredential 表示一个Cassandra凭据
|
||||
type CassandraCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// CassandraScanResult 表示扫描结果
|
||||
type CassandraScanResult struct {
|
||||
Success bool
|
||||
IsAnonymous bool
|
||||
Error error
|
||||
Credential CassandraCredential
|
||||
}
|
||||
|
||||
func CassandraScan(info *Common.HostInfo) (tmperr error) {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
maxRetries := Common.MaxRetries
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 先尝试无认证访问
|
||||
Common.LogDebug("尝试无认证访问...")
|
||||
|
||||
// 首先测试无认证访问
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1))
|
||||
}
|
||||
anonymousCredential := CassandraCredential{Username: "", Password: ""}
|
||||
anonymousResult := tryCassandraCredential(ctx, info, anonymousCredential, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
flag, err := CassandraConn(info, "", "")
|
||||
if flag && err == nil {
|
||||
successMsg := fmt.Sprintf("Cassandra服务 %s 无认证访问成功", target)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存无认证访问结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "cassandra",
|
||||
"auth_type": "anonymous",
|
||||
"type": "unauthorized-access",
|
||||
"description": "数据库允许无认证访问",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
return err
|
||||
}
|
||||
if err != nil && Common.CheckErrs(err) != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
if anonymousResult.Success {
|
||||
saveCassandraSuccess(info, target, anonymousResult.Credential, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["cassandra"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
// 生成所有凭据组合
|
||||
credentials := generateCassandraCredentials(Common.Userdict["cassandra"], Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["cassandra"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
for _, user := range Common.Userdict["cassandra"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := CassandraConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("Cassandra服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存爆破成功结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "cassandra",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("Cassandra服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentCassandraScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveCassandraSuccess(info, target, result.Credential, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Cassandra扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CassandraConn 清理后的连接测试函数
|
||||
func CassandraConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
// generateCassandraCredentials 生成Cassandra的用户名密码组合
|
||||
func generateCassandraCredentials(users, passwords []string) []CassandraCredential {
|
||||
var credentials []CassandraCredential
|
||||
for _, user := range users {
|
||||
for _, pass := range passwords {
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, CassandraCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
return credentials
|
||||
}
|
||||
|
||||
// concurrentCassandraScan 并发扫描Cassandra服务
|
||||
func concurrentCassandraScan(ctx context.Context, info *Common.HostInfo, credentials []CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *CassandraScanResult, 1)
|
||||
workChan := make(chan CassandraCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryCassandraCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("Cassandra并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryCassandraCredential 尝试单个Cassandra凭据
|
||||
func tryCassandraCredential(ctx context.Context, info *Common.HostInfo, credential CassandraCredential, timeoutSeconds int64, maxRetries int) *CassandraScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &CassandraScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建单个连接超时的上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := CassandraConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &CassandraScanResult{
|
||||
Success: true,
|
||||
IsAnonymous: credential.Username == "" && credential.Password == "",
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &CassandraScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// CassandraConn 尝试Cassandra连接,支持上下文超时
|
||||
func CassandraConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
cluster := gocql.NewCluster(host)
|
||||
cluster.Port, _ = strconv.Atoi(port)
|
||||
cluster.Timeout = timeout
|
||||
cluster.ConnectTimeout = timeout
|
||||
cluster.ProtoVersion = 4
|
||||
cluster.Consistency = gocql.One
|
||||
|
||||
@ -161,18 +232,111 @@ func CassandraConn(info *Common.HostInfo, user string, pass string) (bool, error
|
||||
|
||||
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
|
||||
|
||||
session, err := cluster.CreateSession()
|
||||
if err != nil {
|
||||
return false, err
|
||||
// 创建会话通道
|
||||
sessionChan := make(chan struct {
|
||||
session *gocql.Session
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 在后台创建会话,以便可以通过上下文取消
|
||||
go func() {
|
||||
session, err := cluster.CreateSession()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if session != nil {
|
||||
session.Close()
|
||||
}
|
||||
case sessionChan <- struct {
|
||||
session *gocql.Session
|
||||
err error
|
||||
}{session, err}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待会话创建或上下文取消
|
||||
var session *gocql.Session
|
||||
var err error
|
||||
select {
|
||||
case result := <-sessionChan:
|
||||
session, err = result.session, result.err
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
|
||||
defer session.Close()
|
||||
|
||||
var version string
|
||||
if err := session.Query("SELECT peer FROM system.peers").Scan(&version); err != nil {
|
||||
if err := session.Query("SELECT now() FROM system.local").Scan(&version); err != nil {
|
||||
return false, err
|
||||
// 尝试执行查询,测试连接是否成功
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
var version string
|
||||
var err error
|
||||
|
||||
// 尝试两种查询,确保至少一种成功
|
||||
err = session.Query("SELECT peer FROM system.peers").WithContext(ctx).Scan(&version)
|
||||
if err != nil {
|
||||
err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&version)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{err == nil, err}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待查询结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.err
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// saveCassandraSuccess 记录并保存Cassandra成功结果
|
||||
func saveCassandraSuccess(info *Common.HostInfo, target string, credential CassandraCredential, isAnonymous bool) {
|
||||
var successMsg string
|
||||
var details map[string]interface{}
|
||||
|
||||
if isAnonymous {
|
||||
successMsg = fmt.Sprintf("Cassandra服务 %s 无认证访问成功", target)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "cassandra",
|
||||
"auth_type": "anonymous",
|
||||
"type": "unauthorized-access",
|
||||
"description": "数据库允许无认证访问",
|
||||
}
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("Cassandra服务 %s 爆破成功 用户名: %v 密码: %v",
|
||||
target, credential.Username, credential.Password)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "cassandra",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
}
|
||||
|
@ -1,153 +1,214 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ElasticScan(info *Common.HostInfo) (tmperr error) {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
Common.LogDebug("尝试无认证访问...")
|
||||
|
||||
// 首先测试无认证访问
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1))
|
||||
}
|
||||
flag, err := ElasticConn(info, "", "")
|
||||
if flag && err == nil {
|
||||
successMsg := fmt.Sprintf("Elasticsearch服务 %s 无需认证", target)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存无认证访问结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "elasticsearch",
|
||||
"type": "unauthorized-access",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
return err
|
||||
}
|
||||
if err != nil && Common.CheckErrs(err) != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["elastic"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)",
|
||||
totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
for _, user := range Common.Userdict["elastic"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func(user, pass string) {
|
||||
flag, err := ElasticConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{flag, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("Elasticsearch服务 %s 爆破成功 用户名: %v 密码: %v",
|
||||
target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存弱密码结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "elasticsearch",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("Elasticsearch服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
|
||||
target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
// ElasticCredential 表示Elasticsearch的凭据
|
||||
type ElasticCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// ElasticConn 尝试 Elasticsearch 连接
|
||||
func ElasticConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
// ElasticScanResult 表示扫描结果
|
||||
type ElasticScanResult struct {
|
||||
Success bool
|
||||
IsUnauth bool
|
||||
Error error
|
||||
Credential ElasticCredential
|
||||
}
|
||||
|
||||
func ElasticScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return nil
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 首先测试无认证访问
|
||||
Common.LogDebug("尝试无认证访问...")
|
||||
unauthResult := tryElasticCredential(ctx, info, ElasticCredential{"", ""}, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
if unauthResult.Success {
|
||||
// 无需认证情况
|
||||
saveElasticResult(info, target, unauthResult.Credential, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 构建凭据列表
|
||||
var credentials []ElasticCredential
|
||||
for _, user := range Common.Userdict["elastic"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, ElasticCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["elastic"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 并发扫描
|
||||
result := concurrentElasticScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveElasticResult(info, target, result.Credential, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Elasticsearch扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1是因为尝试了无认证
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// concurrentElasticScan 并发扫描Elasticsearch服务
|
||||
func concurrentElasticScan(ctx context.Context, info *Common.HostInfo, credentials []ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *ElasticScanResult, 1)
|
||||
workChan := make(chan ElasticCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryElasticCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("Elasticsearch并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryElasticCredential 尝试单个Elasticsearch凭据
|
||||
func tryElasticCredential(ctx context.Context, info *Common.HostInfo, credential ElasticCredential, timeoutSeconds int64, maxRetries int) *ElasticScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &ElasticScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
success, err := ElasticConn(ctx, info, credential.Username, credential.Password, timeoutSeconds)
|
||||
if success {
|
||||
isUnauth := credential.Username == "" && credential.Password == ""
|
||||
return &ElasticScanResult{
|
||||
Success: true,
|
||||
IsUnauth: isUnauth,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ElasticScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// ElasticConn 尝试Elasticsearch连接
|
||||
func ElasticConn(ctx context.Context, info *Common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(timeoutSeconds) * time.Second
|
||||
|
||||
// 创建带有超时的HTTP客户端
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
@ -156,7 +217,9 @@ func ElasticConn(info *Common.HostInfo, user string, pass string) (bool, error)
|
||||
}
|
||||
|
||||
baseURL := fmt.Sprintf("http://%s:%s", host, port)
|
||||
req, err := http.NewRequest("GET", baseURL+"/_cat/indices", nil)
|
||||
|
||||
// 使用上下文创建请求
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/_cat/indices", nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -166,11 +229,78 @@ func ElasticConn(info *Common.HostInfo, user string, pass string) (bool, error)
|
||||
req.Header.Add("Authorization", "Basic "+auth)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
return resp.StatusCode == 200, nil
|
||||
// 在协程中执行HTTP请求
|
||||
go func() {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{resp.StatusCode == 200, nil}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.err
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// saveElasticResult 保存Elasticsearch扫描结果
|
||||
func saveElasticResult(info *Common.HostInfo, target string, credential ElasticCredential, isUnauth bool) {
|
||||
var successMsg string
|
||||
var details map[string]interface{}
|
||||
|
||||
if isUnauth {
|
||||
successMsg = fmt.Sprintf("Elasticsearch服务 %s 无需认证", target)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "elasticsearch",
|
||||
"type": "unauthorized-access",
|
||||
}
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("Elasticsearch服务 %s 爆破成功 用户名: %v 密码: %v",
|
||||
target, credential.Username, credential.Password)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "elasticsearch",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
}
|
||||
|
369
Plugins/FTP.go
369
Plugins/FTP.go
@ -1,146 +1,262 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/jlaffaye/ftp"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func FtpScan(info *Common.HostInfo) (tmperr error) {
|
||||
// FtpCredential 表示一个FTP凭据
|
||||
type FtpCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// FtpScanResult 表示FTP扫描结果
|
||||
type FtpScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential FtpCredential
|
||||
Directories []string
|
||||
IsAnonymous bool
|
||||
}
|
||||
|
||||
func FtpScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 首先尝试匿名登录
|
||||
Common.LogDebug("尝试匿名登录...")
|
||||
anonymousResult := tryFtpCredential(ctx, info, FtpCredential{"anonymous", ""}, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
// 尝试匿名登录
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
success, dirs, err := FtpConn(info, "anonymous", "")
|
||||
if success && err == nil {
|
||||
Common.LogSuccess("匿名登录成功!")
|
||||
|
||||
// 保存匿名登录结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ftp",
|
||||
"username": "anonymous",
|
||||
"password": "",
|
||||
"type": "anonymous-login",
|
||||
"directories": dirs,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
return nil
|
||||
}
|
||||
errlog := fmt.Sprintf("ftp %s %v", target, err)
|
||||
Common.LogError(errlog)
|
||||
break
|
||||
if anonymousResult.Success {
|
||||
// 匿名登录成功
|
||||
saveFtpResult(info, target, anonymousResult)
|
||||
return nil
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["ftp"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
// 遍历用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []FtpCredential
|
||||
for _, user := range Common.Userdict["ftp"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, FtpCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["ftp"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentFtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 保存成功结果
|
||||
saveFtpResult(info, target, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
dirs []string
|
||||
err error
|
||||
}, 1)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("FTP扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名登录
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, dirs, err := FtpConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
dirs []string
|
||||
err error
|
||||
}{success, dirs, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// concurrentFtpScan 并发扫描FTP服务
|
||||
func concurrentFtpScan(ctx context.Context, info *Common.HostInfo, credentials []FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *FtpScanResult, 1)
|
||||
workChan := make(chan FtpCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case result := <-done:
|
||||
if result.success && result.err == nil {
|
||||
successLog := fmt.Sprintf("FTP服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(successLog)
|
||||
|
||||
// 保存爆破成功结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ftp",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
"directories": result.dirs,
|
||||
},
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryFtpCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
lastErr = result.err
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
lastErr = fmt.Errorf("连接超时")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("FTP并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryFtpCredential 尝试单个FTP凭据
|
||||
func tryFtpCredential(ctx context.Context, info *Common.HostInfo, credential FtpCredential, timeoutSeconds int64, maxRetries int) *FtpScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &FtpScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
directories []string
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 在协程中尝试连接
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
go func() {
|
||||
defer cancel()
|
||||
success, dirs, err := FtpConn(info, credential.Username, credential.Password)
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
directories []string
|
||||
err error
|
||||
}{success, dirs, err}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或超时
|
||||
var success bool
|
||||
var dirs []string
|
||||
var err error
|
||||
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
success = result.success
|
||||
dirs = result.directories
|
||||
err = result.err
|
||||
case <-connCtx.Done():
|
||||
if ctx.Err() != nil {
|
||||
// 全局超时
|
||||
return &FtpScanResult{
|
||||
Success: false,
|
||||
Error: ctx.Err(),
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
// 单个连接超时
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if success {
|
||||
isAnonymous := credential.Username == "anonymous" && credential.Password == ""
|
||||
return &FtpScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
Directories: dirs,
|
||||
IsAnonymous: isAnonymous,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 登录错误不需要重试
|
||||
if strings.Contains(err.Error(), "Login incorrect") {
|
||||
break
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
if lastErr != nil {
|
||||
errlog := fmt.Sprintf("FTP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
|
||||
target, user, pass, lastErr)
|
||||
Common.LogError(errlog)
|
||||
// 连接数过多需要等待
|
||||
if strings.Contains(err.Error(), "too many connections") {
|
||||
Common.LogDebug("连接数过多,等待5秒...")
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(lastErr.Error(), "Login incorrect") {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.Contains(lastErr.Error(), "too many connections") {
|
||||
Common.LogDebug("连接数过多,等待5秒...")
|
||||
time.Sleep(5 * time.Second)
|
||||
if retryCount < maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &FtpScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// FtpConn 建立FTP连接并尝试登录
|
||||
@ -179,6 +295,47 @@ func FtpConn(info *Common.HostInfo, user string, pass string) (success bool, dir
|
||||
return true, directories, nil
|
||||
}
|
||||
|
||||
// saveFtpResult 保存FTP扫描结果
|
||||
func saveFtpResult(info *Common.HostInfo, target string, result *FtpScanResult) {
|
||||
var successMsg string
|
||||
var details map[string]interface{}
|
||||
|
||||
if result.IsAnonymous {
|
||||
successMsg = fmt.Sprintf("FTP服务 %s 匿名登录成功!", target)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ftp",
|
||||
"username": "anonymous",
|
||||
"password": "",
|
||||
"type": "anonymous-login",
|
||||
"directories": result.Directories,
|
||||
}
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("FTP服务 %s 成功爆破 用户名: %v 密码: %v",
|
||||
target, result.Credential.Username, result.Credential.Password)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ftp",
|
||||
"username": result.Credential.Username,
|
||||
"password": result.Credential.Password,
|
||||
"type": "weak-password",
|
||||
"directories": result.Directories,
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
||||
// min 返回两个整数中的较小值
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
|
327
Plugins/IMAP.go
327
Plugins/IMAP.go
@ -2,134 +2,266 @@ package Plugins
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IMAPCredential 表示一个IMAP凭据
|
||||
type IMAPCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// IMAPScanResult 表示IMAP扫描结果
|
||||
type IMAPScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential IMAPCredential
|
||||
}
|
||||
|
||||
// IMAPScan 主扫描函数
|
||||
func IMAPScan(info *Common.HostInfo) (tmperr error) {
|
||||
func IMAPScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalUsers := len(Common.Userdict["imap"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 构建凭据列表
|
||||
var credentials []IMAPCredential
|
||||
for _, user := range Common.Userdict["imap"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, IMAPCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["imap"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
// 并发扫描
|
||||
result := concurrentIMAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveIMAPResult(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := IMAPConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("IMAP扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
// concurrentIMAPScan 并发扫描IMAP服务
|
||||
func concurrentIMAPScan(ctx context.Context, info *Common.HostInfo, credentials []IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *IMAPScanResult, 1)
|
||||
workChan := make(chan IMAPCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success {
|
||||
successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "imap",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryIMAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("IMAP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("IMAP并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryIMAPCredential 尝试单个IMAP凭据
|
||||
func tryIMAPCredential(ctx context.Context, info *Common.HostInfo, credential IMAPCredential, timeoutSeconds int64, maxRetries int) *IMAPScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &IMAPScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建单个连接超时的上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := IMAPConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &IMAPScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &IMAPScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// IMAPConn 连接测试函数
|
||||
func IMAPConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
func IMAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
addr := fmt.Sprintf("%s:%s", host, port)
|
||||
|
||||
// 尝试普通连接
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
if err == nil {
|
||||
if flag, err := tryIMAPAuth(conn, user, pass, timeout); err == nil {
|
||||
return flag, nil
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 在协程中尝试连接
|
||||
go func() {
|
||||
// 先尝试普通连接
|
||||
dialer := &net.Dialer{Timeout: timeout}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||
if err == nil {
|
||||
flag, authErr := tryIMAPAuth(conn, user, pass, timeout)
|
||||
conn.Close()
|
||||
if authErr == nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{flag, nil}:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// 尝试TLS连接
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
conn, err = tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("连接失败: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// 如果普通连接失败或认证失败,尝试TLS连接
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
|
||||
if tlsErr != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, fmt.Errorf("连接失败: %v", tlsErr)}:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer tlsConn.Close()
|
||||
|
||||
return tryIMAPAuth(conn, user, pass, timeout)
|
||||
flag, authErr := tryIMAPAuth(tlsConn, user, pass, timeout)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{flag, authErr}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.err
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// tryIMAPAuth 尝试IMAP认证
|
||||
@ -167,3 +299,26 @@ func tryIMAPAuth(conn net.Conn, user string, pass string, timeout time.Duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// saveIMAPResult 保存IMAP扫描结果
|
||||
func saveIMAPResult(info *Common.HostInfo, target string, credential IMAPCredential) {
|
||||
successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v",
|
||||
target, credential.Username, credential.Password)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "imap",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
342
Plugins/Kafka.go
342
Plugins/Kafka.go
@ -1,134 +1,282 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func KafkaScan(info *Common.HostInfo) (tmperr error) {
|
||||
// KafkaCredential 表示Kafka凭据
|
||||
type KafkaCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// KafkaScanResult 表示扫描结果
|
||||
type KafkaScanResult struct {
|
||||
Success bool
|
||||
IsUnauth bool
|
||||
Error error
|
||||
Credential KafkaCredential
|
||||
}
|
||||
|
||||
func KafkaScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 尝试无认证访问
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 先尝试无认证访问
|
||||
Common.LogDebug("尝试无认证访问...")
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1))
|
||||
unauthResult := tryKafkaCredential(ctx, info, KafkaCredential{"", ""}, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
if unauthResult.Success {
|
||||
// 无认证访问成功
|
||||
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target))
|
||||
|
||||
// 保存无认证访问结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "kafka",
|
||||
"type": "unauthorized-access",
|
||||
},
|
||||
}
|
||||
flag, err := KafkaConn(info, "", "")
|
||||
if flag && err == nil {
|
||||
// 保存无认证访问结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "kafka",
|
||||
"type": "unauthorized-access",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target))
|
||||
return nil
|
||||
}
|
||||
if err != nil && Common.CheckErrs(err) != nil {
|
||||
if retryCount < maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
break
|
||||
Common.SaveResult(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["kafka"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []KafkaCredential
|
||||
for _, user := range Common.Userdict["kafka"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, KafkaCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["kafka"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentKafkaScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 保存爆破成功结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "kafka",
|
||||
"type": "weak-password",
|
||||
"username": result.Credential.Username,
|
||||
"password": result.Credential.Password,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s",
|
||||
target, result.Credential.Username, result.Credential.Password))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Kafka扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了无认证
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// concurrentKafkaScan 并发扫描Kafka服务
|
||||
func concurrentKafkaScan(ctx context.Context, info *Common.HostInfo, credentials []KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *KafkaScanResult, 1)
|
||||
workChan := make(chan KafkaCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryKafkaCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("Kafka并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryKafkaCredential 尝试单个Kafka凭据
|
||||
func tryKafkaCredential(ctx context.Context, info *Common.HostInfo, credential KafkaCredential, timeoutSeconds int64, maxRetries int) *KafkaScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &KafkaScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建单个连接超时的上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
|
||||
// 在协程中执行Kafka连接
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
success, err := KafkaConn(info, credential.Username, credential.Password)
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
// 连接超时或被取消
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := KafkaConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
// 保存爆破成功结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "kafka",
|
||||
"type": "weak-password",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s", target, user, pass))
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}{success, err}:
|
||||
// 发送结果
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("Kafka服务 %s 尝试失败 用户名: %s 密码: %s 错误: %v",
|
||||
target, user, pass, err))
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
// 等待结果或超时
|
||||
var success bool
|
||||
var err error
|
||||
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
success = result.success
|
||||
err = result.err
|
||||
case <-connCtx.Done():
|
||||
if ctx.Err() != nil {
|
||||
// 全局超时
|
||||
cancel()
|
||||
return &KafkaScanResult{
|
||||
Success: false,
|
||||
Error: ctx.Err(),
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
break
|
||||
// 单个连接超时
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
cancel() // 清理单个连接上下文
|
||||
|
||||
if success {
|
||||
isUnauth := credential.Username == "" && credential.Password == ""
|
||||
return &KafkaScanResult{
|
||||
Success: true,
|
||||
IsUnauth: isUnauth,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 记录错误
|
||||
Common.LogError(fmt.Sprintf("Kafka尝试失败 用户名: %s 密码: %s 错误: %v",
|
||||
credential.Username, credential.Password, err))
|
||||
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &KafkaScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// KafkaConn 尝试 Kafka 连接
|
||||
@ -138,6 +286,8 @@ func KafkaConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
|
||||
config := sarama.NewConfig()
|
||||
config.Net.DialTimeout = timeout
|
||||
config.Net.ReadTimeout = timeout
|
||||
config.Net.WriteTimeout = timeout
|
||||
config.Net.TLS.Enable = false
|
||||
config.Version = sarama.V2_0_0_0
|
||||
|
||||
|
386
Plugins/LDAP.go
386
Plugins/LDAP.go
@ -1,166 +1,312 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func LDAPScan(info *Common.HostInfo) (tmperr error) {
|
||||
// LDAPCredential 表示一个LDAP凭据
|
||||
type LDAPCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// LDAPScanResult 表示LDAP扫描结果
|
||||
type LDAPScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential LDAPCredential
|
||||
IsAnonymous bool
|
||||
}
|
||||
|
||||
func LDAPScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
Common.LogDebug("尝试匿名访问...")
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 首先尝试匿名访问
|
||||
flag, err := LDAPConn(info, "", "")
|
||||
if flag && err == nil {
|
||||
// 记录匿名访问成功
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ldap",
|
||||
"type": "anonymous-access",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
Common.LogSuccess(fmt.Sprintf("LDAP服务 %s 匿名访问成功", target))
|
||||
return err
|
||||
Common.LogDebug("尝试匿名访问...")
|
||||
anonymousResult := tryLDAPCredential(ctx, info, LDAPCredential{"", ""}, Common.Timeout, 1)
|
||||
|
||||
if anonymousResult.Success {
|
||||
// 匿名访问成功
|
||||
saveLDAPResult(info, target, anonymousResult)
|
||||
return nil
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["ldap"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []LDAPCredential
|
||||
for _, user := range Common.Userdict["ldap"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, LDAPCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["ldap"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentLDAPScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveLDAPResult(info, target, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := LDAPConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("LDAP扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
// concurrentLDAPScan 并发扫描LDAP服务
|
||||
func concurrentLDAPScan(ctx context.Context, info *Common.HostInfo, credentials []LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *LDAPScanResult, 1)
|
||||
workChan := make(chan LDAPCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
// 记录成功爆破的凭据
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ldap",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryLDAPCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
Common.LogSuccess(fmt.Sprintf("LDAP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass))
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("LDAP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("LDAP并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryLDAPCredential 尝试单个LDAP凭据
|
||||
func tryLDAPCredential(ctx context.Context, info *Common.HostInfo, credential LDAPCredential, timeoutSeconds int64, maxRetries int) *LDAPScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &LDAPScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := LDAPConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
isAnonymous := credential.Username == "" && credential.Password == ""
|
||||
return &LDAPScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
IsAnonymous: isAnonymous,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &LDAPScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
func LDAPConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
// LDAPConn 尝试LDAP连接
|
||||
func LDAPConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
// 配置LDAP连接
|
||||
l, err := ldap.Dial("tcp", address)
|
||||
// 创建拨号器并设置超时
|
||||
dialer := &net.Dialer{
|
||||
Timeout: time.Duration(Common.Timeout) * time.Second,
|
||||
}
|
||||
|
||||
// 使用上下文控制的拨号过程
|
||||
conn, err := dialer.DialContext(ctx, "tcp", address)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 使用已连接的TCP连接创建LDAP连接
|
||||
l := ldap.NewConn(conn, false)
|
||||
defer l.Close()
|
||||
|
||||
// 设置超时
|
||||
l.SetTimeout(timeout)
|
||||
// 在单独的协程中启动LDAP连接
|
||||
go l.Start()
|
||||
|
||||
// 尝试绑定
|
||||
if user != "" {
|
||||
bindDN := fmt.Sprintf("cn=%s,dc=example,dc=com", user)
|
||||
err = l.Bind(bindDN, pass)
|
||||
} else {
|
||||
err = l.UnauthenticatedBind("")
|
||||
// 创建一个完成通道
|
||||
done := make(chan error, 1)
|
||||
|
||||
// 在协程中进行绑定和搜索操作,确保可以被上下文取消
|
||||
go func() {
|
||||
// 尝试绑定
|
||||
var err error
|
||||
if user != "" {
|
||||
// 使用更通用的绑定DN模式
|
||||
bindDN := fmt.Sprintf("cn=%s,dc=example,dc=com", user)
|
||||
err = l.Bind(bindDN, pass)
|
||||
} else {
|
||||
// 匿名绑定
|
||||
err = l.UnauthenticatedBind("")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试简单搜索以验证权限
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
"dc=example,dc=com",
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
"(objectClass=*)",
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
_, err = l.Search(searchRequest)
|
||||
done <- err
|
||||
}()
|
||||
|
||||
// 等待操作完成或上下文取消
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 尝试简单搜索以验证权限
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
"dc=example,dc=com",
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
"(objectClass=*)",
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
_, err = l.Search(searchRequest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// saveLDAPResult 保存LDAP扫描结果
|
||||
func saveLDAPResult(info *Common.HostInfo, target string, result *LDAPScanResult) {
|
||||
var successMsg string
|
||||
var details map[string]interface{}
|
||||
|
||||
if result.IsAnonymous {
|
||||
successMsg = fmt.Sprintf("LDAP服务 %s 匿名访问成功", target)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ldap",
|
||||
"type": "anonymous-access",
|
||||
}
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("LDAP服务 %s 爆破成功 用户名: %v 密码: %v",
|
||||
target, result.Credential.Username, result.Credential.Password)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "ldap",
|
||||
"username": result.Credential.Username,
|
||||
"password": result.Credential.Password,
|
||||
"type": "weak-password",
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
290
Plugins/MSSQL.go
290
Plugins/MSSQL.go
@ -1,121 +1,208 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MssqlCredential 表示一个MSSQL凭据
|
||||
type MssqlCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// MssqlScanResult 表示MSSQL扫描结果
|
||||
type MssqlScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential MssqlCredential
|
||||
}
|
||||
|
||||
// MssqlScan 执行MSSQL服务扫描
|
||||
func MssqlScan(info *Common.HostInfo) (tmperr error) {
|
||||
func MssqlScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalUsers := len(Common.Userdict["mssql"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []MssqlCredential
|
||||
for _, user := range Common.Userdict["mssql"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, MssqlCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["mssql"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 执行MSSQL连接
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentMssqlScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveMssqlResult(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := MssqlConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("MSSQL扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 等待结果或超时
|
||||
var err error
|
||||
// concurrentMssqlScan 并发扫描MSSQL服务
|
||||
func concurrentMssqlScan(ctx context.Context, info *Common.HostInfo, credentials []MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *MssqlScanResult, 1)
|
||||
workChan := make(chan MssqlCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("MSSQL %s %v %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "mssql",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryMssqlCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("MSSQL %s %v %v %v", target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("MSSQL并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryMssqlCredential 尝试单个MSSQL凭据
|
||||
func tryMssqlCredential(ctx context.Context, info *Common.HostInfo, credential MssqlCredential, timeoutSeconds int64, maxRetries int) *MssqlScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &MssqlScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时的上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := MssqlConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &MssqlScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &MssqlScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// MssqlConn 尝试MSSQL连接
|
||||
func MssqlConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
func MssqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
host, port, username, password := info.Host, info.Ports, user, pass
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
// 构造连接字符串
|
||||
connStr := fmt.Sprintf(
|
||||
"server=%s;user id=%s;password=%s;port=%v;encrypt=disable;timeout=%v",
|
||||
host, username, password, port, timeout,
|
||||
"server=%s;user id=%s;password=%s;port=%v;encrypt=disable;",
|
||||
host, username, password, port,
|
||||
)
|
||||
|
||||
// 建立数据库连接
|
||||
@ -129,11 +216,54 @@ func MssqlConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
db.SetConnMaxLifetime(timeout)
|
||||
db.SetConnMaxIdleTime(timeout)
|
||||
db.SetMaxIdleConns(0)
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
// 测试连接
|
||||
if err = db.Ping(); err != nil {
|
||||
return false, err
|
||||
// 通过上下文执行ping操作,以支持超时控制
|
||||
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
|
||||
defer pingCancel()
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
errChan <- db.PingContext(pingCtx)
|
||||
}()
|
||||
|
||||
// 等待ping结果或者超时
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
case <-ctx.Done():
|
||||
// 全局超时或取消
|
||||
return false, ctx.Err()
|
||||
case <-pingCtx.Done():
|
||||
if pingCtx.Err() == context.DeadlineExceeded {
|
||||
// 单个连接超时
|
||||
return false, fmt.Errorf("连接超时")
|
||||
}
|
||||
return false, pingCtx.Err()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// saveMssqlResult 保存MSSQL扫描结果
|
||||
func saveMssqlResult(info *Common.HostInfo, target string, credential MssqlCredential) {
|
||||
successMsg := fmt.Sprintf("MSSQL %s %v %v", target, credential.Username, credential.Password)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "mssql",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
@ -1,46 +1,35 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MemcachedScanResult 表示Memcached扫描结果
|
||||
type MemcachedScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Stats string
|
||||
}
|
||||
|
||||
// MemcachedScan 检测Memcached未授权访问
|
||||
func MemcachedScan(info *Common.HostInfo) error {
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 Memcached %s", realhost))
|
||||
|
||||
// 建立TCP连接
|
||||
client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
// 尝试连接并检查未授权访问
|
||||
result := tryMemcachedConnection(ctx, info, Common.Timeout)
|
||||
|
||||
// 设置超时时间
|
||||
if err := client.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 发送stats命令
|
||||
if _, err := client.Write([]byte("stats\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
rev := make([]byte, 1024)
|
||||
n, err := client.Read(rev)
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("Memcached %v:%v %v", info.Host, info.Ports, err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查响应内容
|
||||
if strings.Contains(string(rev[:n]), "STAT") {
|
||||
// 保存结果
|
||||
result := &Common.ScanResult{
|
||||
if result.Success {
|
||||
// 保存成功结果
|
||||
scanResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
@ -50,11 +39,122 @@ func MemcachedScan(info *Common.HostInfo) error {
|
||||
"service": "memcached",
|
||||
"type": "unauthorized-access",
|
||||
"description": "Memcached unauthorized access",
|
||||
"stats": result.Stats,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
Common.SaveResult(scanResult)
|
||||
Common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", realhost))
|
||||
}
|
||||
|
||||
return nil
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
Common.LogDebug("Memcached扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("Memcached扫描完成: %s", realhost))
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// tryMemcachedConnection 尝试连接Memcached并检查未授权访问
|
||||
func tryMemcachedConnection(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64) *MemcachedScanResult {
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
timeout := time.Duration(timeoutSeconds) * time.Second
|
||||
|
||||
// 创建结果通道
|
||||
resultChan := make(chan *MemcachedScanResult, 1)
|
||||
|
||||
// 创建连接上下文,带超时
|
||||
connCtx, connCancel := context.WithTimeout(ctx, timeout)
|
||||
defer connCancel()
|
||||
|
||||
// 在协程中尝试连接
|
||||
go func() {
|
||||
// 构建结果结构
|
||||
result := &MemcachedScanResult{
|
||||
Success: false,
|
||||
Error: nil,
|
||||
Stats: "",
|
||||
}
|
||||
|
||||
// 建立TCP连接
|
||||
client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout)
|
||||
if err != nil {
|
||||
result.Error = err
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- result:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// 设置操作截止时间
|
||||
if err := client.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
result.Error = err
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- result:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 发送stats命令
|
||||
if _, err := client.Write([]byte("stats\n")); err != nil {
|
||||
result.Error = err
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- result:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
rev := make([]byte, 1024)
|
||||
n, err := client.Read(rev)
|
||||
if err != nil {
|
||||
result.Error = err
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- result:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查响应是否包含统计信息
|
||||
response := string(rev[:n])
|
||||
if strings.Contains(response, "STAT") {
|
||||
result.Success = true
|
||||
result.Stats = response
|
||||
}
|
||||
|
||||
// 发送结果
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- result:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result
|
||||
case <-connCtx.Done():
|
||||
if ctx.Err() != nil {
|
||||
// 全局上下文取消
|
||||
return &MemcachedScanResult{
|
||||
Success: false,
|
||||
Error: ctx.Err(),
|
||||
}
|
||||
}
|
||||
// 连接超时
|
||||
return &MemcachedScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("连接超时"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
@ -8,67 +9,180 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ModbusScanResult 表示 Modbus 扫描结果
|
||||
type ModbusScanResult struct {
|
||||
Success bool
|
||||
DeviceInfo string
|
||||
Error error
|
||||
}
|
||||
|
||||
// ModbusScan 执行 Modbus 服务扫描
|
||||
func ModbusScan(info *Common.HostInfo) error {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("开始 Modbus 扫描: %s", target))
|
||||
|
||||
// 尝试建立连接
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 构造Modbus TCP请求包 - 读取设备ID
|
||||
request := buildModbusRequest()
|
||||
|
||||
// 设置读写超时
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
|
||||
// 发送请求
|
||||
_, err = conn.Write(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("发送Modbus请求失败: %v", err)
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
response := make([]byte, 256)
|
||||
n, err := conn.Read(response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取Modbus响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 验证响应
|
||||
if isValidModbusResponse(response[:n]) {
|
||||
// 获取设备信息
|
||||
deviceInfo := parseModbusResponse(response[:n])
|
||||
// 执行扫描
|
||||
result := tryModbusScan(ctx, info, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
if result.Success {
|
||||
// 保存扫描结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": port,
|
||||
"service": "modbus",
|
||||
"type": "unauthorized-access",
|
||||
"device_id": deviceInfo,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
|
||||
// 控制台输出
|
||||
Common.LogSuccess(fmt.Sprintf("Modbus服务 %v:%v 无认证访问", host, port))
|
||||
if deviceInfo != "" {
|
||||
Common.LogSuccess(fmt.Sprintf("设备信息: %s", deviceInfo))
|
||||
}
|
||||
|
||||
saveModbusResult(info, target, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("非Modbus服务或访问被拒绝")
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Modbus 扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
if result.Error != nil {
|
||||
Common.LogDebug(fmt.Sprintf("Modbus 扫描失败: %v", result.Error))
|
||||
return result.Error
|
||||
}
|
||||
Common.LogDebug("Modbus 扫描完成,未发现服务")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryModbusScan 尝试单个 Modbus 扫描
|
||||
func tryModbusScan(ctx context.Context, info *Common.HostInfo, timeoutSeconds int64, maxRetries int) *ModbusScanResult {
|
||||
var lastErr error
|
||||
host, port := info.Host, info.Ports
|
||||
target := fmt.Sprintf("%s:%s", host, port)
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &ModbusScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试 Modbus 扫描: %s", retry+1, target))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建单个连接超时的上下文
|
||||
connCtx, connCancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
|
||||
// 创建结果通道
|
||||
resultChan := make(chan *ModbusScanResult, 1)
|
||||
|
||||
// 在协程中执行扫描
|
||||
go func() {
|
||||
// 尝试建立连接
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(connCtx, "tcp", target)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- &ModbusScanResult{Success: false, Error: err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 构造 Modbus TCP 请求包 - 读取设备ID
|
||||
request := buildModbusRequest()
|
||||
|
||||
// 设置读写超时
|
||||
conn.SetDeadline(time.Now().Add(time.Duration(timeoutSeconds) * time.Second))
|
||||
|
||||
// 发送请求
|
||||
_, err = conn.Write(request)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- &ModbusScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("发送Modbus请求失败: %v", err),
|
||||
}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
response := make([]byte, 256)
|
||||
n, err := conn.Read(response)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- &ModbusScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("读取Modbus响应失败: %v", err),
|
||||
}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 验证响应
|
||||
if isValidModbusResponse(response[:n]) {
|
||||
// 获取设备信息
|
||||
deviceInfo := parseModbusResponse(response[:n])
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- &ModbusScanResult{
|
||||
Success: true,
|
||||
DeviceInfo: deviceInfo,
|
||||
}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- &ModbusScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("非Modbus服务或访问被拒绝"),
|
||||
}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待扫描结果或超时
|
||||
var result *ModbusScanResult
|
||||
select {
|
||||
case res := <-resultChan:
|
||||
result = res
|
||||
case <-connCtx.Done():
|
||||
if ctx.Err() != nil {
|
||||
connCancel()
|
||||
return &ModbusScanResult{
|
||||
Success: false,
|
||||
Error: ctx.Err(),
|
||||
}
|
||||
}
|
||||
result = &ModbusScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("连接超时"),
|
||||
}
|
||||
}
|
||||
|
||||
connCancel()
|
||||
|
||||
if result.Success {
|
||||
return result
|
||||
}
|
||||
|
||||
lastErr = result.Error
|
||||
if result.Error != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(result.Error); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ModbusScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
}
|
||||
}
|
||||
|
||||
// buildModbusRequest 构建Modbus TCP请求包
|
||||
@ -116,6 +230,45 @@ func parseModbusResponse(response []byte) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 提取更多设备信息
|
||||
unitID := response[6]
|
||||
return fmt.Sprintf("Unit ID: %d", unitID)
|
||||
funcCode := response[7]
|
||||
|
||||
// 简单的设备信息提取,实际应用中可以提取更多信息
|
||||
info := fmt.Sprintf("Unit ID: %d, Function: 0x%02X", unitID, funcCode)
|
||||
|
||||
// 如果是读取线圈响应,尝试解析线圈状态
|
||||
if funcCode == 0x01 && len(response) >= 10 {
|
||||
byteCount := response[8]
|
||||
if byteCount > 0 && len(response) >= 9+int(byteCount) {
|
||||
coilValue := response[9] & 0x01 // 获取第一个线圈状态
|
||||
info += fmt.Sprintf(", Coil Status: %d", coilValue)
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// saveModbusResult 保存Modbus扫描结果
|
||||
func saveModbusResult(info *Common.HostInfo, target string, result *ModbusScanResult) {
|
||||
// 保存扫描结果
|
||||
scanResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "modbus",
|
||||
"type": "unauthorized-access",
|
||||
"device_info": result.DeviceInfo,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(scanResult)
|
||||
|
||||
// 控制台输出
|
||||
Common.LogSuccess(fmt.Sprintf("Modbus服务 %s 无认证访问", target))
|
||||
if result.DeviceInfo != "" {
|
||||
Common.LogSuccess(fmt.Sprintf("设备信息: %s", result.DeviceInfo))
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -14,85 +17,151 @@ func MongodbScan(info *Common.HostInfo) error {
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
isUnauth, err := MongodbUnauth(info)
|
||||
Common.LogDebug(fmt.Sprintf("开始MongoDB扫描: %s", target))
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("MongoDB %v %v", target, err)
|
||||
Common.LogError(errlog)
|
||||
} else if isUnauth {
|
||||
// 记录控制台输出
|
||||
Common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target))
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 保存未授权访问结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "mongodb",
|
||||
"type": "unauthorized-access",
|
||||
"protocol": "mongodb",
|
||||
},
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
isUnauth bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 在协程中执行扫描
|
||||
go func() {
|
||||
isUnauth, err := MongodbUnauth(ctx, info)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
isUnauth bool
|
||||
err error
|
||||
}{isUnauth, err}:
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
}
|
||||
}()
|
||||
|
||||
return err
|
||||
// 等待结果或超时
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
if result.err != nil {
|
||||
errlog := fmt.Sprintf("MongoDB %v %v", target, result.err)
|
||||
Common.LogError(errlog)
|
||||
return result.err
|
||||
} else if result.isUnauth {
|
||||
// 记录控制台输出
|
||||
Common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target))
|
||||
|
||||
// 保存未授权访问结果
|
||||
scanResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "mongodb",
|
||||
"type": "unauthorized-access",
|
||||
"protocol": "mongodb",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(scanResult)
|
||||
} else {
|
||||
Common.LogDebug(fmt.Sprintf("MongoDB %v 需要认证", target))
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
Common.LogError(fmt.Sprintf("MongoDB扫描超时: %s", target))
|
||||
return fmt.Errorf("全局超时")
|
||||
}
|
||||
}
|
||||
|
||||
// MongodbUnauth 检测MongoDB未授权访问
|
||||
func MongodbUnauth(info *Common.HostInfo) (bool, error) {
|
||||
func MongodbUnauth(ctx context.Context, info *Common.HostInfo) (bool, error) {
|
||||
msgPacket := createOpMsgPacket()
|
||||
queryPacket := createOpQueryPacket()
|
||||
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("检测MongoDB未授权访问: %s", realhost))
|
||||
|
||||
// 尝试OP_MSG查询
|
||||
reply, err := checkMongoAuth(realhost, msgPacket)
|
||||
Common.LogDebug("尝试使用OP_MSG协议")
|
||||
reply, err := checkMongoAuth(ctx, realhost, msgPacket)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("OP_MSG查询失败: %v, 尝试使用OP_QUERY协议", err))
|
||||
// 失败则尝试OP_QUERY查询
|
||||
reply, err = checkMongoAuth(realhost, queryPacket)
|
||||
reply, err = checkMongoAuth(ctx, realhost, queryPacket)
|
||||
if err != nil {
|
||||
Common.LogDebug(fmt.Sprintf("OP_QUERY查询也失败: %v", err))
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// 检查响应结果
|
||||
Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply)))
|
||||
if strings.Contains(reply, "totalLinesWritten") {
|
||||
Common.LogDebug("响应中包含totalLinesWritten,确认未授权访问")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
Common.LogDebug("响应未包含预期内容,可能需要认证")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// checkMongoAuth 检查MongoDB认证状态
|
||||
func checkMongoAuth(address string, packet []byte) (string, error) {
|
||||
// 建立TCP连接
|
||||
conn, err := Common.WrapperTcpWithTimeout("tcp", address, time.Duration(Common.Timeout)*time.Second)
|
||||
func checkMongoAuth(ctx context.Context, address string, packet []byte) (string, error) {
|
||||
Common.LogDebug(fmt.Sprintf("建立MongoDB连接: %s", address))
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 使用带超时的连接
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(connCtx, "tcp", address)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("连接失败: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 设置超时时间
|
||||
if err := conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
|
||||
return "", err
|
||||
// 检查上下文是否已取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// 设置读写超时
|
||||
if err := conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
|
||||
return "", fmt.Errorf("设置超时失败: %v", err)
|
||||
}
|
||||
|
||||
// 发送查询包
|
||||
Common.LogDebug("发送查询包")
|
||||
if _, err := conn.Write(packet); err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("发送查询失败: %v", err)
|
||||
}
|
||||
|
||||
// 再次检查上下文是否已取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
reply := make([]byte, 1024)
|
||||
Common.LogDebug("读取响应")
|
||||
reply := make([]byte, 2048)
|
||||
count, err := conn.Read(reply)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if err != nil && err != io.EOF {
|
||||
return "", fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return "", fmt.Errorf("收到空响应")
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("成功接收响应,字节数: %d", count))
|
||||
return string(reply[:count]), nil
|
||||
}
|
||||
|
||||
|
354
Plugins/MySQL.go
354
Plugins/MySQL.go
@ -1,144 +1,306 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MySQLCredential 表示一个MySQL凭据
|
||||
type MySQLCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// MySQLScanResult 表示MySQL扫描结果
|
||||
type MySQLScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential MySQLCredential
|
||||
}
|
||||
|
||||
// MysqlScan 执行MySQL服务扫描
|
||||
func MysqlScan(info *Common.HostInfo) (tmperr error) {
|
||||
func MysqlScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalUsers := len(Common.Userdict["mysql"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []MySQLCredential
|
||||
for _, user := range Common.Userdict["mysql"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, MySQLCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["mysql"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentMySQLScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveMySQLResult(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := MysqlConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("MySQL扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
// concurrentMySQLScan 并发扫描MySQL服务
|
||||
func concurrentMySQLScan(ctx context.Context, info *Common.HostInfo, credentials []MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *MySQLScanResult, 1)
|
||||
workChan := make(chan MySQLCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("MySQL %s %v %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "mysql",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryMySQLCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("MySQL并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryMySQLCredential 尝试单个MySQL凭据
|
||||
func tryMySQLCredential(ctx context.Context, info *Common.HostInfo, credential MySQLCredential, timeoutSeconds int64, maxRetries int) *MySQLScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &MySQLScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建独立的超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := MysqlConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &MySQLScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// Access denied 表示用户名或密码错误,无需重试
|
||||
if strings.Contains(err.Error(), "Access denied") {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("MySQL %s %v %v %v", target, user, pass, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
if strings.Contains(err.Error(), "Access denied") {
|
||||
break // 认证失败,尝试下一个密码
|
||||
}
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
tmperr = err
|
||||
if !strings.Contains(err.Error(), "Access denied") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &MySQLScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// MysqlConn 尝试MySQL连接
|
||||
func MysqlConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
func MysqlConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
host, port, username, password := info.Host, info.Ports, user, pass
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
// 构造连接字符串
|
||||
// 构造连接字符串,包含超时设置
|
||||
connStr := fmt.Sprintf(
|
||||
"%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v",
|
||||
username, password, host, port, timeout,
|
||||
)
|
||||
|
||||
// 建立数据库连接
|
||||
db, err := sql.Open("mysql", connStr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 在协程中尝试连接
|
||||
go func() {
|
||||
// 建立数据库连接
|
||||
db, err := sql.Open("mysql", connStr)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// 设置连接参数
|
||||
db.SetConnMaxLifetime(timeout)
|
||||
db.SetConnMaxIdleTime(timeout)
|
||||
db.SetMaxIdleConns(0)
|
||||
|
||||
// 添加上下文支持
|
||||
conn, err := db.Conn(ctx)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 测试连接
|
||||
err = conn.PingContext(ctx)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 连接成功
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{true, nil}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.err
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// 设置连接参数
|
||||
db.SetConnMaxLifetime(timeout)
|
||||
db.SetConnMaxIdleTime(timeout)
|
||||
db.SetMaxIdleConns(0)
|
||||
|
||||
// 测试连接
|
||||
if err = db.Ping(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 连接成功,只返回结果,不打印日志
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// saveMySQLResult 保存MySQL扫描结果
|
||||
func saveMySQLResult(info *Common.HostInfo, target string, credential MySQLCredential) {
|
||||
successMsg := fmt.Sprintf("MySQL %s %v %v", target, credential.Username, credential.Password)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "mysql",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
@ -1,119 +1,381 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
_ "github.com/sijms/go-ora/v2"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func OracleScan(info *Common.HostInfo) (tmperr error) {
|
||||
// OracleCredential 表示一个Oracle凭据
|
||||
type OracleCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// OracleScanResult 表示Oracle扫描结果
|
||||
type OracleScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential OracleCredential
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// 常见Oracle服务名列表
|
||||
var commonServiceNames = []string{"XE", "ORCL", "ORCLPDB1", "XEPDB1", "PDBORCL"}
|
||||
|
||||
func OracleScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalUsers := len(Common.Userdict["oracle"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建常见高危凭据列表(优先测试)
|
||||
highRiskCredentials := []OracleCredential{
|
||||
{Username: "SYS", Password: "123456"},
|
||||
{Username: "SYSTEM", Password: "123456"},
|
||||
{Username: "SYS", Password: "oracle"},
|
||||
{Username: "SYSTEM", Password: "oracle"},
|
||||
{Username: "SYS", Password: "password"},
|
||||
{Username: "SYSTEM", Password: "password"},
|
||||
{Username: "SYS", Password: "sys123"},
|
||||
{Username: "SYS", Password: "change_on_install"},
|
||||
{Username: "SYSTEM", Password: "manager"},
|
||||
}
|
||||
|
||||
// 先尝试常见高危凭据
|
||||
Common.LogDebug("尝试常见高危凭据...")
|
||||
for _, cred := range highRiskCredentials {
|
||||
result := tryAllServiceNames(ctx, info, cred, Common.Timeout, 1)
|
||||
if result != nil && result.Success {
|
||||
saveOracleResult(info, target, result.Credential, result.ServiceName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 构建完整凭据列表
|
||||
var credentials []OracleCredential
|
||||
for _, user := range Common.Userdict["oracle"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
// 转换用户名为大写,提高匹配率
|
||||
credentials = append(credentials, OracleCredential{
|
||||
Username: strings.ToUpper(user),
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["oracle"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentOracleScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveOracleResult(info, target, result.Credential, result.ServiceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Oracle扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+len(highRiskCredentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryAllServiceNames 尝试所有常见服务名
|
||||
func tryAllServiceNames(ctx context.Context, info *Common.HostInfo, credential OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult {
|
||||
for _, serviceName := range commonServiceNames {
|
||||
result := tryOracleCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
result.ServiceName = serviceName
|
||||
return result
|
||||
}
|
||||
|
||||
// 对SYS用户尝试SYSDBA模式
|
||||
if strings.ToUpper(credential.Username) == "SYS" {
|
||||
result = tryOracleSysCredential(ctx, info, credential, serviceName, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
result.ServiceName = serviceName
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// concurrentOracleScan 并发扫描Oracle服务
|
||||
func concurrentOracleScan(ctx context.Context, info *Common.HostInfo, credentials []OracleCredential, timeoutSeconds int64, maxRetries int) *OracleScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *OracleScanResult, 1)
|
||||
workChan := make(chan OracleCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
// 尝试所有常见服务名
|
||||
result := tryAllServiceNames(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result != nil && result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 执行Oracle连接
|
||||
done := make(chan struct {
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("Oracle并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryOracleCredential 尝试单个Oracle凭据
|
||||
func tryOracleCredential(ctx context.Context, info *Common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &OracleScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
|
||||
// 在协程中执行数据库连接
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
success, err := OracleConn(connCtx, info, credential.Username, credential.Password, serviceName, false)
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
// 已超时或取消,不发送结果
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
}{success, err}:
|
||||
}
|
||||
}()
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := OracleConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 等待结果或连接超时
|
||||
var success bool
|
||||
var err error
|
||||
|
||||
// 等待结果或超时
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
success = result.success
|
||||
err = result.err
|
||||
case <-connCtx.Done():
|
||||
err = connCtx.Err()
|
||||
}
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "oracle",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
// 取消连接超时上下文
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &OracleScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 如果是认证错误,不需要重试
|
||||
if strings.Contains(err.Error(), "ORA-01017") {
|
||||
break // 认证失败
|
||||
}
|
||||
|
||||
// 处理错误情况
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("Oracle %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &OracleScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// tryOracleSysCredential 尝试SYS用户SYSDBA模式连接
|
||||
func tryOracleSysCredential(ctx context.Context, info *Common.HostInfo, credential OracleCredential, serviceName string, timeoutSeconds int64, maxRetries int) *OracleScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &OracleScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试SYS用户SYSDBA模式: %s:%s@%s", retry+1, credential.Username, credential.Password, serviceName))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
|
||||
// 在协程中执行数据库连接
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
success, err := OracleConn(connCtx, info, credential.Username, credential.Password, serviceName, true)
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
// 已超时或取消,不发送结果
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或连接超时
|
||||
var success bool
|
||||
var err error
|
||||
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
success = result.success
|
||||
err = result.err
|
||||
case <-connCtx.Done():
|
||||
err = connCtx.Err()
|
||||
}
|
||||
|
||||
// 取消连接超时上下文
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &OracleScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 如果是认证错误,不需要重试
|
||||
if strings.Contains(err.Error(), "ORA-01017") {
|
||||
break // 认证失败
|
||||
}
|
||||
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &OracleScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// OracleConn 尝试Oracle连接
|
||||
func OracleConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
host, port, username, password := info.Host, info.Ports, user, pass
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
func OracleConn(ctx context.Context, info *Common.HostInfo, user string, pass string, serviceName string, asSysdba bool) (bool, error) {
|
||||
host, port := info.Host, info.Ports
|
||||
|
||||
// 构造连接字符串
|
||||
connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/orcl",
|
||||
username, password, host, port)
|
||||
// 构造连接字符串,添加更多参数
|
||||
connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/%s?connect_timeout=%d",
|
||||
user, pass, host, port, serviceName, Common.Timeout)
|
||||
|
||||
// 对SYS用户使用SYSDBA权限
|
||||
if asSysdba {
|
||||
connStr += "&sysdba=1"
|
||||
}
|
||||
|
||||
// 建立数据库连接
|
||||
db, err := sql.Open("oracle", connStr)
|
||||
@ -123,14 +385,51 @@ func OracleConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
defer db.Close()
|
||||
|
||||
// 设置连接参数
|
||||
db.SetConnMaxLifetime(timeout)
|
||||
db.SetConnMaxIdleTime(timeout)
|
||||
db.SetConnMaxLifetime(time.Duration(Common.Timeout) * time.Second)
|
||||
db.SetConnMaxIdleTime(time.Duration(Common.Timeout) * time.Second)
|
||||
db.SetMaxIdleConns(0)
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
// 使用上下文测试连接
|
||||
pingCtx, cancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 测试连接
|
||||
if err = db.Ping(); err != nil {
|
||||
err = db.PingContext(pingCtx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 不需要额外的查询验证,连接成功即可
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// saveOracleResult 保存Oracle扫描结果
|
||||
func saveOracleResult(info *Common.HostInfo, target string, credential OracleCredential, serviceName string) {
|
||||
var successMsg string
|
||||
if strings.ToUpper(credential.Username) == "SYS" {
|
||||
successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s (可能需要SYSDBA权限)",
|
||||
target, credential.Username, credential.Password, serviceName)
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v 服务名: %s",
|
||||
target, credential.Username, credential.Password, serviceName)
|
||||
}
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "oracle",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"service_name": serviceName,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
423
Plugins/POP3.go
423
Plugins/POP3.go
@ -2,152 +2,347 @@ package Plugins
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func POP3Scan(info *Common.HostInfo) (tmperr error) {
|
||||
// POP3Credential 表示一个POP3凭据
|
||||
type POP3Credential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// POP3ScanResult 表示POP3扫描结果
|
||||
type POP3ScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential POP3Credential
|
||||
IsTLS bool
|
||||
}
|
||||
|
||||
func POP3Scan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalUsers := len(Common.Userdict["pop3"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []POP3Credential
|
||||
for _, user := range Common.Userdict["pop3"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, POP3Credential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["pop3"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
isTLS bool
|
||||
}, 1)
|
||||
// 使用工作池并发扫描,但需要限制速率
|
||||
result := concurrentPOP3Scan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
savePOP3Result(info, target, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, isTLS, err := POP3Conn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
isTLS bool
|
||||
}{success, err, isTLS}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("POP3扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
// concurrentPOP3Scan 并发扫描POP3服务(包含速率限制)
|
||||
func concurrentPOP3Scan(ctx context.Context, info *Common.HostInfo, credentials []POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult {
|
||||
// 不使用ModuleThreadNum控制并发数,必须单线程
|
||||
maxConcurrent := 1
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 1 // POP3默认并发更低
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *POP3ScanResult, 1)
|
||||
|
||||
// 创建限速通道,控制请求频率
|
||||
// 每次发送前需要从中获取令牌,确保请求间隔
|
||||
rateLimiter := make(chan struct{}, maxConcurrent)
|
||||
|
||||
// 初始填充令牌
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
rateLimiter <- struct{}{}
|
||||
}
|
||||
|
||||
// 使用动态的请求间隔
|
||||
requestInterval := 1500 * time.Millisecond // 默认间隔1.5秒
|
||||
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 创建任务队列
|
||||
taskQueue := make(chan POP3Credential, len(credentials))
|
||||
for _, cred := range credentials {
|
||||
taskQueue <- cred
|
||||
}
|
||||
close(taskQueue)
|
||||
|
||||
// 记录已处理的凭据数
|
||||
var processedCount int32
|
||||
processedCountMutex := &sync.Mutex{}
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func(workerID int) {
|
||||
defer wg.Done()
|
||||
|
||||
for credential := range taskQueue {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v", target, user, pass)
|
||||
if result.isTLS {
|
||||
successMsg += " (TLS)"
|
||||
}
|
||||
Common.LogSuccess(successMsg)
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
case <-rateLimiter:
|
||||
// 获取令牌,可以发送请求
|
||||
processedCountMutex.Lock()
|
||||
processedCount++
|
||||
currentCount := processedCount
|
||||
processedCountMutex.Unlock()
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "pop3",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
"tls": result.isTLS,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 工作线程 %d 尝试: %s:%s",
|
||||
currentCount, len(credentials), workerID, credential.Username, credential.Password))
|
||||
|
||||
result := tryPOP3Credential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
|
||||
// 尝试完成后添加延迟,然后归还令牌
|
||||
time.Sleep(requestInterval)
|
||||
|
||||
// 未被取消的情况下归还令牌
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
// 如果已经取消,不再归还令牌
|
||||
default:
|
||||
rateLimiter <- struct{}{}
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("POP3服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
|
||||
target, user, pass, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
continue
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// 等待结果或完成
|
||||
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("POP3并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryPOP3Credential 尝试单个POP3凭据
|
||||
func tryPOP3Credential(ctx context.Context, info *Common.HostInfo, credential POP3Credential, timeoutSeconds int64, maxRetries int) *POP3ScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &POP3ScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
// 重试间隔时间增加,避免触发服务器限制
|
||||
retryDelay := time.Duration(retry*2000) * time.Millisecond
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, isTLS, err := POP3Conn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &POP3ScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
IsTLS: isTLS,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 处理特定错误情况
|
||||
if strings.Contains(strings.ToLower(err.Error()), "too many connections") ||
|
||||
strings.Contains(strings.ToLower(err.Error()), "connection refused") ||
|
||||
strings.Contains(strings.ToLower(err.Error()), "timeout") {
|
||||
// 服务器可能限制连接,增加等待时间
|
||||
waitTime := time.Duration((retry+1)*3000) * time.Millisecond
|
||||
Common.LogDebug(fmt.Sprintf("服务器可能限制连接,等待 %v 后重试", waitTime))
|
||||
time.Sleep(waitTime)
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &POP3ScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
func POP3Conn(info *Common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) {
|
||||
// POP3Conn 尝试POP3连接
|
||||
func POP3Conn(ctx context.Context, info *Common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) {
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
|
||||
// 首先尝试普通连接
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
if err == nil {
|
||||
if flag, err := tryPOP3Auth(conn, user, pass, timeout); err == nil {
|
||||
return flag, false, nil
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
isTLS bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 在协程中尝试连接,支持取消
|
||||
go func() {
|
||||
// 首先尝试普通连接
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout,
|
||||
// 增加KeepAlive设置,可能有助于处理一些服务器的限制
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||
if err == nil {
|
||||
flag, authErr := tryPOP3Auth(conn, user, pass, timeout)
|
||||
conn.Close()
|
||||
if authErr == nil && flag {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
isTLS bool
|
||||
err error
|
||||
}{flag, false, nil}:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// 如果普通连接失败,尝试TLS连接
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
conn, err = tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
return false, false, fmt.Errorf("连接失败: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// 如果普通连接失败,尝试TLS连接
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
success, err = tryPOP3Auth(conn, user, pass, timeout)
|
||||
return success, true, err
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
|
||||
if tlsErr != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
isTLS bool
|
||||
err error
|
||||
}{false, false, fmt.Errorf("连接失败: %v", tlsErr)}:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer tlsConn.Close()
|
||||
|
||||
flag, authErr := tryPOP3Auth(tlsConn, user, pass, timeout)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
isTLS bool
|
||||
err error
|
||||
}{flag, true, authErr}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.isTLS, result.err
|
||||
case <-ctx.Done():
|
||||
return false, false, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// tryPOP3Auth 尝试POP3认证
|
||||
func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) {
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
// 设置较长的超时时间以适应一些较慢的服务器
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
|
||||
// 读取欢迎信息
|
||||
_, err := reader.ReadString('\n')
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("读取欢迎消息失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查是否有错误信息
|
||||
if strings.Contains(strings.ToLower(response), "error") ||
|
||||
strings.Contains(strings.ToLower(response), "too many") {
|
||||
return false, fmt.Errorf("服务器拒绝连接: %s", strings.TrimSpace(response))
|
||||
}
|
||||
|
||||
// 发送用户名前等待一小段时间
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
// 发送用户名
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("USER %s\r\n", user)))
|
||||
@ -157,14 +352,17 @@ func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration)
|
||||
|
||||
// 读取用户名响应
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
response, err := reader.ReadString('\n')
|
||||
response, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("读取用户名响应失败: %v", err)
|
||||
}
|
||||
if !strings.Contains(response, "+OK") {
|
||||
return false, fmt.Errorf("用户名无效")
|
||||
return false, fmt.Errorf("用户名无效: %s", strings.TrimSpace(response))
|
||||
}
|
||||
|
||||
// 发送密码前等待一小段时间
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
// 发送密码
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", pass)))
|
||||
@ -183,5 +381,34 @@ func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("认证失败")
|
||||
return false, fmt.Errorf("认证失败: %s", strings.TrimSpace(response))
|
||||
}
|
||||
|
||||
// savePOP3Result 保存POP3扫描结果
|
||||
func savePOP3Result(info *Common.HostInfo, target string, result *POP3ScanResult) {
|
||||
tlsStatus := ""
|
||||
if result.IsTLS {
|
||||
tlsStatus = " (TLS)"
|
||||
}
|
||||
|
||||
successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v%s",
|
||||
target, result.Credential.Username, result.Credential.Password, tlsStatus)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "pop3",
|
||||
"username": result.Credential.Username,
|
||||
"password": result.Credential.Password,
|
||||
"type": "weak-password",
|
||||
"tls": result.IsTLS,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
@ -1,115 +1,205 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PostgresCredential 表示一个PostgreSQL凭据
|
||||
type PostgresCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// PostgresScanResult 表示PostgreSQL扫描结果
|
||||
type PostgresScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential PostgresCredential
|
||||
}
|
||||
|
||||
// PostgresScan 执行PostgreSQL服务扫描
|
||||
func PostgresScan(info *Common.HostInfo) (tmperr error) {
|
||||
func PostgresScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
maxRetries := Common.MaxRetries
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalUsers := len(Common.Userdict["postgresql"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 构建凭据列表
|
||||
var credentials []PostgresCredential
|
||||
for _, user := range Common.Userdict["postgresql"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, PostgresCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["postgresql"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentPostgresScan(ctx, info, credentials, Common.Timeout+10, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
savePostgresResult(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := PostgresConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("PostgreSQL扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
// concurrentPostgresScan 并发扫描PostgreSQL服务
|
||||
func concurrentPostgresScan(ctx context.Context, info *Common.HostInfo, credentials []PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *PostgresScanResult, 1)
|
||||
workChan := make(chan PostgresCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "postgresql",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryPostgresCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("PostgreSQL服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("PostgreSQL并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryPostgresCredential 尝试单个PostgreSQL凭据
|
||||
func tryPostgresCredential(ctx context.Context, info *Common.HostInfo, credential PostgresCredential, timeoutSeconds int64, maxRetries int) *PostgresScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &PostgresScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建单个连接超时的上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := PostgresConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &PostgresScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &PostgresScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// PostgresConn 尝试PostgreSQL连接
|
||||
func PostgresConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
func PostgresConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
// 构造连接字符串
|
||||
connStr := fmt.Sprintf(
|
||||
"postgres://%v:%v@%v:%v/postgres?sslmode=disable",
|
||||
user, pass, info.Host, info.Ports,
|
||||
"postgres://%v:%v@%v:%v/postgres?sslmode=disable&connect_timeout=%d",
|
||||
user, pass, info.Host, info.Ports, Common.Timeout/1000, // 转换为秒
|
||||
)
|
||||
|
||||
// 建立数据库连接
|
||||
@ -120,12 +210,45 @@ func PostgresConn(info *Common.HostInfo, user string, pass string) (bool, error)
|
||||
defer db.Close()
|
||||
|
||||
// 设置连接参数
|
||||
db.SetConnMaxLifetime(timeout)
|
||||
db.SetConnMaxLifetime(time.Duration(Common.Timeout) * time.Millisecond)
|
||||
db.SetMaxOpenConns(1)
|
||||
db.SetMaxIdleConns(0)
|
||||
|
||||
// 测试连接
|
||||
if err = db.Ping(); err != nil {
|
||||
// 使用上下文测试连接
|
||||
err = db.PingContext(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 简单查询测试权限
|
||||
var version string
|
||||
err = db.QueryRowContext(ctx, "SELECT version()").Scan(&version)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// savePostgresResult 保存PostgreSQL扫描结果
|
||||
func savePostgresResult(info *Common.HostInfo, target string, credential PostgresCredential) {
|
||||
successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v",
|
||||
target, credential.Username, credential.Password)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "postgresql",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
287
Plugins/RDP.go
287
Plugins/RDP.go
@ -1,6 +1,7 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
@ -22,84 +23,200 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Brutelist 表示暴力破解的用户名密码组合
|
||||
type Brutelist struct {
|
||||
user string
|
||||
pass string
|
||||
// RDPCredential 表示一个RDP凭据
|
||||
type RDPCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// RDPScanResult 表示RDP扫描结果
|
||||
type RDPScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential RDPCredential
|
||||
}
|
||||
|
||||
// RdpScan 执行RDP服务扫描
|
||||
func RdpScan(info *Common.HostInfo) (tmperr error) {
|
||||
func RdpScan(info *Common.HostInfo) error {
|
||||
defer func() {
|
||||
recover()
|
||||
if r := recover(); r != nil {
|
||||
Common.LogError(fmt.Sprintf("RDP扫描panic: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
port, _ := strconv.Atoi(info.Ports)
|
||||
total := len(Common.Userdict["rdp"]) * len(Common.Passwords)
|
||||
num := 0
|
||||
target := fmt.Sprintf("%v:%v", info.Host, port)
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 遍历用户名密码组合
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 构建凭据列表
|
||||
var credentials []RDPCredential
|
||||
for _, user := range Common.Userdict["rdp"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
num++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
|
||||
// 尝试连接
|
||||
flag, err := RdpConn(info.Host, Common.Domain, user, pass, port, Common.Timeout)
|
||||
|
||||
if flag && err == nil {
|
||||
// 连接成功
|
||||
var result string
|
||||
if Common.Domain != "" {
|
||||
result = fmt.Sprintf("RDP %v Domain: %v\\%v Password: %v", target, Common.Domain, user, pass)
|
||||
} else {
|
||||
result = fmt.Sprintf("RDP %v Username: %v Password: %v", target, user, pass)
|
||||
}
|
||||
Common.LogSuccess(result)
|
||||
|
||||
// 保存结果
|
||||
details := map[string]interface{}{
|
||||
"port": port,
|
||||
"service": "rdp",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
}
|
||||
if Common.Domain != "" {
|
||||
details["domain"] = Common.Domain
|
||||
}
|
||||
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 连接失败
|
||||
errlog := fmt.Sprintf("(%v/%v) RDP %v Username: %v Password: %v Error: %v",
|
||||
num, total, target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, RDPCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
Domain: Common.Domain,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return tmperr
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["rdp"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentRdpScan(ctx, info, credentials, port, Common.Timeout)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveRdpResult(info, target, port, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("RDP扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// concurrentRdpScan 并发扫描RDP服务
|
||||
func concurrentRdpScan(ctx context.Context, info *Common.HostInfo, credentials []RDPCredential, port int, timeoutSeconds int64) *RDPScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *RDPScanResult, 1)
|
||||
workChan := make(chan RDPCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryRdpCredential(scanCtx, info.Host, credential, port, timeoutSeconds)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("RDP并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryRdpCredential 尝试单个RDP凭据
|
||||
func tryRdpCredential(ctx context.Context, host string, credential RDPCredential, port int, timeoutSeconds int64) *RDPScanResult {
|
||||
// 创建结果通道
|
||||
resultChan := make(chan *RDPScanResult, 1)
|
||||
|
||||
// 在协程中进行连接尝试
|
||||
go func() {
|
||||
success, err := RdpConn(host, credential.Domain, credential.Username, credential.Password, port, timeoutSeconds)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// 上下文已取消,不返回结果
|
||||
case resultChan <- &RDPScanResult{
|
||||
Success: success,
|
||||
Error: err,
|
||||
Credential: credential,
|
||||
}:
|
||||
// 成功发送结果
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result
|
||||
case <-ctx.Done():
|
||||
return &RDPScanResult{
|
||||
Success: false,
|
||||
Error: ctx.Err(),
|
||||
Credential: credential,
|
||||
}
|
||||
case <-time.After(time.Duration(timeoutSeconds) * time.Second):
|
||||
// 单个连接超时
|
||||
return &RDPScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("连接超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RdpConn 尝试RDP连接
|
||||
func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool, error) {
|
||||
defer func() {
|
||||
recover()
|
||||
if r := recover(); r != nil {
|
||||
glog.Error("RDP连接panic:", r)
|
||||
}
|
||||
}()
|
||||
|
||||
target := fmt.Sprintf("%s:%d", ip, port)
|
||||
|
||||
// 创建RDP客户端
|
||||
@ -111,6 +228,43 @@ func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool,
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// saveRdpResult 保存RDP扫描结果
|
||||
func saveRdpResult(info *Common.HostInfo, target string, port int, credential RDPCredential) {
|
||||
var successMsg string
|
||||
|
||||
if credential.Domain != "" {
|
||||
successMsg = fmt.Sprintf("RDP %v Domain: %v\\%v Password: %v",
|
||||
target, credential.Domain, credential.Username, credential.Password)
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("RDP %v Username: %v Password: %v",
|
||||
target, credential.Username, credential.Password)
|
||||
}
|
||||
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
details := map[string]interface{}{
|
||||
"port": port,
|
||||
"service": "rdp",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
}
|
||||
|
||||
if credential.Domain != "" {
|
||||
details["domain"] = credential.Domain
|
||||
}
|
||||
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
||||
// Client RDP客户端结构
|
||||
type Client struct {
|
||||
Host string // 服务地址(ip:port)
|
||||
@ -161,8 +315,25 @@ func (g *Client) Login(domain, user, pwd string, timeout int64) error {
|
||||
// 设置事件处理器
|
||||
g.setupEventHandlers(wg, &breakFlag, &err)
|
||||
|
||||
wg.Wait()
|
||||
return err
|
||||
// 添加额外的超时保护
|
||||
connectionDone := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(connectionDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-connectionDone:
|
||||
// 连接过程正常完成
|
||||
return err
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
// 超时
|
||||
if !breakFlag {
|
||||
breakFlag = true
|
||||
wg.Done()
|
||||
}
|
||||
return fmt.Errorf("连接超时")
|
||||
}
|
||||
}
|
||||
|
||||
// initProtocolStack 初始化RDP协议栈
|
||||
|
@ -1,205 +1,308 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RabbitMQCredential 表示一个RabbitMQ凭据
|
||||
type RabbitMQCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// RabbitMQScanResult 表示扫描结果
|
||||
type RabbitMQScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential RabbitMQCredential
|
||||
ErrorMsg string // 保存详细的错误信息
|
||||
}
|
||||
|
||||
// RabbitMQScan 执行 RabbitMQ 服务扫描
|
||||
func RabbitMQScan(info *Common.HostInfo) (tmperr error) {
|
||||
func RabbitMQScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
Common.LogDebug("尝试默认账号 guest/guest")
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 先测试默认账号 guest/guest
|
||||
user, pass := "guest", "guest"
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试默认账号: guest/guest", retryCount+1))
|
||||
}
|
||||
Common.LogDebug("尝试默认账号 guest/guest")
|
||||
defaultCredential := RabbitMQCredential{Username: "guest", Password: "guest"}
|
||||
defaultResult := tryRabbitMQCredential(ctx, info, defaultCredential, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
success, err := RabbitMQConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "rabbitmq",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("RabbitMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
|
||||
target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
if defaultResult.Success {
|
||||
saveRabbitMQResult(info, target, defaultResult.Credential)
|
||||
return nil
|
||||
} else if defaultResult.Error != nil {
|
||||
// 打印默认账号的详细错误信息
|
||||
Common.LogDebug(fmt.Sprintf("默认账号 guest/guest 失败,详细错误: %s", defaultResult.ErrorMsg))
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["rabbitmq"])
|
||||
totalPass := len(Common.Passwords)
|
||||
total := totalUsers * totalPass
|
||||
tried := 0
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
// 遍历其他用户名密码组合
|
||||
// 构建其他凭据列表
|
||||
var credentials []RabbitMQCredential
|
||||
for _, user := range Common.Userdict["rabbitmq"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, RabbitMQCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["rabbitmq"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentRabbitMQScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveRabbitMQResult(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(user, pass string) {
|
||||
success, err := RabbitMQConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("RabbitMQ扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了默认账号
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
// concurrentRabbitMQScan 并发扫描RabbitMQ服务
|
||||
func concurrentRabbitMQScan(ctx context.Context, info *Common.HostInfo, credentials []RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *RabbitMQScanResult, 1)
|
||||
workChan := make(chan RabbitMQCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v",
|
||||
target, user, pass)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "rabbitmq",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryRabbitMQCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("RabbitMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
|
||||
target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("RabbitMQ并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryRabbitMQCredential 尝试单个RabbitMQ凭据
|
||||
func tryRabbitMQCredential(ctx context.Context, info *Common.HostInfo, credential RabbitMQCredential, timeoutSeconds int64, maxRetries int) *RabbitMQScanResult {
|
||||
var lastErr error
|
||||
var errorMsg string
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &RabbitMQScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
ErrorMsg: "全局超时",
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err, detailErr := RabbitMQConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &RabbitMQScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
errorMsg = detailErr
|
||||
|
||||
// 打印详细的错误信息,包括所有原始错误信息
|
||||
Common.LogDebug(fmt.Sprintf("凭据 %s:%s 失败,错误详情: %s",
|
||||
credential.Username, credential.Password, errorMsg))
|
||||
|
||||
if err != nil {
|
||||
// 可以根据错误信息类型来决定是否需要重试
|
||||
// 例如,如果错误是认证错误,则无需重试
|
||||
if strings.Contains(errorMsg, "ACCESS_REFUSED") {
|
||||
Common.LogDebug("认证错误,无需重试")
|
||||
break
|
||||
}
|
||||
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried+1))
|
||||
return tmperr
|
||||
return &RabbitMQScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
ErrorMsg: errorMsg,
|
||||
}
|
||||
}
|
||||
|
||||
// RabbitMQConn 尝试 RabbitMQ 连接
|
||||
func RabbitMQConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
func RabbitMQConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, error, string) {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
// 构造 AMQP URL
|
||||
amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%s/", user, pass, host, port)
|
||||
|
||||
// 配置连接
|
||||
config := amqp.Config{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return net.DialTimeout(network, addr, timeout)
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
detailErr string
|
||||
}, 1)
|
||||
|
||||
// 在协程中尝试连接
|
||||
go func() {
|
||||
// 配置连接
|
||||
config := amqp.Config{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
},
|
||||
}
|
||||
|
||||
// 尝试连接
|
||||
conn, err := amqp.DialConfig(amqpURL, config)
|
||||
|
||||
if err != nil {
|
||||
detailErr := err.Error()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
detailErr string
|
||||
}{false, err, detailErr}:
|
||||
}
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
detailErr string
|
||||
}{true, nil, ""}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.err, result.detailErr
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err(), ctx.Err().Error()
|
||||
}
|
||||
}
|
||||
|
||||
// saveRabbitMQResult 保存RabbitMQ扫描结果
|
||||
func saveRabbitMQResult(info *Common.HostInfo, target string, credential RabbitMQCredential) {
|
||||
successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v",
|
||||
target, credential.Username, credential.Password)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "rabbitmq",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
|
||||
// 尝试连接
|
||||
conn, err := amqp.DialConfig(amqpURL, config)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 如果成功连接
|
||||
if conn != nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("认证失败")
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
698
Plugins/Rsync.go
698
Plugins/Rsync.go
@ -1,275 +1,483 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func RsyncScan(info *Common.HostInfo) (tmperr error) {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
Common.LogDebug("尝试匿名访问...")
|
||||
|
||||
// 首先测试匿名访问
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试匿名访问", retryCount+1))
|
||||
}
|
||||
|
||||
flag, err := RsyncConn(info, "", "")
|
||||
if flag && err == nil {
|
||||
Common.LogSuccess(fmt.Sprintf("Rsync服务 %s 匿名访问成功", target))
|
||||
|
||||
// 保存匿名访问结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "rsync",
|
||||
"type": "anonymous-access",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("Rsync服务 %s 匿名访问失败: %v", target, err))
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["rsync"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
for _, user := range Common.Userdict["rsync"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func(user, pass string) {
|
||||
flag, err := RsyncConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{flag && err == nil, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success {
|
||||
Common.LogSuccess(fmt.Sprintf("Rsync服务 %s 爆破成功 用户名: %v 密码: %v",
|
||||
target, user, pass))
|
||||
|
||||
// 保存爆破成功结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "rsync",
|
||||
"type": "weak-password",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
Common.LogError(fmt.Sprintf("Rsync服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
|
||||
target, user, pass, err))
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
// RsyncCredential 表示一个Rsync凭据
|
||||
type RsyncCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func RsyncConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
// RsyncScanResult 表示Rsync扫描结果
|
||||
type RsyncScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential RsyncCredential
|
||||
IsAnonymous bool
|
||||
ModuleName string
|
||||
}
|
||||
|
||||
func RsyncScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return nil
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 首先尝试匿名访问
|
||||
Common.LogDebug("尝试匿名访问...")
|
||||
anonymousResult := tryRsyncCredential(ctx, info, RsyncCredential{"", ""}, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
if anonymousResult.Success {
|
||||
// 匿名访问成功
|
||||
saveRsyncResult(info, target, anonymousResult)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 构建凭据列表
|
||||
var credentials []RsyncCredential
|
||||
for _, user := range Common.Userdict["rsync"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, RsyncCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["rsync"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentRsyncScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 保存成功结果
|
||||
saveRsyncResult(info, target, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Rsync扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// concurrentRsyncScan 并发扫描Rsync服务
|
||||
func concurrentRsyncScan(ctx context.Context, info *Common.HostInfo, credentials []RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *RsyncScanResult, 1)
|
||||
workChan := make(chan RsyncCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryRsyncCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("Rsync并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryRsyncCredential 尝试单个Rsync凭据
|
||||
func tryRsyncCredential(ctx context.Context, info *Common.HostInfo, credential RsyncCredential, timeoutSeconds int64, maxRetries int) *RsyncScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &RsyncScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, moduleName, err := RsyncConn(connCtx, info, credential.Username, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
isAnonymous := credential.Username == "" && credential.Password == ""
|
||||
return &RsyncScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
IsAnonymous: isAnonymous,
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &RsyncScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// RsyncConn 尝试Rsync连接
|
||||
func RsyncConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (bool, string, error) {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
// 设置带有上下文的拨号器
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
// 建立连接
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
|
||||
conn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", host, port))
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
// 创建结果通道用于超时控制
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 1. 读取服务器初始greeting
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 在协程中处理连接,以支持上下文取消
|
||||
go func() {
|
||||
buffer := make([]byte, 1024)
|
||||
|
||||
greeting := string(buffer[:n])
|
||||
if !strings.HasPrefix(greeting, "@RSYNCD:") {
|
||||
return false, fmt.Errorf("不是Rsync服务")
|
||||
}
|
||||
|
||||
// 获取服务器版本号
|
||||
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
|
||||
|
||||
// 2. 回应相同的版本号
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 3. 选择模块 - 先列出可用模块
|
||||
_, err = conn.Write([]byte("#list\n"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 4. 读取模块列表
|
||||
var moduleList strings.Builder
|
||||
for {
|
||||
n, err = conn.Read(buffer)
|
||||
// 1. 读取服务器初始greeting
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
chunk := string(buffer[:n])
|
||||
moduleList.WriteString(chunk)
|
||||
if strings.Contains(chunk, "@RSYNCD: EXIT") {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
modules := strings.Split(moduleList.String(), "\n")
|
||||
for _, module := range modules {
|
||||
if strings.HasPrefix(module, "@RSYNCD") || module == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取模块名
|
||||
moduleName := strings.Fields(module)[0]
|
||||
|
||||
// 5. 为每个模块创建新连接尝试认证
|
||||
authConn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer authConn.Close()
|
||||
|
||||
// 重复初始握手
|
||||
_, err = authConn.Read(buffer)
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// 6. 选择模块
|
||||
_, err = authConn.Write([]byte(moduleName + "\n"))
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// 7. 等待认证挑战
|
||||
n, err = authConn.Read(buffer)
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
authResponse := string(buffer[:n])
|
||||
if strings.Contains(authResponse, "@RSYNCD: OK") {
|
||||
// 模块不需要认证
|
||||
if user == "" && pass == "" {
|
||||
result := fmt.Sprintf("Rsync服务 %v:%v 模块:%v 无需认证", host, port, moduleName)
|
||||
Common.LogSuccess(result)
|
||||
return true, nil
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}{false, "", err}:
|
||||
}
|
||||
} else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") {
|
||||
if user != "" && pass != "" {
|
||||
// 8. 发送认证信息
|
||||
authString := fmt.Sprintf("%s %s\n", user, pass)
|
||||
_, err = authConn.Write([]byte(authString))
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 9. 读取认证结果
|
||||
n, err = authConn.Read(buffer)
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
greeting := string(buffer[:n])
|
||||
if !strings.HasPrefix(greeting, "@RSYNCD:") {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}{false, "", fmt.Errorf("不是Rsync服务")}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(string(buffer[:n]), "@ERROR") {
|
||||
result := fmt.Sprintf("Rsync服务 %v:%v 模块:%v 认证成功 用户名: %v 密码: %v",
|
||||
host, port, moduleName, user, pass)
|
||||
Common.LogSuccess(result)
|
||||
return true, nil
|
||||
}
|
||||
// 获取服务器版本号
|
||||
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
|
||||
|
||||
// 2. 回应相同的版本号
|
||||
conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}{false, "", err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 选择模块 - 先列出可用模块
|
||||
conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_, err = conn.Write([]byte("#list\n"))
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}{false, "", err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 读取模块列表
|
||||
var moduleList strings.Builder
|
||||
for {
|
||||
// 检查上下文是否取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, err = conn.Read(buffer)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
chunk := string(buffer[:n])
|
||||
moduleList.WriteString(chunk)
|
||||
if strings.Contains(chunk, "@RSYNCD: EXIT") {
|
||||
break
|
||||
}
|
||||
}
|
||||
authConn.Close()
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("认证失败或无可用模块")
|
||||
modules := strings.Split(moduleList.String(), "\n")
|
||||
for _, module := range modules {
|
||||
if strings.HasPrefix(module, "@RSYNCD") || module == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取模块名
|
||||
moduleName := strings.Fields(module)[0]
|
||||
|
||||
// 检查上下文是否取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// 5. 为每个模块创建新连接尝试认证
|
||||
authConn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", host, port))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer authConn.Close()
|
||||
|
||||
// 重复初始握手
|
||||
authConn.SetReadDeadline(time.Now().Add(timeout))
|
||||
_, err = authConn.Read(buffer)
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
authConn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// 6. 选择模块
|
||||
authConn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_, err = authConn.Write([]byte(moduleName + "\n"))
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// 7. 等待认证挑战
|
||||
authConn.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, err = authConn.Read(buffer)
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
authResponse := string(buffer[:n])
|
||||
if strings.Contains(authResponse, "@RSYNCD: OK") {
|
||||
// 模块不需要认证
|
||||
if user == "" && pass == "" {
|
||||
authConn.Close()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}{true, moduleName, nil}:
|
||||
}
|
||||
return
|
||||
}
|
||||
} else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") {
|
||||
if user != "" && pass != "" {
|
||||
// 8. 发送认证信息
|
||||
authString := fmt.Sprintf("%s %s\n", user, pass)
|
||||
authConn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_, err = authConn.Write([]byte(authString))
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// 9. 读取认证结果
|
||||
authConn.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, err = authConn.Read(buffer)
|
||||
if err != nil {
|
||||
authConn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(string(buffer[:n]), "@ERROR") {
|
||||
authConn.Close()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}{true, moduleName, nil}:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
authConn.Close()
|
||||
}
|
||||
|
||||
// 如果执行到这里,没有找到成功的认证
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
moduleName string
|
||||
err error
|
||||
}{false, "", fmt.Errorf("认证失败或无可用模块")}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待结果或上下文取消
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
return result.success, result.moduleName, result.err
|
||||
case <-ctx.Done():
|
||||
return false, "", ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// saveRsyncResult 保存Rsync扫描结果
|
||||
func saveRsyncResult(info *Common.HostInfo, target string, result *RsyncScanResult) {
|
||||
var successMsg string
|
||||
var details map[string]interface{}
|
||||
|
||||
if result.IsAnonymous {
|
||||
successMsg = fmt.Sprintf("Rsync服务 %s 匿名访问成功 模块: %s", target, result.ModuleName)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "rsync",
|
||||
"type": "anonymous-access",
|
||||
"module": result.ModuleName,
|
||||
}
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("Rsync服务 %s 爆破成功 用户名: %v 密码: %v 模块: %s",
|
||||
target, result.Credential.Username, result.Credential.Password, result.ModuleName)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "rsync",
|
||||
"type": "weak-password",
|
||||
"username": result.Credential.Username,
|
||||
"password": result.Credential.Password,
|
||||
"module": result.ModuleName,
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
315
Plugins/SMB.go
315
Plugins/SMB.go
@ -1,78 +1,258 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"github.com/stacktitan/smb/smb"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SmbScan(info *Common.HostInfo) (tmperr error) {
|
||||
// SmbCredential 表示一个SMB凭据
|
||||
type SmbCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// SmbScanResult 表示SMB扫描结果
|
||||
type SmbScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential SmbCredential
|
||||
}
|
||||
|
||||
func SmbScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return nil
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
|
||||
// 遍历所有用户
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 构建凭据列表
|
||||
var credentials []SmbCredential
|
||||
for _, user := range Common.Userdict["smb"] {
|
||||
// 遍历该用户的所有密码
|
||||
for _, pass := range Common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
|
||||
success, err := doWithTimeOut(info, user, pass)
|
||||
if success {
|
||||
// 构建结果消息
|
||||
var successMsg string
|
||||
details := map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "smb",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
}
|
||||
|
||||
if Common.Domain != "" {
|
||||
successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, Common.Domain, user, pass)
|
||||
details["domain"] = Common.Domain
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, user, pass)
|
||||
}
|
||||
|
||||
// 记录成功日志
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("SMB认证失败 %s %s:%s %v", target, user, pass, err)
|
||||
Common.LogError(errMsg)
|
||||
|
||||
// 等待失败日志打印完成
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if strings.Contains(err.Error(), "账号锁定") {
|
||||
// 账号锁定时跳过当前用户的剩余密码
|
||||
break // 跳出密码循环,继续下一个用户
|
||||
}
|
||||
}
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, SmbCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["smb"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentSmbScan(ctx, info, credentials, Common.Timeout)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveSmbResult(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
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)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
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扫描结果
|
||||
func saveSmbResult(info *Common.HostInfo, target string, credential SmbCredential) {
|
||||
// 构建结果消息
|
||||
var successMsg string
|
||||
details := map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "smb",
|
||||
"username": credential.Username,
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
}
|
||||
|
||||
if Common.Domain != "" {
|
||||
successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, Common.Domain, credential.Username, credential.Password)
|
||||
details["domain"] = Common.Domain
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, credential.Username, credential.Password)
|
||||
}
|
||||
|
||||
// 记录成功日志
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
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,
|
||||
@ -116,34 +296,3 @@ func SmblConn(info *Common.HostInfo, user string, pass string, signal chan struc
|
||||
signal <- struct{}{}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func doWithTimeOut(info *Common.HostInfo, user string, pass string) (flag bool, err error) {
|
||||
signal := make(chan struct{}, 1)
|
||||
result := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
success, err := SmblConn(info, user, pass, signal)
|
||||
select {
|
||||
case result <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case r := <-result:
|
||||
return r.success, r.err
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
select {
|
||||
case r := <-result:
|
||||
return r.success, r.err
|
||||
default:
|
||||
return false, fmt.Errorf("连接超时")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
514
Plugins/SMB2.go
514
Plugins/SMB2.go
@ -1,146 +1,397 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hirochachacha/go-smb2"
|
||||
)
|
||||
|
||||
// Smb2Credential 表示一个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) (tmperr error) {
|
||||
func SmbScan2(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 使用哈希认证模式
|
||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
Common.LogDebug(fmt.Sprintf("开始SMB2扫描 %s", target))
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 根据是否提供哈希选择认证模式
|
||||
if len(Common.HashBytes) > 0 {
|
||||
return smbHashScan(info)
|
||||
return smbHashScan(ctx, info)
|
||||
}
|
||||
|
||||
// 使用密码认证模式
|
||||
return smbPasswordScan(info)
|
||||
return smbPasswordScan(ctx, info)
|
||||
}
|
||||
|
||||
func smbPasswordScan(info *Common.HostInfo) error {
|
||||
// smbPasswordScan 使用密码进行SMB2认证扫描
|
||||
func smbPasswordScan(ctx context.Context, info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasprint := false
|
||||
|
||||
// 遍历每个用户
|
||||
// 构建凭据列表
|
||||
var credentials []Smb2Credential
|
||||
for _, user := range Common.Userdict["smb"] {
|
||||
accountLocked := false // 添加账户锁定标志
|
||||
|
||||
// 遍历该用户的所有密码
|
||||
for _, pass := range Common.Passwords {
|
||||
if accountLocked { // 如果账户被锁定,跳过剩余密码
|
||||
break
|
||||
}
|
||||
|
||||
pass = strings.ReplaceAll(pass, "{user}", user)
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < Common.MaxRetries; retryCount++ {
|
||||
success, err, printed := Smb2Con(info, user, pass, []byte{}, hasprint)
|
||||
|
||||
if printed {
|
||||
hasprint = true
|
||||
}
|
||||
|
||||
if success {
|
||||
logSuccessfulAuth(info, user, pass, []byte{})
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logFailedAuth(info, user, pass, []byte{}, err)
|
||||
|
||||
// 检查是否账户锁定
|
||||
if strings.Contains(err.Error(), "account has been automatically locked") ||
|
||||
strings.Contains(err.Error(), "account has been locked") {
|
||||
accountLocked = true // 设置锁定标志
|
||||
break
|
||||
}
|
||||
|
||||
// 其他登录失败情况
|
||||
if strings.Contains(err.Error(), "LOGIN_FAILED") ||
|
||||
strings.Contains(err.Error(), "Authentication failed") ||
|
||||
strings.Contains(err.Error(), "attempted logon is invalid") ||
|
||||
strings.Contains(err.Error(), "bad username or authentication") {
|
||||
break
|
||||
}
|
||||
|
||||
if retryCount < Common.MaxRetries-1 {
|
||||
time.Sleep(time.Second * time.Duration(retryCount+2))
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
actualPass := strings.ReplaceAll(pass, "{user}", user)
|
||||
credentials = append(credentials, Smb2Credential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
Hash: []byte{},
|
||||
IsHash: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
Common.LogDebug(fmt.Sprintf("开始SMB2密码认证扫描 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["smb"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
return concurrentSmb2Scan(ctx, info, credentials)
|
||||
}
|
||||
|
||||
func smbHashScan(info *Common.HostInfo) error {
|
||||
// smbHashScan 使用哈希进行SMB2认证扫描
|
||||
func smbHashScan(ctx context.Context, info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasprint := false
|
||||
|
||||
// 遍历每个用户
|
||||
// 构建凭据列表
|
||||
var credentials []Smb2Credential
|
||||
for _, user := range Common.Userdict["smb"] {
|
||||
// 遍历该用户的所有hash
|
||||
for _, hash := range Common.HashBytes {
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < Common.MaxRetries; retryCount++ {
|
||||
success, err, printed := Smb2Con(info, user, "", hash, hasprint)
|
||||
|
||||
if printed {
|
||||
hasprint = true
|
||||
}
|
||||
|
||||
if success {
|
||||
logSuccessfulAuth(info, user, "", hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logFailedAuth(info, user, "", hash, err)
|
||||
|
||||
// 检查是否账户锁定
|
||||
if strings.Contains(err.Error(), "user account has been automatically locked") {
|
||||
// 账户锁定,跳过该用户的剩余hash
|
||||
break
|
||||
}
|
||||
|
||||
// 其他登录失败情况
|
||||
if strings.Contains(err.Error(), "LOGIN_FAILED") ||
|
||||
strings.Contains(err.Error(), "Authentication failed") ||
|
||||
strings.Contains(err.Error(), "attempted logon is invalid") ||
|
||||
strings.Contains(err.Error(), "bad username or authentication") {
|
||||
break
|
||||
}
|
||||
|
||||
if retryCount < Common.MaxRetries-1 {
|
||||
time.Sleep(time.Second * time.Duration(retryCount+1))
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
credentials = append(credentials, Smb2Credential{
|
||||
Username: user,
|
||||
Password: "",
|
||||
Hash: hash,
|
||||
IsHash: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
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连接,使用上下文提供的超时控制
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:445", info.Host))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("连接失败: %v", err), nil
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 配置NTLM认证
|
||||
initiator := smb2.NTLMInitiator{
|
||||
User: user,
|
||||
Domain: Common.Domain,
|
||||
}
|
||||
|
||||
// 设置认证方式(哈希或密码)
|
||||
if len(hash) > 0 {
|
||||
initiator.Hash = hash
|
||||
} else {
|
||||
initiator.Password = pass
|
||||
}
|
||||
|
||||
// 创建SMB2会话
|
||||
dialer := &smb2.Dialer{
|
||||
Initiator: &initiator,
|
||||
}
|
||||
|
||||
// 使用context设置超时
|
||||
session, err := dialer.Dial(conn)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("SMB2会话建立失败: %v", err), nil
|
||||
}
|
||||
defer session.Logoff()
|
||||
|
||||
// 检查上下文是否已取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err(), nil
|
||||
default:
|
||||
}
|
||||
|
||||
// 获取共享列表
|
||||
sharesList, err := session.ListSharenames()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("获取共享列表失败: %v", err), 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 记录成功的认证
|
||||
@ -198,69 +449,6 @@ func logFailedAuth(info *Common.HostInfo, user, pass string, hash []byte, err er
|
||||
Common.LogError(errlog)
|
||||
}
|
||||
|
||||
// Smb2Con 尝试SMB2连接并进行认证,检查共享访问权限
|
||||
func Smb2Con(info *Common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, flag2 bool) {
|
||||
// 建立TCP连接
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:445", info.Host),
|
||||
time.Duration(Common.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("连接失败: %v", err), false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 配置NTLM认证
|
||||
initiator := smb2.NTLMInitiator{
|
||||
User: user,
|
||||
Domain: Common.Domain,
|
||||
}
|
||||
|
||||
// 设置认证方式(哈希或密码)
|
||||
if len(hash) > 0 {
|
||||
initiator.Hash = hash
|
||||
} else {
|
||||
initiator.Password = pass
|
||||
}
|
||||
|
||||
// 创建SMB2会话
|
||||
d := &smb2.Dialer{
|
||||
Initiator: &initiator,
|
||||
}
|
||||
session, err := d.Dial(conn)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("SMB2会话建立失败: %v", err), false
|
||||
}
|
||||
defer session.Logoff()
|
||||
|
||||
// 获取共享列表
|
||||
shares, err := session.ListSharenames()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("获取共享列表失败: %v", err), false
|
||||
}
|
||||
|
||||
// 打印共享信息(如果未打印过)
|
||||
if !hasprint {
|
||||
logShareInfo(info, user, pass, hash, shares)
|
||||
flag2 = true
|
||||
}
|
||||
|
||||
// 尝试访问C$共享以验证管理员权限
|
||||
fs, err := session.Mount("C$")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("挂载C$失败: %v", err), flag2
|
||||
}
|
||||
defer fs.Umount()
|
||||
|
||||
// 尝试读取系统文件以验证权限
|
||||
path := `Windows\win.ini`
|
||||
f, err := fs.OpenFile(path, os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("访问系统文件失败: %v", err), flag2
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return true, nil, flag2
|
||||
}
|
||||
|
||||
// logShareInfo 记录SMB共享信息
|
||||
func logShareInfo(info *Common.HostInfo, user string, pass string, hash []byte, shares []string) {
|
||||
credential := pass
|
||||
|
368
Plugins/SMTP.go
368
Plugins/SMTP.go
@ -1,167 +1,278 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SmtpCredential 表示一个SMTP凭据
|
||||
type SmtpCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// SmtpScanResult 表示SMTP扫描结果
|
||||
type SmtpScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential SmtpCredential
|
||||
IsAnonymous bool
|
||||
}
|
||||
|
||||
// SmtpScan 执行 SMTP 服务扫描
|
||||
func SmtpScan(info *Common.HostInfo) (tmperr error) {
|
||||
func SmtpScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
Common.LogDebug("尝试匿名访问...")
|
||||
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 先测试匿名访问
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
flag, err := SmtpConn(info, "", "")
|
||||
if flag && err == nil {
|
||||
msg := fmt.Sprintf("SMTP服务 %s 允许匿名访问", target)
|
||||
Common.LogSuccess(msg)
|
||||
Common.LogDebug("尝试匿名访问...")
|
||||
anonymousResult := trySmtpCredential(ctx, info, SmtpCredential{"", ""}, Common.Timeout, Common.MaxRetries)
|
||||
|
||||
// 保存匿名访问结果
|
||||
result := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "smtp",
|
||||
"type": "anonymous-access",
|
||||
"anonymous": true,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(result)
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("smtp %s anonymous %v", target, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
if anonymousResult.Success {
|
||||
// 匿名访问成功
|
||||
saveSmtpResult(info, target, anonymousResult)
|
||||
return nil
|
||||
}
|
||||
|
||||
totalUsers := len(Common.Userdict["smtp"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []SmtpCredential
|
||||
for _, user := range Common.Userdict["smtp"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, SmtpCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["smtp"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentSmtpScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveSmtpResult(info, target, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("SMTP扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)+1)) // +1 是因为还尝试了匿名访问
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// concurrentSmtpScan 并发扫描SMTP服务
|
||||
func concurrentSmtpScan(ctx context.Context, info *Common.HostInfo, credentials []SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *SmtpScanResult, 1)
|
||||
workChan := make(chan SmtpCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := trySmtpCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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("SMTP并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// trySmtpCredential 尝试单个SMTP凭据
|
||||
func trySmtpCredential(ctx context.Context, info *Common.HostInfo, credential SmtpCredential, timeoutSeconds int64, maxRetries int) *SmtpScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &SmtpScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
|
||||
// 在协程中尝试连接
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
success, err := SmtpConn(info, credential.Username, credential.Password, timeoutSeconds)
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func(user, pass string) {
|
||||
flag, err := SmtpConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{flag, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
msg := fmt.Sprintf("SMTP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass)
|
||||
Common.LogSuccess(msg)
|
||||
|
||||
// 保存成功爆破结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "smtp",
|
||||
"type": "weak-password",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}{success, err}:
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("SMTP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
|
||||
target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
// 等待结果或超时
|
||||
var success bool
|
||||
var err error
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
success = result.success
|
||||
err = result.err
|
||||
case <-connCtx.Done():
|
||||
cancel()
|
||||
if ctx.Err() != nil {
|
||||
// 全局超时
|
||||
return &SmtpScanResult{
|
||||
Success: false,
|
||||
Error: ctx.Err(),
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
break
|
||||
// 单个连接超时
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
cancel() // 释放连接上下文
|
||||
|
||||
if success {
|
||||
isAnonymous := credential.Username == "" && credential.Password == ""
|
||||
return &SmtpScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
IsAnonymous: isAnonymous,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &SmtpScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// SmtpConn 尝试 SMTP 连接
|
||||
func SmtpConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
func SmtpConn(info *Common.HostInfo, user string, pass string, timeoutSeconds int64) (bool, error) {
|
||||
host, port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
timeout := time.Duration(timeoutSeconds) * time.Second
|
||||
addr := fmt.Sprintf("%s:%s", host, port)
|
||||
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
// 设置连接超时
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
conn, err := dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 设置读写超时
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
|
||||
client, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// 尝试认证
|
||||
if user != "" {
|
||||
auth := smtp.PlainAuth("", user, pass, host)
|
||||
err = client.Auth(auth)
|
||||
@ -170,6 +281,7 @@ func SmtpConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试发送邮件(测试权限)
|
||||
err = client.Mail("test@test.com")
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -177,3 +289,41 @@ func SmtpConn(info *Common.HostInfo, user string, pass string) (bool, error) {
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// saveSmtpResult 保存SMTP扫描结果
|
||||
func saveSmtpResult(info *Common.HostInfo, target string, result *SmtpScanResult) {
|
||||
var successMsg string
|
||||
var details map[string]interface{}
|
||||
|
||||
if result.IsAnonymous {
|
||||
successMsg = fmt.Sprintf("SMTP服务 %s 允许匿名访问", target)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "smtp",
|
||||
"type": "anonymous-access",
|
||||
"anonymous": true,
|
||||
}
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("SMTP服务 %s 爆破成功 用户名: %v 密码: %v",
|
||||
target, result.Credential.Username, result.Credential.Password)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "smtp",
|
||||
"type": "weak-password",
|
||||
"username": result.Credential.Username,
|
||||
"password": result.Credential.Password,
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
@ -2,145 +2,280 @@ package Plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TelnetCredential 表示一个Telnet凭据
|
||||
type TelnetCredential struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// TelnetScanResult 表示Telnet扫描结果
|
||||
type TelnetScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential TelnetCredential
|
||||
NoAuth bool
|
||||
}
|
||||
|
||||
// TelnetScan 执行Telnet服务扫描和密码爆破
|
||||
func TelnetScan(info *Common.HostInfo) (tmperr error) {
|
||||
func TelnetScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalUsers := len(Common.Userdict["telnet"])
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
|
||||
|
||||
tried := 0
|
||||
total := totalUsers * totalPass
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 遍历所有用户名密码组合
|
||||
// 构建凭据列表
|
||||
var credentials []TelnetCredential
|
||||
for _, user := range Common.Userdict["telnet"] {
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
credentials = append(credentials, TelnetCredential{
|
||||
Username: user,
|
||||
Password: actualPass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)",
|
||||
len(Common.Userdict["telnet"]), len(Common.Passwords), len(credentials)))
|
||||
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentTelnetScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveTelnetResult(info, target, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Telnet扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// concurrentTelnetScan 并发扫描Telnet服务
|
||||
func concurrentTelnetScan(ctx context.Context, info *Common.HostInfo, credentials []TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *TelnetScanResult, 1)
|
||||
workChan := make(chan TelnetCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryTelnetCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success || result.NoAuth {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据或无需认证,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
done := make(chan struct {
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
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 || result.NoAuth) {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("Telnet并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryTelnetCredential 尝试单个Telnet凭据
|
||||
func tryTelnetCredential(ctx context.Context, info *Common.HostInfo, credential TelnetCredential, timeoutSeconds int64, maxRetries int) *TelnetScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &TelnetScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retry+1, credential.Username, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建结果通道
|
||||
resultChan := make(chan struct {
|
||||
success bool
|
||||
noAuth bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 设置单个连接超时
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
go func() {
|
||||
defer cancel()
|
||||
noAuth, err := telnetConnWithContext(connCtx, info, credential.Username, credential.Password)
|
||||
select {
|
||||
case <-connCtx.Done():
|
||||
// 连接已超时或取消
|
||||
case resultChan <- struct {
|
||||
success bool
|
||||
noAuth bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
go func(user, pass string) {
|
||||
flag, err := telnetConn(info, user, pass)
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
noAuth bool
|
||||
err error
|
||||
}{err == nil, flag, err}:
|
||||
default:
|
||||
}
|
||||
}(user, pass)
|
||||
|
||||
var err error
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.noAuth {
|
||||
// 无需认证
|
||||
msg := fmt.Sprintf("Telnet服务 %s 无需认证", target)
|
||||
Common.LogSuccess(msg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "telnet",
|
||||
"type": "unauthorized-access",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
|
||||
} else if result.success {
|
||||
// 成功爆破
|
||||
msg := fmt.Sprintf("Telnet服务 %s 用户名:%v 密码:%v", target, user, pass)
|
||||
Common.LogSuccess(msg)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "telnet",
|
||||
"type": "weak-password",
|
||||
"username": user,
|
||||
"password": pass,
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
}{err == nil, noAuth, err}:
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("Telnet连接失败 %s 用户名:%v 密码:%v 错误:%v",
|
||||
target, user, pass, err)
|
||||
Common.LogError(errlog)
|
||||
// 等待结果或超时
|
||||
var success bool
|
||||
var noAuth bool
|
||||
var err error
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
success = result.success
|
||||
noAuth = result.noAuth
|
||||
err = result.err
|
||||
case <-connCtx.Done():
|
||||
if ctx.Err() != nil {
|
||||
// 全局超时
|
||||
return &TelnetScanResult{
|
||||
Success: false,
|
||||
Error: ctx.Err(),
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
break
|
||||
// 单个连接超时
|
||||
err = fmt.Errorf("连接超时")
|
||||
}
|
||||
|
||||
if noAuth {
|
||||
return &TelnetScanResult{
|
||||
Success: false,
|
||||
NoAuth: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
return &TelnetScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
|
||||
return tmperr
|
||||
return &TelnetScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// telnetConn 尝试建立Telnet连接并进行身份验证
|
||||
func telnetConn(info *Common.HostInfo, user, pass string) (flag bool, err error) {
|
||||
client := NewTelnet(info.Host, info.Ports)
|
||||
|
||||
if err = client.Connect(); err != nil {
|
||||
// telnetConnWithContext 带上下文的Telnet连接尝试
|
||||
func telnetConnWithContext(ctx context.Context, info *Common.HostInfo, user, pass string) (bool, error) {
|
||||
// 创建TCP连接(使用上下文控制)
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", info.Host, info.Ports))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
client := &TelnetClient{
|
||||
IPAddr: info.Host,
|
||||
Port: info.Ports,
|
||||
UserName: user,
|
||||
Password: pass,
|
||||
conn: conn,
|
||||
}
|
||||
|
||||
// 设置连接关闭
|
||||
defer client.Close()
|
||||
|
||||
client.UserName = user
|
||||
client.Password = pass
|
||||
// 检查上下文是否已取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// 初始化连接
|
||||
client.init()
|
||||
|
||||
client.ServerType = client.MakeServerType()
|
||||
|
||||
if client.ServerType == UnauthorizedAccess {
|
||||
@ -151,119 +286,42 @@ func telnetConn(info *Common.HostInfo, user, pass string) (flag bool, err error)
|
||||
return false, err
|
||||
}
|
||||
|
||||
const (
|
||||
// 写入操作后的延迟时间
|
||||
TIME_DELAY_AFTER_WRITE = 300 * time.Millisecond
|
||||
// saveTelnetResult 保存Telnet扫描结果
|
||||
func saveTelnetResult(info *Common.HostInfo, target string, result *TelnetScanResult) {
|
||||
var successMsg string
|
||||
var details map[string]interface{}
|
||||
|
||||
// Telnet基础控制字符
|
||||
IAC = byte(255) // 解释为命令(Interpret As Command)
|
||||
DONT = byte(254) // 请求对方停止执行某选项
|
||||
DO = byte(253) // 请求对方执行某选项
|
||||
WONT = byte(252) // 拒绝执行某选项
|
||||
WILL = byte(251) // 同意执行某选项
|
||||
if result.NoAuth {
|
||||
successMsg = fmt.Sprintf("Telnet服务 %s 无需认证", target)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "telnet",
|
||||
"type": "unauthorized-access",
|
||||
}
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("Telnet服务 %s 用户名:%v 密码:%v",
|
||||
target, result.Credential.Username, result.Credential.Password)
|
||||
details = map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "telnet",
|
||||
"type": "weak-password",
|
||||
"username": result.Credential.Username,
|
||||
"password": result.Credential.Password,
|
||||
}
|
||||
}
|
||||
|
||||
// 子协商相关控制字符
|
||||
SB = byte(250) // 子协商开始(Subnegotiation Begin)
|
||||
SE = byte(240) // 子协商结束(Subnegotiation End)
|
||||
Common.LogSuccess(successMsg)
|
||||
|
||||
// 特殊功能字符
|
||||
NULL = byte(0) // 空字符
|
||||
EOF = byte(236) // 文档结束
|
||||
SUSP = byte(237) // 暂停进程
|
||||
ABORT = byte(238) // 停止进程
|
||||
REOR = byte(239) // 记录结束
|
||||
|
||||
// 控制操作字符
|
||||
NOP = byte(241) // 无操作
|
||||
DM = byte(242) // 数据标记
|
||||
BRK = byte(243) // 中断
|
||||
IP = byte(244) // 中断进程
|
||||
AO = byte(245) // 终止输出
|
||||
AYT = byte(246) // 在线确认
|
||||
EC = byte(247) // 擦除字符
|
||||
EL = byte(248) // 擦除行
|
||||
GA = byte(249) // 继续进行
|
||||
|
||||
// Telnet协议选项代码 (来自arpa/telnet.h)
|
||||
BINARY = byte(0) // 8位数据通道
|
||||
ECHO = byte(1) // 回显
|
||||
RCP = byte(2) // 准备重新连接
|
||||
SGA = byte(3) // 禁止继续
|
||||
NAMS = byte(4) // 近似消息大小
|
||||
STATUS = byte(5) // 状态查询
|
||||
TM = byte(6) // 时间标记
|
||||
RCTE = byte(7) // 远程控制传输和回显
|
||||
|
||||
// 输出协商选项
|
||||
NAOL = byte(8) // 输出行宽度协商
|
||||
NAOP = byte(9) // 输出页面大小协商
|
||||
NAOCRD = byte(10) // 回车处理协商
|
||||
NAOHTS = byte(11) // 水平制表符停止协商
|
||||
NAOHTD = byte(12) // 水平制表符处理协商
|
||||
NAOFFD = byte(13) // 换页符处理协商
|
||||
NAOVTS = byte(14) // 垂直制表符停止协商
|
||||
NAOVTD = byte(15) // 垂直制表符处理协商
|
||||
NAOLFD = byte(16) // 换行符处理协商
|
||||
|
||||
// 扩展功能选项
|
||||
XASCII = byte(17) // 扩展ASCII字符集
|
||||
LOGOUT = byte(18) // 强制登出
|
||||
BM = byte(19) // 字节宏
|
||||
DET = byte(20) // 数据输入终端
|
||||
SUPDUP = byte(21) // SUPDUP协议
|
||||
SUPDUPOUTPUT = byte(22) // SUPDUP输出
|
||||
SNDLOC = byte(23) // 发送位置
|
||||
|
||||
// 终端相关选项
|
||||
TTYPE = byte(24) // 终端类型
|
||||
EOR = byte(25) // 记录结束
|
||||
TUID = byte(26) // TACACS用户识别
|
||||
OUTMRK = byte(27) // 输出标记
|
||||
TTYLOC = byte(28) // 终端位置编号
|
||||
VT3270REGIME = byte(29) // 3270体制
|
||||
|
||||
// 通信控制选项
|
||||
X3PAD = byte(30) // X.3 PAD
|
||||
NAWS = byte(31) // 窗口大小
|
||||
TSPEED = byte(32) // 终端速度
|
||||
LFLOW = byte(33) // 远程流控制
|
||||
LINEMODE = byte(34) // 行模式选项
|
||||
|
||||
// 环境与认证选项
|
||||
XDISPLOC = byte(35) // X显示位置
|
||||
OLD_ENVIRON = byte(36) // 旧环境变量
|
||||
AUTHENTICATION = byte(37) // 认证
|
||||
ENCRYPT = byte(38) // 加密选项
|
||||
NEW_ENVIRON = byte(39) // 新环境变量
|
||||
|
||||
// IANA分配的额外选项
|
||||
// http://www.iana.org/assignments/telnet-options
|
||||
TN3270E = byte(40) // TN3270E
|
||||
XAUTH = byte(41) // XAUTH
|
||||
CHARSET = byte(42) // 字符集
|
||||
RSP = byte(43) // 远程串行端口
|
||||
COM_PORT_OPTION = byte(44) // COM端口控制
|
||||
SUPPRESS_LOCAL_ECHO = byte(45) // 禁止本地回显
|
||||
TLS = byte(46) // 启动TLS
|
||||
KERMIT = byte(47) // KERMIT协议
|
||||
SEND_URL = byte(48) // 发送URL
|
||||
FORWARD_X = byte(49) // X转发
|
||||
|
||||
// 特殊用途选项
|
||||
PRAGMA_LOGON = byte(138) // PRAGMA登录
|
||||
SSPI_LOGON = byte(139) // SSPI登录
|
||||
PRAGMA_HEARTBEAT = byte(140) // PRAGMA心跳
|
||||
EXOPL = byte(255) // 扩展选项列表
|
||||
NOOPT = byte(0) // 无选项
|
||||
)
|
||||
|
||||
// 服务器类型常量定义
|
||||
const (
|
||||
Closed = iota // 连接关闭
|
||||
UnauthorizedAccess // 无需认证
|
||||
OnlyPassword // 仅需密码
|
||||
UsernameAndPassword // 需要用户名和密码
|
||||
)
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: details,
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
||||
// TelnetClient Telnet客户端结构体
|
||||
type TelnetClient struct {
|
||||
@ -276,28 +334,8 @@ type TelnetClient struct {
|
||||
ServerType int // 服务器类型
|
||||
}
|
||||
|
||||
// NewTelnet 创建新的Telnet客户端实例
|
||||
func NewTelnet(addr, port string) *TelnetClient {
|
||||
return &TelnetClient{
|
||||
IPAddr: addr,
|
||||
Port: port,
|
||||
UserName: "",
|
||||
Password: "",
|
||||
conn: nil,
|
||||
LastResponse: "",
|
||||
ServerType: Closed,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect 建立Telnet连接
|
||||
func (c *TelnetClient) Connect() error {
|
||||
// 建立TCP连接,超时时间5秒
|
||||
conn, err := net.DialTimeout("tcp", c.Netloc(), 5*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = conn
|
||||
|
||||
// init 初始化Telnet连接
|
||||
func (c *TelnetClient) init() {
|
||||
// 启动后台goroutine处理服务器响应
|
||||
go func() {
|
||||
for {
|
||||
@ -328,8 +366,7 @@ func (c *TelnetClient) Connect() error {
|
||||
}()
|
||||
|
||||
// 等待连接初始化完成
|
||||
time.Sleep(time.Second * 3)
|
||||
return nil
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
|
||||
// WriteContext 写入数据到Telnet连接
|
||||
@ -362,7 +399,9 @@ func (c *TelnetClient) Netloc() string {
|
||||
|
||||
// Close 关闭Telnet连接
|
||||
func (c *TelnetClient) Close() {
|
||||
c.conn.Close()
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// SerializationResponse 解析Telnet响应数据
|
||||
@ -398,9 +437,11 @@ func (c *TelnetClient) SerializationResponse(responseBuf []byte) (displayBuf []b
|
||||
if ch == SB {
|
||||
displayBuf = append(displayBuf, responseBuf[:index]...)
|
||||
seIndex := bytes.IndexByte(responseBuf, SE)
|
||||
commandList = append(commandList, responseBuf[index:seIndex])
|
||||
responseBuf = responseBuf[seIndex+1:]
|
||||
continue
|
||||
if seIndex != -1 && seIndex > index {
|
||||
commandList = append(commandList, responseBuf[index:seIndex+1])
|
||||
responseBuf = responseBuf[seIndex+1:]
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
@ -465,6 +506,8 @@ func (c *TelnetClient) MakeReply(command []byte) []byte {
|
||||
// read 从Telnet连接读取数据
|
||||
func (c *TelnetClient) read() ([]byte, error) {
|
||||
var buf [2048]byte
|
||||
// 设置读取超时为2秒
|
||||
_ = c.conn.SetReadDeadline(time.Now().Add(time.Second * 2))
|
||||
n, err := c.conn.Read(buf[0:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -481,6 +524,8 @@ func (c *TelnetClient) write(buf []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 写入后短暂延迟,让服务器有时间处理
|
||||
time.Sleep(TIME_DELAY_AFTER_WRITE)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -503,7 +548,17 @@ func (c *TelnetClient) Login() error {
|
||||
// MakeServerType 通过分析服务器响应判断服务器类型
|
||||
func (c *TelnetClient) MakeServerType() int {
|
||||
responseString := c.ReadContext()
|
||||
|
||||
// 空响应情况
|
||||
if responseString == "" {
|
||||
return Closed
|
||||
}
|
||||
|
||||
response := strings.Split(responseString, "\n")
|
||||
if len(response) == 0 {
|
||||
return Closed
|
||||
}
|
||||
|
||||
lastLine := strings.ToLower(response[len(response)-1])
|
||||
|
||||
// 检查是否需要用户名和密码
|
||||
@ -556,7 +611,7 @@ func (c *TelnetClient) loginForOnlyPassword() error {
|
||||
|
||||
// 发送密码并等待响应
|
||||
c.WriteContext(c.Password)
|
||||
time.Sleep(time.Second * 3)
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
// 验证登录结果
|
||||
responseString := c.ReadContext()
|
||||
@ -574,12 +629,12 @@ func (c *TelnetClient) loginForOnlyPassword() error {
|
||||
func (c *TelnetClient) loginForUsernameAndPassword() error {
|
||||
// 发送用户名
|
||||
c.WriteContext(c.UserName)
|
||||
time.Sleep(time.Second * 3)
|
||||
time.Sleep(time.Second * 2)
|
||||
c.Clear()
|
||||
|
||||
// 发送密码
|
||||
c.WriteContext(c.Password)
|
||||
time.Sleep(time.Second * 5)
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
// 验证登录结果
|
||||
responseString := c.ReadContext()
|
||||
@ -640,12 +695,21 @@ func (c *TelnetClient) isLoginFailed(responseString string) bool {
|
||||
|
||||
// isLoginSucceed 检查是否登录成功
|
||||
func (c *TelnetClient) isLoginSucceed(responseString string) bool {
|
||||
// 空响应视为失败
|
||||
if responseString == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取最后一行响应
|
||||
lines := strings.Split(responseString, "\n")
|
||||
if len(lines) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
lastLine := lines[len(lines)-1]
|
||||
|
||||
// 检查命令提示符
|
||||
if regexp.MustCompile("^[#$].*").MatchString(lastLine) ||
|
||||
if regexp.MustCompile("^[#$>].*").MatchString(lastLine) ||
|
||||
regexp.MustCompile("^<[a-zA-Z0-9_]+>.*").MatchString(lastLine) {
|
||||
return true
|
||||
}
|
||||
@ -658,7 +722,7 @@ func (c *TelnetClient) isLoginSucceed(responseString string) bool {
|
||||
// 发送测试命令验证
|
||||
c.Clear()
|
||||
c.WriteContext("?")
|
||||
time.Sleep(time.Second * 3)
|
||||
time.Sleep(time.Second * 2)
|
||||
responseString = c.ReadContext()
|
||||
|
||||
// 检查响应长度
|
||||
@ -668,3 +732,38 @@ func (c *TelnetClient) isLoginSucceed(responseString string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Telnet协议常量定义
|
||||
const (
|
||||
// 写入操作后的延迟时间
|
||||
TIME_DELAY_AFTER_WRITE = 300 * time.Millisecond
|
||||
|
||||
// Telnet基础控制字符
|
||||
IAC = byte(255) // 解释为命令(Interpret As Command)
|
||||
DONT = byte(254) // 请求对方停止执行某选项
|
||||
DO = byte(253) // 请求对方执行某选项
|
||||
WONT = byte(252) // 拒绝执行某选项
|
||||
WILL = byte(251) // 同意执行某选项
|
||||
|
||||
// 子协商相关控制字符
|
||||
SB = byte(250) // 子协商开始(Subnegotiation Begin)
|
||||
SE = byte(240) // 子协商结束(Subnegotiation End)
|
||||
|
||||
// 特殊功能字符
|
||||
NULL = byte(0) // 空字符
|
||||
EOF = byte(236) // 文档结束
|
||||
SUSP = byte(237) // 暂停进程
|
||||
ABORT = byte(238) // 停止进程
|
||||
REOR = byte(239) // 记录结束
|
||||
|
||||
// Telnet选项代码
|
||||
BINARY = byte(0) // 8位数据通道
|
||||
ECHO = byte(1) // 回显
|
||||
SGA = byte(3) // 禁止继续
|
||||
|
||||
// 服务器类型常量定义
|
||||
Closed = iota // 连接关闭
|
||||
UnauthorizedAccess // 无需认证
|
||||
OnlyPassword // 仅需密码
|
||||
UsernameAndPassword // 需要用户名和密码
|
||||
)
|
||||
|
310
Plugins/VNC.go
310
Plugins/VNC.go
@ -1,132 +1,274 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/mitchellh/go-vnc"
|
||||
"github.com/shadow1ng/fscan/Common"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func VncScan(info *Common.HostInfo) (tmperr error) {
|
||||
// VncCredential 表示VNC凭据
|
||||
type VncCredential struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
// VncScanResult 表示VNC扫描结果
|
||||
type VncScanResult struct {
|
||||
Success bool
|
||||
Error error
|
||||
Credential VncCredential
|
||||
}
|
||||
|
||||
func VncScan(info *Common.HostInfo) error {
|
||||
if Common.DisableBrute {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
maxRetries := Common.MaxRetries
|
||||
modename := "vnc"
|
||||
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
|
||||
totalPass := len(Common.Passwords)
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", totalPass))
|
||||
|
||||
tried := 0
|
||||
// 设置全局超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 遍历所有密码
|
||||
// 构建密码列表
|
||||
var credentials []VncCredential
|
||||
for _, pass := range Common.Passwords {
|
||||
tried++
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", tried, totalPass, pass))
|
||||
credentials = append(credentials, VncCredential{Password: pass})
|
||||
}
|
||||
|
||||
// 重试循环
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
if retryCount > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retryCount+1, pass))
|
||||
}
|
||||
Common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", len(credentials)))
|
||||
|
||||
done := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
// 使用工作池并发扫描
|
||||
result := concurrentVncScan(ctx, info, credentials, Common.Timeout, Common.MaxRetries)
|
||||
if result != nil {
|
||||
// 记录成功结果
|
||||
saveVncResult(info, target, result.Credential)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func(pass string) {
|
||||
success, err := VncConn(info, pass)
|
||||
// 检查是否因为全局超时而退出
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
Common.LogDebug("VNC扫描全局超时")
|
||||
return fmt.Errorf("全局超时")
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", len(credentials)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// concurrentVncScan 并发扫描VNC服务
|
||||
func concurrentVncScan(ctx context.Context, info *Common.HostInfo, credentials []VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult {
|
||||
// 使用ModuleThreadNum控制并发数
|
||||
maxConcurrent := Common.ModuleThreadNum
|
||||
if maxConcurrent <= 0 {
|
||||
maxConcurrent = 10 // 默认值
|
||||
}
|
||||
if maxConcurrent > len(credentials) {
|
||||
maxConcurrent = len(credentials)
|
||||
}
|
||||
|
||||
// 创建工作池
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan *VncScanResult, 1)
|
||||
workChan := make(chan VncCredential, maxConcurrent)
|
||||
scanCtx, scanCancel := context.WithCancel(ctx)
|
||||
defer scanCancel()
|
||||
|
||||
// 启动工作协程
|
||||
for i := 0; i < maxConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for credential := range workChan {
|
||||
select {
|
||||
case done <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{success, err}:
|
||||
case <-scanCtx.Done():
|
||||
return
|
||||
default:
|
||||
result := tryVncCredential(scanCtx, info, credential, timeoutSeconds, maxRetries)
|
||||
if result.Success {
|
||||
select {
|
||||
case resultChan <- result:
|
||||
scanCancel() // 找到有效凭据,取消其他工作
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}(pass)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var err error
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i, cred := range credentials {
|
||||
select {
|
||||
case result := <-done:
|
||||
err = result.err
|
||||
if result.success && err == nil {
|
||||
// 连接成功
|
||||
successLog := fmt.Sprintf("%s://%s 密码: %v", modename, target, pass)
|
||||
Common.LogSuccess(successLog)
|
||||
case <-scanCtx.Done():
|
||||
break
|
||||
default:
|
||||
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", i+1, len(credentials), cred.Password))
|
||||
workChan <- cred
|
||||
}
|
||||
}
|
||||
close(workChan)
|
||||
}()
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "vnc",
|
||||
"password": pass,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
Common.SaveResult(vulnResult)
|
||||
return nil
|
||||
}
|
||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
||||
err = fmt.Errorf("连接超时")
|
||||
// 等待结果或完成
|
||||
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("VNC并发扫描全局超时")
|
||||
scanCancel() // 确保取消所有未完成工作
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// tryVncCredential 尝试单个VNC凭据
|
||||
func tryVncCredential(ctx context.Context, info *Common.HostInfo, credential VncCredential, timeoutSeconds int64, maxRetries int) *VncScanResult {
|
||||
var lastErr error
|
||||
|
||||
for retry := 0; retry < maxRetries; retry++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &VncScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("全局超时"),
|
||||
Credential: credential,
|
||||
}
|
||||
default:
|
||||
if retry > 0 {
|
||||
Common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retry+1, credential.Password))
|
||||
time.Sleep(500 * time.Millisecond) // 重试前等待
|
||||
}
|
||||
|
||||
// 创建连接超时上下文
|
||||
connCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
success, err := VncConn(connCtx, info, credential.Password)
|
||||
cancel()
|
||||
|
||||
if success {
|
||||
return &VncScanResult{
|
||||
Success: true,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("%s://%s 尝试密码: %v 错误: %v",
|
||||
modename, target, pass, err)
|
||||
Common.LogError(errlog)
|
||||
|
||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
||||
if retryCount == maxRetries-1 {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
// 检查是否需要重试
|
||||
if retryErr := Common.CheckErrs(err); retryErr == nil {
|
||||
break // 不需要重试的错误
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", tried))
|
||||
return tmperr
|
||||
return &VncScanResult{
|
||||
Success: false,
|
||||
Error: lastErr,
|
||||
Credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
// VncConn 尝试建立VNC连接
|
||||
func VncConn(info *Common.HostInfo, pass string) (flag bool, err error) {
|
||||
flag = false
|
||||
func VncConn(ctx context.Context, info *Common.HostInfo, pass string) (bool, error) {
|
||||
Host, Port := info.Host, info.Ports
|
||||
timeout := time.Duration(Common.Timeout) * time.Second
|
||||
|
||||
// 建立TCP连接
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port),
|
||||
time.Duration(Common.Timeout)*time.Second)
|
||||
// 使用带上下文的TCP连接
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", Host, Port))
|
||||
if err != nil {
|
||||
return
|
||||
return false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 配置VNC客户端
|
||||
config := &vnc.ClientConfig{
|
||||
Auth: []vnc.ClientAuth{
|
||||
&vnc.PasswordAuth{
|
||||
Password: pass,
|
||||
// 设置读写超时
|
||||
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 创建完成通道
|
||||
doneChan := make(chan struct {
|
||||
success bool
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// 在协程中处理VNC认证
|
||||
go func() {
|
||||
// 配置VNC客户端
|
||||
config := &vnc.ClientConfig{
|
||||
Auth: []vnc.ClientAuth{
|
||||
&vnc.PasswordAuth{
|
||||
Password: pass,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 尝试VNC认证
|
||||
client, err := vnc.Client(conn, config)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case doneChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{false, err}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 认证成功
|
||||
defer client.Close()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case doneChan <- struct {
|
||||
success bool
|
||||
err error
|
||||
}{true, nil}:
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待认证结果或上下文取消
|
||||
select {
|
||||
case result := <-doneChan:
|
||||
return result.success, result.err
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// saveVncResult 保存VNC扫描结果
|
||||
func saveVncResult(info *Common.HostInfo, target string, credential VncCredential) {
|
||||
successLog := fmt.Sprintf("vnc://%s 密码: %v", target, credential.Password)
|
||||
Common.LogSuccess(successLog)
|
||||
|
||||
// 保存结果
|
||||
vulnResult := &Common.ScanResult{
|
||||
Time: time.Now(),
|
||||
Type: Common.VULN,
|
||||
Target: info.Host,
|
||||
Status: "vulnerable",
|
||||
Details: map[string]interface{}{
|
||||
"port": info.Ports,
|
||||
"service": "vnc",
|
||||
"password": credential.Password,
|
||||
"type": "weak-password",
|
||||
},
|
||||
}
|
||||
|
||||
// 尝试VNC认证
|
||||
client, err := vnc.Client(conn, config)
|
||||
if err == nil {
|
||||
defer client.Close()
|
||||
flag = true
|
||||
}
|
||||
|
||||
return
|
||||
Common.SaveResult(vulnResult)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<plugins>
|
||||
<simpleAuthenticationPlugin>
|
||||
<users>
|
||||
<authenticationUser username="admin" password="123456" groups="admins,publishers,consumers"/>
|
||||
<authenticationUser username="admin" password="Aa123456789" groups="admins,publishers,consumers"/>
|
||||
<authenticationUser username="test" password="test123" groups="publishers,consumers"/>
|
||||
<authenticationUser username="root" password="root123" groups="admins"/>
|
||||
<authenticationUser username="system" password="admin123" groups="admins"/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
admin=123456
|
||||
admin=Aa123456789
|
||||
test=test123
|
||||
root=root123
|
||||
system=admin123
|
@ -1,28 +1,22 @@
|
||||
# docker-compose.yml
|
||||
version: '3'
|
||||
services:
|
||||
zookeeper:
|
||||
image: bitnami/zookeeper:latest
|
||||
environment:
|
||||
- ALLOW_ANONYMOUS_LOGIN=yes
|
||||
ports:
|
||||
- "2181:2181"
|
||||
|
||||
kafka:
|
||||
image: bitnami/kafka:latest
|
||||
ports:
|
||||
- "9092:9092"
|
||||
environment:
|
||||
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
|
||||
- KAFKA_CFG_LISTENERS=SASL_PLAINTEXT://:9092
|
||||
- KAFKA_CFG_NODE_ID=1
|
||||
- KAFKA_CFG_PROCESS_ROLES=broker,controller
|
||||
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093
|
||||
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
|
||||
- KAFKA_CFG_LISTENERS=CONTROLLER://:9093,SASL_PLAINTEXT://:9092
|
||||
- KAFKA_CFG_ADVERTISED_LISTENERS=SASL_PLAINTEXT://localhost:9092
|
||||
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=SASL_PLAINTEXT:SASL_PLAINTEXT
|
||||
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT
|
||||
- KAFKA_CFG_SASL_ENABLED_MECHANISMS=PLAIN
|
||||
- KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN
|
||||
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=SASL_PLAINTEXT
|
||||
- KAFKA_OPTS=-Djava.security.auth.login.config=/opt/bitnami/kafka/config/kafka_jaas.conf
|
||||
- ALLOW_PLAINTEXT_LISTENER=yes
|
||||
volumes:
|
||||
- ./kafka_jaas.conf:/opt/bitnami/kafka/config/kafka_jaas.conf
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- ./kafka_jaas.conf:/opt/bitnami/kafka/config/kafka_jaas.conf
|
@ -5,7 +5,7 @@ ENV LDAP_ORGANISATION="Example Inc"
|
||||
ENV LDAP_DOMAIN="example.com"
|
||||
ENV LDAP_BASE_DN="dc=example,dc=com"
|
||||
# 设置一个弱密码
|
||||
ENV LDAP_ADMIN_PASSWORD="123456"
|
||||
ENV LDAP_ADMIN_PASSWORD="Aa123456789"
|
||||
# 允许匿名访问
|
||||
ENV LDAP_READONLY_USER="true"
|
||||
ENV LDAP_READONLY_USER_USERNAME="readonly"
|
||||
|
@ -1,16 +1,13 @@
|
||||
# 使用Oracle官方容器镜像
|
||||
FROM container-registry.oracle.com/database/express:latest
|
||||
FROM container-registry.oracle.com/database/express:21.3.0-xe
|
||||
|
||||
# 设置环境变量
|
||||
ENV ORACLE_PWD=123456
|
||||
ENV ORACLE_SID=XE
|
||||
ENV ORACLE_PDB=XEPDB1
|
||||
ENV ORACLE_CHARACTERSET=AL32UTF8
|
||||
|
||||
# 开放1521端口
|
||||
EXPOSE 1521
|
||||
EXPOSE 1521 5500
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=3s \
|
||||
CMD sqlplus -L sys/123456@//localhost:1521/XE as sysdba << EOF
|
||||
exit;
|
||||
EOF
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 \
|
||||
CMD nc -z localhost 1521 || exit 1
|
@ -1,15 +1,2 @@
|
||||
docker pull cassandra:3.11
|
||||
|
||||
docker run -d --name cassandra-test \
|
||||
-e CASSANDRA_AUTHENTICATOR=AllowAllAuthenticator \
|
||||
-p 9042:9042 \
|
||||
-p 9160:9160 \
|
||||
cassandra:3.11
|
||||
|
||||
docker run -d --name cassandra-test \
|
||||
-e CASSANDRA_AUTHENTICATOR=PasswordAuthenticator \
|
||||
-e CASSANDRA_PASSWORD=123456 \
|
||||
-e CASSANDRA_USER=admin \
|
||||
-p 9042:9042 \
|
||||
-p 9160:9160 \
|
||||
cassandra:3.11
|
||||
docker build -t rsync-test .
|
||||
docker run -d --name rsync-server -p 873:873 rsync-test
|
@ -0,0 +1,2 @@
|
||||
docker build -t telnet-test .
|
||||
docker run -d -p 23:23 --name telnet-server telnet-test
|
Loading…
Reference in New Issue
Block a user