mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
feat: 实现-nobr/-ne参数和服务识别功能
新增功能: - 添加-ne参数禁用利用攻击,实现弱密码检测和利用攻击的分离控制 - 实现-nobr模式下所有插件的服务识别功能,单数据包获取最大信息 - 修复端口插件匹配逻辑,只调用适配端口的插件提升扫描效率 插件改造: - MySQL: 通过握手包识别获取版本信息 - Redis: INFO命令识别版本,优先检测未授权访问 - SSH: Banner识别获取协议和服务器版本 - ActiveMQ: STOMP协议识别获取版本和认证状态 技术改进: - 新增端口匹配算法确保精准插件调用 - 完善i18n国际化支持所有新功能 - 统一服务识别接口设计便于扩展
This commit is contained in:
parent
ecc79aa9b8
commit
51735c4e25
@ -54,6 +54,7 @@ var (
|
|||||||
RedisWriteFile string
|
RedisWriteFile string
|
||||||
|
|
||||||
DisableBrute bool
|
DisableBrute bool
|
||||||
|
DisableExploit bool
|
||||||
MaxRetries int
|
MaxRetries int
|
||||||
|
|
||||||
DisableSave bool
|
DisableSave bool
|
||||||
@ -198,6 +199,7 @@ func Flag(Info *HostInfo) {
|
|||||||
// 暴力破解控制参数
|
// 暴力破解控制参数
|
||||||
// ═════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════
|
||||||
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
|
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
|
||||||
|
flag.BoolVar(&DisableExploit, "ne", false, i18n.GetText("flag_disable_exploit"))
|
||||||
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
|
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
|
||||||
|
|
||||||
// ═════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════
|
||||||
|
@ -194,6 +194,10 @@ var FlagMessages = map[string]map[string]string{
|
|||||||
LangZH: "禁用暴力破解",
|
LangZH: "禁用暴力破解",
|
||||||
LangEN: "Disable brute force",
|
LangEN: "Disable brute force",
|
||||||
},
|
},
|
||||||
|
"flag_disable_exploit": {
|
||||||
|
LangZH: "禁用利用攻击",
|
||||||
|
LangEN: "Disable exploit attacks",
|
||||||
|
},
|
||||||
"flag_max_retries": {
|
"flag_max_retries": {
|
||||||
LangZH: "最大重试次数",
|
LangZH: "最大重试次数",
|
||||||
LangEN: "Maximum retries",
|
LangEN: "Maximum retries",
|
||||||
|
@ -224,6 +224,10 @@ var PluginMessages = map[string]map[string]string{
|
|||||||
LangZH: "MySQL弱密码扫描成功: %s [%s:%s]",
|
LangZH: "MySQL弱密码扫描成功: %s [%s:%s]",
|
||||||
LangEN: "MySQL weak password scan successful: %s [%s:%s]",
|
LangEN: "MySQL weak password scan successful: %s [%s:%s]",
|
||||||
},
|
},
|
||||||
|
"mysql_service_identified": {
|
||||||
|
LangZH: "MySQL服务识别成功: %s - %s",
|
||||||
|
LangEN: "MySQL service identified: %s - %s",
|
||||||
|
},
|
||||||
"mysql_connection_failed": {
|
"mysql_connection_failed": {
|
||||||
LangZH: "MySQL连接失败: %v",
|
LangZH: "MySQL连接失败: %v",
|
||||||
LangEN: "MySQL connection failed: %v",
|
LangEN: "MySQL connection failed: %v",
|
||||||
@ -262,6 +266,10 @@ var PluginMessages = map[string]map[string]string{
|
|||||||
LangZH: "Redis弱密码扫描成功: %s [%s]",
|
LangZH: "Redis弱密码扫描成功: %s [%s]",
|
||||||
LangEN: "Redis weak password scan successful: %s [%s]",
|
LangEN: "Redis weak password scan successful: %s [%s]",
|
||||||
},
|
},
|
||||||
|
"redis_service_identified": {
|
||||||
|
LangZH: "Redis服务识别成功: %s - %s",
|
||||||
|
LangEN: "Redis service identified: %s - %s",
|
||||||
|
},
|
||||||
"redis_connection_failed": {
|
"redis_connection_failed": {
|
||||||
LangZH: "Redis连接失败: %v",
|
LangZH: "Redis连接失败: %v",
|
||||||
LangEN: "Redis connection failed: %v",
|
LangEN: "Redis connection failed: %v",
|
||||||
@ -300,6 +308,10 @@ var PluginMessages = map[string]map[string]string{
|
|||||||
LangZH: "SSH密码认证成功: %s [%s:%s]",
|
LangZH: "SSH密码认证成功: %s [%s:%s]",
|
||||||
LangEN: "SSH password authentication successful: %s [%s:%s]",
|
LangEN: "SSH password authentication successful: %s [%s:%s]",
|
||||||
},
|
},
|
||||||
|
"ssh_service_identified": {
|
||||||
|
LangZH: "SSH服务识别成功: %s - %s",
|
||||||
|
LangEN: "SSH service identified: %s - %s",
|
||||||
|
},
|
||||||
"ssh_connection_failed": {
|
"ssh_connection_failed": {
|
||||||
LangZH: "SSH连接失败: %v",
|
LangZH: "SSH连接失败: %v",
|
||||||
LangEN: "SSH connection failed: %v",
|
LangEN: "SSH connection failed: %v",
|
||||||
@ -422,6 +434,10 @@ var PluginMessages = map[string]map[string]string{
|
|||||||
LangZH: "ActiveMQ弱密码扫描成功(STOMP): %s [%s:%s]",
|
LangZH: "ActiveMQ弱密码扫描成功(STOMP): %s [%s:%s]",
|
||||||
LangEN: "ActiveMQ weak password scan successful(STOMP): %s [%s:%s]",
|
LangEN: "ActiveMQ weak password scan successful(STOMP): %s [%s:%s]",
|
||||||
},
|
},
|
||||||
|
"activemq_service_identified": {
|
||||||
|
LangZH: "ActiveMQ服务识别成功: %s (%s) - %s",
|
||||||
|
LangEN: "ActiveMQ service identified: %s (%s) - %s",
|
||||||
|
},
|
||||||
"activemq_stomp_auth_success": {
|
"activemq_stomp_auth_success": {
|
||||||
LangZH: "ActiveMQ STOMP认证成功: %s@%s:%d",
|
LangZH: "ActiveMQ STOMP认证成功: %s@%s:%d",
|
||||||
LangEN: "ActiveMQ STOMP authentication successful: %s@%s:%d",
|
LangEN: "ActiveMQ STOMP authentication successful: %s@%s:%d",
|
||||||
|
@ -109,7 +109,11 @@ func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostIn
|
|||||||
for _, pluginName := range allPlugins {
|
for _, pluginName := range allPlugins {
|
||||||
// 首先检查新插件架构
|
// 首先检查新插件架构
|
||||||
if factory := base.GlobalPluginRegistry.GetFactory(pluginName); factory != nil {
|
if factory := base.GlobalPluginRegistry.GetFactory(pluginName); factory != nil {
|
||||||
servicePlugins = append(servicePlugins, pluginName)
|
// 获取插件元数据检查端口匹配
|
||||||
|
metadata := factory.GetMetadata()
|
||||||
|
if s.isNewPluginApplicableToAnyPort(metadata, portSet, isCustomMode) {
|
||||||
|
servicePlugins = append(servicePlugins, pluginName)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,3 +163,27 @@ func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlug
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用
|
||||||
|
func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool {
|
||||||
|
// 自定义模式下运行所有明确指定的插件
|
||||||
|
if isCustomMode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无端口限制的插件适用于所有端口
|
||||||
|
if len(metadata.Ports) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有端口限制的插件:检查是否匹配任何目标端口
|
||||||
|
for port := range portSet {
|
||||||
|
for _, pluginPort := range metadata.Ports {
|
||||||
|
if pluginPort == port {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -3,6 +3,9 @@ package activemq
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
@ -61,6 +64,13 @@ func NewActiveMQPlugin() *ActiveMQPlugin {
|
|||||||
// Scan 执行ActiveMQ服务的完整安全扫描
|
// Scan 执行ActiveMQ服务的完整安全扫描
|
||||||
// 重写基础扫描方法,集成弱密码检测和自动利用功能
|
// 重写基础扫描方法,集成弱密码检测和自动利用功能
|
||||||
func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 如果禁用暴力破解,则进行基础服务识别
|
||||||
|
if common.DisableBrute {
|
||||||
|
return p.performServiceIdentification(ctx, info)
|
||||||
|
}
|
||||||
|
|
||||||
// 调用基础服务插件进行弱密码扫描
|
// 调用基础服务插件进行弱密码扫描
|
||||||
result, err := p.ServicePlugin.Scan(ctx, info)
|
result, err := p.ServicePlugin.Scan(ctx, info)
|
||||||
if err != nil || !result.Success {
|
if err != nil || !result.Success {
|
||||||
@ -68,14 +78,13 @@ func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录成功的弱密码发现(使用i18n,根据端口显示不同协议)
|
// 记录成功的弱密码发现(使用i18n,根据端口显示不同协议)
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
||||||
cred := result.Credentials[0]
|
cred := result.Credentials[0]
|
||||||
|
|
||||||
// 专注于STOMP协议的成功消息
|
// 专注于STOMP协议的成功消息
|
||||||
common.LogSuccess(i18n.GetText("activemq_stomp_scan_success", target, cred.Username, cred.Password))
|
common.LogSuccess(i18n.GetText("activemq_stomp_scan_success", target, cred.Username, cred.Password))
|
||||||
|
|
||||||
// 自动利用功能(可通过-nobr参数禁用)
|
// 自动利用功能(可通过-ne参数禁用)
|
||||||
if result.Success && len(result.Credentials) > 0 && !common.DisableBrute {
|
if result.Success && len(result.Credentials) > 0 && !common.DisableExploit {
|
||||||
// 同步执行利用攻击,确保显示结果
|
// 同步执行利用攻击,确保显示结果
|
||||||
p.autoExploit(context.Background(), info, result.Credentials[0])
|
p.autoExploit(context.Background(), info, result.Credentials[0])
|
||||||
}
|
}
|
||||||
@ -173,6 +182,89 @@ func (p *ActiveMQPlugin) generateCredentials() []*base.Credential {
|
|||||||
return unique
|
return unique
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// performServiceIdentification 执行服务识别(-nobr模式)
|
||||||
|
func (p *ActiveMQPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 尝试连接到ActiveMQ服务进行基础识别
|
||||||
|
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: err,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// 尝试STOMP协议识别
|
||||||
|
stompInfo, isActiveMQ := p.identifySTOMPService(conn)
|
||||||
|
if isActiveMQ {
|
||||||
|
// 记录服务识别成功
|
||||||
|
common.LogSuccess(i18n.GetText("activemq_service_identified", target, "STOMP", stompInfo))
|
||||||
|
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: true,
|
||||||
|
Service: "ActiveMQ",
|
||||||
|
Banner: stompInfo,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
"service": "ActiveMQ",
|
||||||
|
"protocol": "STOMP",
|
||||||
|
"port": info.Ports,
|
||||||
|
"info": stompInfo,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法识别为ActiveMQ,返回一般服务信息
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: fmt.Errorf("无法识别为ActiveMQ服务"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifySTOMPService 识别STOMP协议服务
|
||||||
|
func (p *ActiveMQPlugin) identifySTOMPService(conn net.Conn) (string, bool) {
|
||||||
|
// 发送STOMP CONNECT帧进行协议识别(不提供凭据)
|
||||||
|
connectFrame := "CONNECT\naccept-version:1.0,1.1,1.2\nhost:localhost\n\n\x00"
|
||||||
|
|
||||||
|
conn.SetWriteDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
if _, err := conn.Write([]byte(connectFrame)); err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
response := make([]byte, 1024)
|
||||||
|
n, err := conn.Read(response)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
responseStr := string(response[:n])
|
||||||
|
|
||||||
|
// 检查是否为STOMP协议响应
|
||||||
|
if strings.Contains(responseStr, "CONNECTED") {
|
||||||
|
// 提取版本信息
|
||||||
|
version := "unknown"
|
||||||
|
if strings.Contains(responseStr, "version:") {
|
||||||
|
lines := strings.Split(responseStr, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "version:") {
|
||||||
|
version = strings.TrimPrefix(line, "version:")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("STOMP协议版本: %s", version), true
|
||||||
|
} else if strings.Contains(responseStr, "ERROR") {
|
||||||
|
// 即使返回错误,但能识别STOMP协议格式
|
||||||
|
return "STOMP协议(需要认证)", true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// GetServiceName 获取服务名称
|
// GetServiceName 获取服务名称
|
||||||
func (p *ActiveMQPlugin) GetServiceName() string {
|
func (p *ActiveMQPlugin) GetServiceName() string {
|
||||||
return "ActiveMQ"
|
return "ActiveMQ"
|
||||||
|
@ -3,6 +3,9 @@ package mysql
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
@ -62,6 +65,13 @@ func NewMySQLPlugin() *MySQLPlugin {
|
|||||||
// Scan 执行MySQL服务的完整安全扫描
|
// Scan 执行MySQL服务的完整安全扫描
|
||||||
// 重写基础扫描方法,集成弱密码检测和自动利用功能
|
// 重写基础扫描方法,集成弱密码检测和自动利用功能
|
||||||
func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 如果禁用暴力破解,则进行基础服务识别
|
||||||
|
if common.DisableBrute {
|
||||||
|
return p.performServiceIdentification(ctx, info)
|
||||||
|
}
|
||||||
|
|
||||||
// 调用基础服务插件进行弱密码扫描
|
// 调用基础服务插件进行弱密码扫描
|
||||||
result, err := p.ServicePlugin.Scan(ctx, info)
|
result, err := p.ServicePlugin.Scan(ctx, info)
|
||||||
if err != nil || !result.Success {
|
if err != nil || !result.Success {
|
||||||
@ -69,12 +79,11 @@ func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Sc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录成功的弱密码发现(使用i18n)
|
// 记录成功的弱密码发现(使用i18n)
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
||||||
cred := result.Credentials[0]
|
cred := result.Credentials[0]
|
||||||
common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password))
|
common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password))
|
||||||
|
|
||||||
// 自动利用功能(可通过-nobr参数禁用)
|
// 自动利用功能(可通过-ne参数禁用)
|
||||||
if result.Success && len(result.Credentials) > 0 && !common.DisableBrute {
|
if result.Success && len(result.Credentials) > 0 && !common.DisableExploit {
|
||||||
// 异步执行利用攻击,避免阻塞扫描进程
|
// 异步执行利用攻击,避免阻塞扫描进程
|
||||||
go p.autoExploit(context.Background(), info, result.Credentials[0])
|
go p.autoExploit(context.Background(), info, result.Credentials[0])
|
||||||
}
|
}
|
||||||
@ -127,6 +136,84 @@ func (p *MySQLPlugin) generateCredentials() []*base.Credential {
|
|||||||
return base.GenerateCredentials(usernames, common.Passwords)
|
return base.GenerateCredentials(usernames, common.Passwords)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// performServiceIdentification 执行MySQL服务识别(-nobr模式)
|
||||||
|
func (p *MySQLPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 尝试连接到MySQL服务获取握手包
|
||||||
|
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: err,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// 读取MySQL握手包
|
||||||
|
mysqlInfo, isMySQL := p.identifyMySQLService(conn)
|
||||||
|
if isMySQL {
|
||||||
|
// 记录服务识别成功
|
||||||
|
common.LogSuccess(i18n.GetText("mysql_service_identified", target, mysqlInfo))
|
||||||
|
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: true,
|
||||||
|
Service: "MySQL",
|
||||||
|
Banner: mysqlInfo,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
"service": "MySQL",
|
||||||
|
"port": info.Ports,
|
||||||
|
"info": mysqlInfo,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法识别为MySQL,返回失败
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: fmt.Errorf("无法识别为MySQL服务"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifyMySQLService 通过握手包识别MySQL服务
|
||||||
|
func (p *MySQLPlugin) identifyMySQLService(conn net.Conn) (string, bool) {
|
||||||
|
// 设置读取超时
|
||||||
|
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
|
||||||
|
// MySQL服务器在连接后会主动发送握手包
|
||||||
|
handshake := make([]byte, 1024)
|
||||||
|
n, err := conn.Read(handshake)
|
||||||
|
if err != nil || n < 10 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查MySQL握手包格式
|
||||||
|
// MySQL握手包开始: 包长度(3字节) + 序号(1字节) + 协议版本(1字节)
|
||||||
|
if handshake[4] != 10 { // MySQL 协议版本通常是10
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取版本字符串(从第5字节开始到第一个0结束)
|
||||||
|
versionStart := 5
|
||||||
|
versionEnd := versionStart
|
||||||
|
for versionEnd < n && handshake[versionEnd] != 0 {
|
||||||
|
versionEnd++
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionEnd <= versionStart {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
versionStr := string(handshake[versionStart:versionEnd])
|
||||||
|
|
||||||
|
// 验证版本字符串是否包含MySQL标识
|
||||||
|
if len(versionStr) > 0 && (regexp.MustCompile(`\d+\.\d+`).MatchString(versionStr)) {
|
||||||
|
return fmt.Sprintf("MySQL版本: %s", versionStr), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 插件注册
|
// 插件注册
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
@ -3,6 +3,9 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
@ -67,19 +70,16 @@ func (p *RedisPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Sc
|
|||||||
common.LogSuccess(i18n.GetText("redis_unauth_success", target))
|
common.LogSuccess(i18n.GetText("redis_unauth_success", target))
|
||||||
|
|
||||||
// 如果启用了利用功能,执行自动利用
|
// 如果启用了利用功能,执行自动利用
|
||||||
if !common.DisableBrute { // 使用DisableBrute作为替代,用户可以通过-nobr禁用利用功能
|
if !common.DisableExploit { // 使用DisableExploit控制利用功能
|
||||||
go p.autoExploit(context.Background(), info, nil) // 未授权访问不需要凭据
|
go p.autoExploit(context.Background(), info, nil) // 未授权访问不需要凭据
|
||||||
}
|
}
|
||||||
|
|
||||||
return unauthorizedResult, nil
|
return unauthorizedResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果未授权访问失败,尝试暴力破解
|
// 如果未授权访问失败,在-nobr模式下进行基础服务识别
|
||||||
if common.DisableBrute {
|
if common.DisableBrute {
|
||||||
return &base.ScanResult{
|
return p.performServiceIdentification(ctx, info)
|
||||||
Success: false,
|
|
||||||
Error: fmt.Errorf("暴力破解已禁用且无未授权访问"),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行基础的暴力破解扫描
|
// 执行基础的暴力破解扫描
|
||||||
@ -92,7 +92,7 @@ func (p *RedisPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Sc
|
|||||||
target, result.Credentials[0].Password))
|
target, result.Credentials[0].Password))
|
||||||
|
|
||||||
// 如果扫描成功并且启用了利用功能,执行自动利用
|
// 如果扫描成功并且启用了利用功能,执行自动利用
|
||||||
if result.Success && len(result.Credentials) > 0 && !common.DisableBrute {
|
if result.Success && len(result.Credentials) > 0 && !common.DisableExploit {
|
||||||
go p.autoExploit(context.Background(), info, result.Credentials[0])
|
go p.autoExploit(context.Background(), info, result.Credentials[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +170,87 @@ func (p *RedisPlugin) generateCredentials() []*base.Credential {
|
|||||||
return base.GeneratePasswordOnlyCredentials(common.Passwords)
|
return base.GeneratePasswordOnlyCredentials(common.Passwords)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// performServiceIdentification 执行Redis服务识别(-nobr模式)
|
||||||
|
func (p *RedisPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 尝试连接到Redis服务
|
||||||
|
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: err,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// 发送INFO命令获取Redis服务器信息
|
||||||
|
redisInfo, isRedis := p.identifyRedisService(conn)
|
||||||
|
if isRedis {
|
||||||
|
// 记录服务识别成功
|
||||||
|
common.LogSuccess(i18n.GetText("redis_service_identified", target, redisInfo))
|
||||||
|
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: true,
|
||||||
|
Service: "Redis",
|
||||||
|
Banner: redisInfo,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
"service": "Redis",
|
||||||
|
"port": info.Ports,
|
||||||
|
"info": redisInfo,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法识别为Redis,返回失败
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: fmt.Errorf("无法识别为Redis服务"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifyRedisService 通过INFO命令识别Redis服务
|
||||||
|
func (p *RedisPlugin) identifyRedisService(conn net.Conn) (string, bool) {
|
||||||
|
// 发送INFO命令
|
||||||
|
infoCmd := "INFO server\r\n"
|
||||||
|
|
||||||
|
conn.SetWriteDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
if _, err := conn.Write([]byte(infoCmd)); err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
response := make([]byte, 2048)
|
||||||
|
n, err := conn.Read(response)
|
||||||
|
if err != nil || n < 10 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
responseStr := string(response[:n])
|
||||||
|
|
||||||
|
// 检查是否为Redis响应
|
||||||
|
if strings.Contains(responseStr, "redis_version:") {
|
||||||
|
// 提取Redis版本信息
|
||||||
|
lines := strings.Split(responseStr, "\r\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "redis_version:") {
|
||||||
|
version := strings.TrimPrefix(line, "redis_version:")
|
||||||
|
return fmt.Sprintf("Redis版本: %s", version), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Redis服务(版本未知)", true
|
||||||
|
} else if strings.Contains(responseStr, "-NOAUTH") {
|
||||||
|
// 需要认证的Redis
|
||||||
|
return "Redis服务(需要认证)", true
|
||||||
|
} else if strings.Contains(responseStr, "+PONG") || strings.Contains(responseStr, "$") {
|
||||||
|
// 通过RESP协议特征识别
|
||||||
|
return "Redis服务", true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 插件注册
|
// 插件注册
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
@ -3,11 +3,16 @@ package ssh
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
"github.com/shadow1ng/fscan/plugins/base"
|
"github.com/shadow1ng/fscan/plugins/base"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"io/ioutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SSHConnector SSH连接器实现
|
// SSHConnector SSH连接器实现
|
||||||
@ -151,7 +156,7 @@ func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Scan
|
|||||||
common.LogSuccess(i18n.GetText("ssh_key_auth_success", target, result.Credentials[0].Username))
|
common.LogSuccess(i18n.GetText("ssh_key_auth_success", target, result.Credentials[0].Username))
|
||||||
|
|
||||||
// 自动利用功能 - 同步执行以确保及时显示结果
|
// 自动利用功能 - 同步执行以确保及时显示结果
|
||||||
if !common.DisableBrute {
|
if !common.DisableExploit {
|
||||||
p.autoExploit(context.Background(), info, result.Credentials[0])
|
p.autoExploit(context.Background(), info, result.Credentials[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,10 +166,7 @@ func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Scan
|
|||||||
|
|
||||||
// 执行基础的密码扫描
|
// 执行基础的密码扫描
|
||||||
if common.DisableBrute {
|
if common.DisableBrute {
|
||||||
return &base.ScanResult{
|
return p.performServiceIdentification(ctx, info)
|
||||||
Success: false,
|
|
||||||
Error: fmt.Errorf("暴力破解已禁用"),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := p.ServicePlugin.Scan(ctx, info)
|
result, err := p.ServicePlugin.Scan(ctx, info)
|
||||||
@ -177,8 +179,8 @@ func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Scan
|
|||||||
cred := result.Credentials[0]
|
cred := result.Credentials[0]
|
||||||
common.LogSuccess(i18n.GetText("ssh_pwd_auth_success", target, cred.Username, cred.Password))
|
common.LogSuccess(i18n.GetText("ssh_pwd_auth_success", target, cred.Username, cred.Password))
|
||||||
|
|
||||||
// 自动利用功能(可通过-nobr参数禁用)- 同步执行以确保及时显示结果
|
// 自动利用功能(可通过-ne参数禁用)- 同步执行以确保及时显示结果
|
||||||
if result.Success && len(result.Credentials) > 0 && !common.DisableBrute {
|
if result.Success && len(result.Credentials) > 0 && !common.DisableExploit {
|
||||||
p.autoExploit(context.Background(), info, result.Credentials[0])
|
p.autoExploit(context.Background(), info, result.Credentials[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,6 +263,89 @@ func (p *SSHPlugin) IsExploitSupported(method base.ExploitType) bool {
|
|||||||
return p.exploiter.IsExploitSupported(method)
|
return p.exploiter.IsExploitSupported(method)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// performServiceIdentification 执行SSH服务识别(-nobr模式)
|
||||||
|
func (p *SSHPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||||||
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
|
|
||||||
|
// 尝试连接到SSH服务获取Banner
|
||||||
|
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: err,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// 读取SSH Banner
|
||||||
|
sshInfo, isSSH := p.identifySSHService(conn)
|
||||||
|
if isSSH {
|
||||||
|
// 记录服务识别成功
|
||||||
|
common.LogSuccess(i18n.GetText("ssh_service_identified", target, sshInfo))
|
||||||
|
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: true,
|
||||||
|
Service: "SSH",
|
||||||
|
Banner: sshInfo,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
"service": "SSH",
|
||||||
|
"port": info.Ports,
|
||||||
|
"info": sshInfo,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法识别为SSH,返回失败
|
||||||
|
return &base.ScanResult{
|
||||||
|
Success: false,
|
||||||
|
Error: fmt.Errorf("无法识别为SSH服务"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// identifySSHService 通过Banner识别SSH服务
|
||||||
|
func (p *SSHPlugin) identifySSHService(conn net.Conn) (string, bool) {
|
||||||
|
// 设置读取超时
|
||||||
|
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||||||
|
|
||||||
|
// SSH服务器在连接后会发送Banner
|
||||||
|
banner := make([]byte, 512)
|
||||||
|
n, err := conn.Read(banner)
|
||||||
|
if err != nil || n < 4 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
bannerStr := strings.TrimSpace(string(banner[:n]))
|
||||||
|
|
||||||
|
// 检查SSH协议标识
|
||||||
|
if strings.HasPrefix(bannerStr, "SSH-") {
|
||||||
|
// 提取SSH版本信息
|
||||||
|
parts := strings.Fields(bannerStr)
|
||||||
|
if len(parts) > 0 {
|
||||||
|
// 提取协议版本和服务器标识
|
||||||
|
versionPart := parts[0]
|
||||||
|
serverInfo := ""
|
||||||
|
if len(parts) > 1 {
|
||||||
|
serverInfo = strings.Join(parts[1:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用正则表达式提取更详细信息
|
||||||
|
if matched := regexp.MustCompile(`SSH-([0-9.]+)-(.+)`).FindStringSubmatch(versionPart); len(matched) >= 3 {
|
||||||
|
protocolVersion := matched[1]
|
||||||
|
serverVersion := matched[2]
|
||||||
|
if serverInfo != "" {
|
||||||
|
return fmt.Sprintf("SSH %s (%s) %s", protocolVersion, serverVersion, serverInfo), true
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("SSH %s (%s)", protocolVersion, serverVersion), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("SSH服务: %s", bannerStr), true
|
||||||
|
}
|
||||||
|
return "SSH服务", true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 插件注册
|
// 插件注册
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
Loading…
Reference in New Issue
Block a user