fscan/Plugins/services/snmp/plugin.go
ZacharyZcR 8da185257b feat: 实现SNMP网络管理协议专业扫描插件
- 新增SNMP服务插件,支持UDP协议和community字符串认证
- 实现SNMP连接器、利用器和主插件的完整架构
- 添加UDP端口161的特殊处理机制,解决UDP端口扫描问题
- 支持默认community字符串爆破(public, private, cisco, community)
- 集成SNMP系统信息获取和服务识别功能
- 完善国际化消息支持和Docker测试环境配置
2025-08-09 15:34:05 +08:00

240 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package snmp
import (
"context"
"fmt"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// SNMPPlugin SNMP插件实现
type SNMPPlugin struct {
*base.ServicePlugin
exploiter *SNMPExploiter
}
// NewSNMPPlugin 创建SNMP插件
func NewSNMPPlugin() *SNMPPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "snmp",
Version: "2.0.0",
Author: "fscan-team",
Description: "SNMP网络管理协议服务扫描插件",
Category: "service",
Ports: []int{161}, // SNMP默认端口
Protocols: []string{"udp"},
Tags: []string{"snmp", "network-management", "weak-community", "information-disclosure"},
}
// 创建连接器和服务插件
connector := NewSNMPConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建SNMP插件
plugin := &SNMPPlugin{
ServicePlugin: servicePlugin,
exploiter: NewSNMPExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
base.CapInformationLeak,
})
return plugin
}
// Scan 重写扫描方法进行SNMP服务扫描
func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 如果禁用了暴力破解,只进行服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 优先尝试默认community字符串
defaultCommunities := []*base.Credential{
{Username: "public", Password: ""},
{Username: "private", Password: ""},
{Username: "cisco", Password: ""},
{Username: "community", Password: ""},
}
// 先测试默认community
for _, cred := range defaultCommunities {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("snmp_weak_community_success", target, cred.Username))
return &base.ScanResult{
Success: true,
Service: "SNMP",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "SNMP",
"port": info.Ports,
"community": cred.Username,
"type": "weak-community",
},
}, nil
}
}
// 生成其他凭据进行暴力破解
credentials := p.generateCredentials()
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("snmp_weak_community_success", target, cred.Username))
return &base.ScanResult{
Success: true,
Service: "SNMP",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "SNMP",
"port": info.Ports,
"community": cred.Username,
"type": "weak-community",
},
}, nil
}
}
// 所有凭据都失败但可能识别到了SNMP服务
return p.performServiceIdentification(ctx, info)
}
// generateCredentials 生成SNMP凭据community字符串
func (p *SNMPPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// 获取SNMP community字典
communities := common.Userdict["snmp"]
if len(communities) == 0 {
// 常见的SNMP community字符串
communities = []string{
"admin", "manager", "secret", "read", "write", "test",
"monitor", "guest", "default", "root", "snmp", "router",
"switch", "network", "public1", "private1", "v1", "v2c",
}
}
// 生成community凭据
for _, community := range communities {
credentials = append(credentials, &base.Credential{
Username: community,
Password: "",
})
}
// 如果有密码字典也作为community使用
passwords := common.Passwords
if len(passwords) > 0 {
for _, password := range passwords {
// 替换密码中的占位符
actualPassword := strings.Replace(password, "{user}", "snmp", -1)
if actualPassword != "" && actualPassword != "snmp" {
credentials = append(credentials, &base.Credential{
Username: actualPassword,
Password: "",
})
}
}
}
return credentials
}
// Exploit 使用exploiter执行利用
func (p *SNMPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return p.exploiter.Exploit(ctx, info, creds)
}
// GetExploitMethods 获取利用方法
func (p *SNMPPlugin) GetExploitMethods() []base.ExploitMethod {
return p.exploiter.GetExploitMethods()
}
// IsExploitSupported 检查利用支持
func (p *SNMPPlugin) IsExploitSupported(method base.ExploitType) bool {
return p.exploiter.IsExploitSupported(method)
}
// performServiceIdentification 执行SNMP服务识别-nobr模式
func (p *SNMPPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试识别SNMP服务
connector := NewSNMPConnector()
conn, err := connector.Connect(ctx, info)
if err == nil && conn != nil {
if snmpConn, ok := conn.(*SNMPConnection); ok {
// 记录服务识别成功
common.LogSuccess(i18n.GetText("snmp_service_identified", target, snmpConn.info))
connector.Close(conn)
return &base.ScanResult{
Success: true,
Service: "SNMP",
Banner: snmpConn.info,
Extra: map[string]interface{}{
"service": "SNMP",
"port": info.Ports,
"info": snmpConn.info,
"protocol": "SNMP",
"community": snmpConn.community,
},
}, nil
}
}
// 如果无法识别为SNMP返回失败
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("无法识别为SNMP服务"),
}, nil
}
// =============================================================================
// 插件注册
// =============================================================================
// RegisterSNMPPlugin 注册SNMP插件
func RegisterSNMPPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "snmp",
Version: "2.0.0",
Author: "fscan-team",
Description: "SNMP网络管理协议服务扫描插件",
Category: "service",
Ports: []int{161}, // SNMP默认端口
Protocols: []string{"udp"},
Tags: []string{"snmp", "network-management", "weak-community", "information-disclosure"},
},
func() base.Plugin {
return NewSNMPPlugin()
},
)
base.GlobalPluginRegistry.Register("snmp", factory)
}
// 自动注册
func init() {
RegisterSNMPPlugin()
}