mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 新增SNMP服务插件,支持UDP协议和community字符串认证 - 实现SNMP连接器、利用器和主插件的完整架构 - 添加UDP端口161的特殊处理机制,解决UDP端口扫描问题 - 支持默认community字符串爆破(public, private, cisco, community) - 集成SNMP系统信息获取和服务识别功能 - 完善国际化消息支持和Docker测试环境配置
271 lines
6.0 KiB
Go
271 lines
6.0 KiB
Go
package snmp
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gosnmp/gosnmp"
|
||
"github.com/shadow1ng/fscan/common"
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
"github.com/shadow1ng/fscan/plugins/base"
|
||
)
|
||
|
||
// SNMPConnector SNMP连接器实现
|
||
type SNMPConnector struct {
|
||
host string
|
||
port int
|
||
}
|
||
|
||
// SNMPConnection SNMP连接结构
|
||
type SNMPConnection struct {
|
||
client *gosnmp.GoSNMP
|
||
community string
|
||
sysDesc string
|
||
info string
|
||
}
|
||
|
||
// NewSNMPConnector 创建SNMP连接器
|
||
func NewSNMPConnector() *SNMPConnector {
|
||
return &SNMPConnector{}
|
||
}
|
||
|
||
// Connect 建立SNMP连接
|
||
func (c *SNMPConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
|
||
// 解析端口
|
||
port, err := strconv.Atoi(info.Ports)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
|
||
}
|
||
|
||
c.host = info.Host
|
||
c.port = port
|
||
|
||
timeout := time.Duration(common.Timeout) * time.Second
|
||
|
||
// 结果通道
|
||
type connResult struct {
|
||
conn *SNMPConnection
|
||
err error
|
||
banner string
|
||
}
|
||
resultChan := make(chan connResult, 1)
|
||
|
||
// 在协程中尝试连接
|
||
go func() {
|
||
// 尝试使用默认的public community进行连接
|
||
client := &gosnmp.GoSNMP{
|
||
Target: info.Host,
|
||
Port: uint16(port),
|
||
Community: "public",
|
||
Version: gosnmp.Version2c,
|
||
Timeout: timeout,
|
||
Retries: 1,
|
||
}
|
||
|
||
err := client.Connect()
|
||
if err != nil {
|
||
select {
|
||
case <-ctx.Done():
|
||
case resultChan <- connResult{nil, err, ""}:
|
||
}
|
||
return
|
||
}
|
||
|
||
// 尝试获取系统描述信息
|
||
oids := []string{"1.3.6.1.2.1.1.1.0"} // sysDescr OID
|
||
result, err := client.Get(oids)
|
||
|
||
var sysDesc string
|
||
var banner string
|
||
|
||
if err == nil && len(result.Variables) > 0 {
|
||
if result.Variables[0].Type != gosnmp.NoSuchObject {
|
||
switch v := result.Variables[0].Value.(type) {
|
||
case []byte:
|
||
sysDesc = strings.TrimSpace(string(v))
|
||
case string:
|
||
sysDesc = strings.TrimSpace(v)
|
||
}
|
||
}
|
||
}
|
||
|
||
if sysDesc != "" {
|
||
banner = fmt.Sprintf("SNMP Service (Version: %s, System: %s)", client.Version.String(), sysDesc)
|
||
} else {
|
||
banner = fmt.Sprintf("SNMP Service (Version: %s)", client.Version.String())
|
||
}
|
||
|
||
// 创建连接对象
|
||
snmpConn := &SNMPConnection{
|
||
client: client,
|
||
community: "public",
|
||
sysDesc: sysDesc,
|
||
info: banner,
|
||
}
|
||
|
||
select {
|
||
case <-ctx.Done():
|
||
client.Conn.Close()
|
||
case resultChan <- connResult{snmpConn, nil, banner}:
|
||
}
|
||
}()
|
||
|
||
// 等待连接结果
|
||
select {
|
||
case result := <-resultChan:
|
||
if result.err != nil {
|
||
return nil, result.err
|
||
}
|
||
return result.conn, nil
|
||
case <-ctx.Done():
|
||
return nil, ctx.Err()
|
||
}
|
||
}
|
||
|
||
// Authenticate 进行SNMP认证(通过community字符串)
|
||
func (c *SNMPConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
|
||
snmpConn, ok := conn.(*SNMPConnection)
|
||
if !ok {
|
||
return fmt.Errorf("无效的SNMP连接类型")
|
||
}
|
||
|
||
// 对于SNMP,将用户名作为community字符串使用
|
||
community := cred.Username
|
||
if community == "" {
|
||
community = cred.Password // 如果用户名为空,尝试使用密码作为community
|
||
}
|
||
|
||
timeout := time.Duration(common.Timeout) * time.Second
|
||
|
||
// 结果通道
|
||
type authResult struct {
|
||
client *gosnmp.GoSNMP
|
||
sysDesc string
|
||
err error
|
||
}
|
||
resultChan := make(chan authResult, 1)
|
||
|
||
// 在协程中尝试认证
|
||
go func() {
|
||
// 关闭旧连接
|
||
if snmpConn.client != nil && snmpConn.client.Conn != nil {
|
||
snmpConn.client.Conn.Close()
|
||
}
|
||
|
||
// 创建新的SNMP客户端
|
||
client := &gosnmp.GoSNMP{
|
||
Target: c.host,
|
||
Port: uint16(c.port),
|
||
Community: community,
|
||
Version: gosnmp.Version2c,
|
||
Timeout: timeout,
|
||
Retries: 1,
|
||
}
|
||
|
||
err := client.Connect()
|
||
if err != nil {
|
||
select {
|
||
case <-ctx.Done():
|
||
case resultChan <- authResult{nil, "", err}:
|
||
}
|
||
return
|
||
}
|
||
|
||
// 尝试获取系统描述信息验证认证
|
||
oids := []string{"1.3.6.1.2.1.1.1.0"} // sysDescr OID
|
||
result, err := client.Get(oids)
|
||
if err != nil {
|
||
client.Conn.Close()
|
||
select {
|
||
case <-ctx.Done():
|
||
case resultChan <- authResult{nil, "", err}:
|
||
}
|
||
return
|
||
}
|
||
|
||
var sysDesc string
|
||
if len(result.Variables) > 0 && result.Variables[0].Type != gosnmp.NoSuchObject {
|
||
switch v := result.Variables[0].Value.(type) {
|
||
case []byte:
|
||
sysDesc = strings.TrimSpace(string(v))
|
||
case string:
|
||
sysDesc = strings.TrimSpace(v)
|
||
}
|
||
}
|
||
|
||
select {
|
||
case <-ctx.Done():
|
||
client.Conn.Close()
|
||
case resultChan <- authResult{client, sysDesc, nil}:
|
||
}
|
||
}()
|
||
|
||
// 等待认证结果
|
||
select {
|
||
case result := <-resultChan:
|
||
if result.err != nil {
|
||
return fmt.Errorf(i18n.GetText("snmp_auth_failed"), result.err)
|
||
}
|
||
|
||
// 更新连接信息
|
||
snmpConn.client = result.client
|
||
snmpConn.community = community
|
||
snmpConn.sysDesc = result.sysDesc
|
||
if result.sysDesc != "" {
|
||
snmpConn.info = fmt.Sprintf("SNMP Service (Community: %s, System: %s)", community, result.sysDesc)
|
||
} else {
|
||
snmpConn.info = fmt.Sprintf("SNMP Service (Community: %s)", community)
|
||
}
|
||
|
||
return nil
|
||
case <-ctx.Done():
|
||
return ctx.Err()
|
||
}
|
||
}
|
||
|
||
// Close 关闭SNMP连接
|
||
func (c *SNMPConnector) Close(conn interface{}) error {
|
||
if snmpConn, ok := conn.(*SNMPConnection); ok {
|
||
if snmpConn.client != nil && snmpConn.client.Conn != nil {
|
||
snmpConn.client.Conn.Close()
|
||
}
|
||
return nil
|
||
}
|
||
return fmt.Errorf("无效的SNMP连接类型")
|
||
}
|
||
|
||
// GetConnectionInfo 获取连接信息
|
||
func (conn *SNMPConnection) GetConnectionInfo() map[string]interface{} {
|
||
info := map[string]interface{}{
|
||
"protocol": "SNMP",
|
||
"service": "SNMP",
|
||
"info": conn.info,
|
||
"community": conn.community,
|
||
}
|
||
|
||
if conn.sysDesc != "" {
|
||
info["system"] = conn.sysDesc
|
||
}
|
||
|
||
return info
|
||
}
|
||
|
||
// IsAlive 检查连接是否仍然有效
|
||
func (conn *SNMPConnection) IsAlive() bool {
|
||
if conn.client == nil || conn.client.Conn == nil {
|
||
return false
|
||
}
|
||
|
||
// 简单的SNMP GET测试连接
|
||
oids := []string{"1.3.6.1.2.1.1.1.0"}
|
||
_, err := conn.client.Get(oids)
|
||
return err == nil
|
||
}
|
||
|
||
// GetServerInfo 获取服务器信息
|
||
func (conn *SNMPConnection) GetServerInfo() string {
|
||
return conn.info
|
||
} |