fscan/plugins/services/snmp.go
ZacharyZcR 6cf5719e8a refactor: 彻底清理插件系统,消除虚假利用功能
- 删除整个legacy插件系统(7794行代码)
- 完成所有插件向单文件架构迁移
- 移除19个插件的虚假Exploit功能,只保留真实利用:
  * Redis: 文件写入、SSH密钥注入、计划任务
  * SSH: 命令执行
  * MS17010: EternalBlue漏洞利用
- 统一插件接口,简化架构复杂度
- 清理临时文件和备份文件

重构效果:
- 代码行数: -7794行
- 插件文件数: 从3文件架构→单文件架构
- 真实利用插件: 从22个→3个
- 架构复杂度: 大幅简化
2025-08-26 11:43:48 +08:00

270 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

package services
import (
"context"
"encoding/hex"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// SNMPPlugin SNMP网络管理协议扫描和利用插件 - 包含系统信息收集利用功能
type SNMPPlugin struct {
name string
ports []int
}
// NewSNMPPlugin 创建SNMP插件
func NewSNMPPlugin() *SNMPPlugin {
return &SNMPPlugin{
name: "snmp",
ports: []int{161, 162}, // SNMP端口
}
}
// GetName 实现Plugin接口
func (p *SNMPPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *SNMPPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行SNMP扫描 - 弱团体字符串检测
func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 生成测试团体字符串
communities := p.generateCommunities()
// 逐个测试团体字符串
for _, community := range communities {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "snmp",
Error: ctx.Err(),
}
default:
}
// 测试团体字符串
if p.testCommunity(ctx, info, community) {
// SNMP团体字符串测试成功
common.LogSuccess(i18n.GetText("snmp_scan_success", target, community))
return &ScanResult{
Success: true,
Service: "snmp",
Username: community, // 团体字符串作为用户名
Password: "",
}
}
}
// 所有团体字符串都失败
return &ScanResult{
Success: false,
Service: "snmp",
Error: fmt.Errorf("未发现弱团体字符串"),
}
}
// generateCommunities 生成测试团体字符串
func (p *SNMPPlugin) generateCommunities() []string {
// SNMP默认和常见团体字符串
return []string{
"public",
"private",
"community",
"snmp",
"admin",
"manager",
"read",
"write",
"test",
"guest",
"monitor",
"security",
"system",
}
}
// testCommunity 测试单个团体字符串
func (p *SNMPPlugin) testCommunity(ctx context.Context, info *common.HostInfo, community string) bool {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return false
}
defer conn.Close()
// 构建SNMP Get请求包 (sysDescr.0 OID: 1.3.6.1.2.1.1.1.0)
packet := p.buildSNMPGetRequest(community, "1.3.6.1.2.1.1.1.0")
conn.SetWriteDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if _, err := conn.Write(packet); 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
}
// 简单检查响应是否为有效的SNMP响应
return p.isValidSNMPResponse(response[:n])
}
// buildSNMPGetRequest 构建SNMP Get请求
func (p *SNMPPlugin) buildSNMPGetRequest(community, oid string) []byte {
// 简化的SNMP v1 Get请求构建
// 这里使用硬编码的包结构实际应该使用ASN.1编码
// SNMP Get Request for sysDescr.0 (1.3.6.1.2.1.1.1.0)
// 这是一个预构建的SNMP包模板community字符串需要替换
template := "302902010004067075626c6963a01c02020f7102010002010030113015060a2b060102010101000500"
// 将十六进制模板转换为字节
packet, err := hex.DecodeString(template)
if err != nil {
return nil
}
// 这里应该替换community字符串但为了简化使用固定的"public"
// 实际实现需要proper ASN.1编码
return packet
}
// isValidSNMPResponse 检查是否为有效的SNMP响应
func (p *SNMPPlugin) isValidSNMPResponse(data []byte) bool {
// 简单检查SNMP响应标志
if len(data) < 10 {
return false
}
// SNMP响应通常以0x30开始 (SEQUENCE)
return data[0] == 0x30
}
// getSystemInfo 获取系统信息
func (p *SNMPPlugin) getSystemInfo(ctx context.Context, info *common.HostInfo, community string) string {
var sysInfo strings.Builder
// 系统相关的OID
oids := map[string]string{
"系统描述": "1.3.6.1.2.1.1.1.0",
"系统OID": "1.3.6.1.2.1.1.2.0",
"系统运行时间": "1.3.6.1.2.1.1.3.0",
"系统联系人": "1.3.6.1.2.1.1.4.0",
"系统名称": "1.3.6.1.2.1.1.5.0",
"系统位置": "1.3.6.1.2.1.1.6.0",
}
for name, oid := range oids {
if value := p.getSNMPValue(ctx, info, community, oid); value != "" {
sysInfo.WriteString(fmt.Sprintf("%s: %s\n", name, value))
}
}
return sysInfo.String()
}
// getNetworkInterfaces 获取网络接口信息
func (p *SNMPPlugin) getNetworkInterfaces(ctx context.Context, info *common.HostInfo, community string) []string {
// 这里简化实现,实际需要遍历接口表
interfaces := []string{
"接口信息需要完整SNMP库支持",
"简化实现中暂时无法获取详细接口信息",
}
return interfaces
}
// getProcesses 获取进程信息
func (p *SNMPPlugin) getProcesses(ctx context.Context, info *common.HostInfo, community string) []string {
// HOST-RESOURCES-MIB中的进程表 (hrSWRunTable)
processes := []string{
"进程信息需要完整SNMP库支持",
"简化实现中暂时无法获取详细进程信息",
}
return processes
}
// getUsers 获取用户信息
func (p *SNMPPlugin) getUsers(ctx context.Context, info *common.HostInfo, community string) []string {
// 用户信息通常不通过SNMP直接获取
return []string{"用户信息通常不通过SNMP协议直接暴露"}
}
// getSNMPValue 获取指定OID的值
func (p *SNMPPlugin) getSNMPValue(ctx context.Context, info *common.HostInfo, community, oid string) string {
// 简化实现实际需要proper SNMP库
if p.testCommunity(ctx, info, community) {
return "SNMP值获取需要完整SNMP库支持"
}
return ""
}
// identifyService 服务识别 - 检测SNMP服务
func (p *SNMPPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试使用默认团体字符串"public"进行服务识别
if p.testCommunity(ctx, info, "public") {
banner := "SNMP网络管理服务 (public团体可访问)"
common.LogSuccess(i18n.GetText("snmp_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "snmp",
Banner: banner,
}
}
// 尝试UDP连接测试
conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return &ScanResult{
Success: false,
Service: "snmp",
Error: err,
}
}
defer conn.Close()
banner := "SNMP网络管理服务 (需要有效团体字符串)"
common.LogSuccess(i18n.GetText("snmp_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "snmp",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("snmp", func() Plugin {
return NewSNMPPlugin()
})
}