diff --git a/plugins/services/activemq.go b/plugins/services/activemq.go index 6e9859d..6656c96 100644 --- a/plugins/services/activemq.go +++ b/plugins/services/activemq.go @@ -8,34 +8,28 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// ActiveMQPlugin ActiveMQ消息队列扫描插件 - 基于STOMP协议 type ActiveMQPlugin struct { name string ports []int } -// NewActiveMQPlugin 创建ActiveMQ插件 func NewActiveMQPlugin() *ActiveMQPlugin { return &ActiveMQPlugin{ name: "activemq", - ports: []int{61616, 61617, 61618, 8161}, // STOMP端口 + Web管理界面 + ports: []int{61616, 61617, 61618, 8161}, } } -// GetName 实现Plugin接口 func (p *ActiveMQPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *ActiveMQPlugin) GetPorts() []int { return p.ports } -// Scan 执行ActiveMQ扫描 - STOMP协议弱密码检测 func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -44,36 +38,18 @@ func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR return p.identifyService(info) } - // 生成测试凭据 credentials := GenerateCredentials("activemq") if len(credentials) == 0 { - // ActiveMQ默认凭据 - credentials = []Credential{ - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: ""}, - {Username: "admin", Password: "password"}, - {Username: "user", Password: "user"}, - {Username: "guest", Password: "guest"}, + return &ScanResult{ + Success: false, + Service: "activemq", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "activemq", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // ActiveMQ认证成功 - common.LogSuccess(i18n.GetText("activemq_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("ActiveMQ %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -84,7 +60,6 @@ func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "activemq", @@ -92,39 +67,30 @@ func (p *ActiveMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR } } -// testCredential 测试单个凭据 - STOMP协议认证 func (p *ActiveMQPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second - // 建立TCP连接 conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout) if err != nil { return false } defer conn.Close() - // 使用STOMP协议进行认证 return p.authenticateSTOMP(conn, cred.Username, cred.Password) } -// authenticateSTOMP 使用STOMP协议进行身份验证 -// STOMP (Simple Text Oriented Messaging Protocol) 是ActiveMQ支持的文本协议 func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string) bool { timeout := time.Duration(common.Timeout) * time.Second - // 构造STOMP CONNECT帧 - // STOMP是基于帧的协议,每个帧以NULL字符结尾 stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", username, password) - // 设置写超时并发送认证请求 conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte(stompConnect)); err != nil { return false } - // 设置读超时并读取响应 conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 1024) n, err := conn.Read(response) @@ -134,26 +100,19 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str responseStr := string(response[:n]) - // 检查STOMP响应 - // 成功响应应该包含"CONNECTED"帧 - // 失败响应包含"ERROR"帧 if strings.Contains(responseStr, "CONNECTED") { return true } else if strings.Contains(responseStr, "ERROR") { - // 错误响应,认证失败 return false } - // 未知响应格式 return false } -// identifyService 服务识别 - 检测STOMP协议 func (p *ActiveMQPlugin) identifyService(info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second - // 尝试连接 conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout) if err != nil { return &ScanResult{ @@ -164,7 +123,6 @@ func (p *ActiveMQPlugin) identifyService(info *common.HostInfo) *ScanResult { } defer conn.Close() - // 发送简单的STOMP CONNECT帧(无认证信息) stompConnect := "CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\n\n\x00" conn.SetWriteDeadline(time.Now().Add(timeout)) @@ -176,7 +134,6 @@ func (p *ActiveMQPlugin) identifyService(info *common.HostInfo) *ScanResult { } } - // 读取响应 conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 512) n, err := conn.Read(response) @@ -190,10 +147,9 @@ func (p *ActiveMQPlugin) identifyService(info *common.HostInfo) *ScanResult { responseStr := string(response[:n]) - // 检查是否为STOMP协议响应 if strings.Contains(responseStr, "CONNECTED") || strings.Contains(responseStr, "ERROR") { - banner := p.extractSTOMPVersion(responseStr) - common.LogSuccess(i18n.GetText("activemq_service_identified", target, banner)) + banner := "ActiveMQ STOMP" + common.LogSuccess(fmt.Sprintf("ActiveMQ %s %s", target, banner)) return &ScanResult{ Success: true, @@ -209,34 +165,7 @@ func (p *ActiveMQPlugin) identifyService(info *common.HostInfo) *ScanResult { } } -// extractSTOMPVersion 从STOMP响应中提取版本信息 -func (p *ActiveMQPlugin) extractSTOMPVersion(response string) string { - lines := strings.Split(response, "\n") - - for _, line := range lines { - // 查找version头 - if strings.HasPrefix(line, "version:") { - version := strings.TrimPrefix(line, "version:") - return fmt.Sprintf("ActiveMQ STOMP %s", version) - } - // 查找server头 - if strings.HasPrefix(line, "server:") { - server := strings.TrimPrefix(line, "server:") - return fmt.Sprintf("ActiveMQ %s", server) - } - } - // 如果没有找到版本信息,返回通用描述 - if strings.Contains(response, "CONNECTED") { - return "ActiveMQ STOMP (版本未知)" - } else if strings.Contains(response, "ERROR") { - return "ActiveMQ STOMP (需要认证)" - } - - return "ActiveMQ STOMP" -} - -// init 自动注册插件 func init() { RegisterPlugin("activemq", func() Plugin { return NewActiveMQPlugin() diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index 12278a5..b74d58a 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -3,79 +3,53 @@ package services import ( "context" "fmt" - "net" "strconv" "strings" "time" "github.com/gocql/gocql" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// CassandraPlugin Cassandra数据库扫描插件 - 基于gocql库 type CassandraPlugin struct { name string ports []int } -// NewCassandraPlugin 创建Cassandra插件 func NewCassandraPlugin() *CassandraPlugin { return &CassandraPlugin{ name: "cassandra", - ports: []int{9042, 9160, 7000, 7001}, // CQL端口 + Thrift端口 + 集群通信端口 + ports: []int{9042, 9160, 7000, 7001}, } } -// GetName 实现Plugin接口 func (p *CassandraPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *CassandraPlugin) GetPorts() []int { return p.ports } -// Scan 执行Cassandra扫描 - CQL协议弱密码检测 func (p *CassandraPlugin) 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) } - // 生成测试凭据 credentials := GenerateCredentials("cassandra") if len(credentials) == 0 { - // Cassandra默认凭据 - credentials = []Credential{ - {Username: "cassandra", Password: "cassandra"}, - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: ""}, - {Username: "root", Password: "root"}, - {Username: "user", Password: "user"}, + return &ScanResult{ + Success: false, + Service: "cassandra", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "cassandra", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // Cassandra认证成功 - common.LogSuccess(i18n.GetText("cassandra_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("Cassandra %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -86,7 +60,6 @@ func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo) *Scan } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "cassandra", @@ -94,26 +67,18 @@ func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo) *Scan } } -// testCredential 测试单个凭据 - 使用gocql进行CQL认证 func (p *CassandraPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { - // 解析端口 port, err := strconv.Atoi(info.Ports) if err != nil { return false } - // 创建Cassandra集群配置 cluster := gocql.NewCluster(info.Host) cluster.Port = port - - // 设置连接参数 timeout := time.Duration(common.Timeout) * time.Second cluster.Timeout = timeout cluster.ConnectTimeout = timeout - cluster.ProtoVersion = 4 - cluster.Consistency = gocql.One - // 设置认证信息 if cred.Username != "" || cred.Password != "" { cluster.Authenticator = gocql.PasswordAuthenticator{ Username: cred.Username, @@ -121,93 +86,21 @@ func (p *CassandraPlugin) testCredential(ctx context.Context, info *common.HostI } } - // 支持代理连接 - if common.Socks5Proxy != "" { - cluster.Dialer = &cassandraProxyDialer{timeout: timeout} - } - - // 设置重试策略 - cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 2} - - // 创建会话 - session, err := p.createSessionWithTimeout(ctx, cluster) + session, err := cluster.CreateSession() if err != nil { return false } defer session.Close() - // 执行简单查询验证连接 - return p.validateConnection(ctx, session) + var dummy interface{} + err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&dummy) + return err == nil } -// createSessionWithTimeout 在超时控制下创建Cassandra会话 -func (p *CassandraPlugin) createSessionWithTimeout(ctx context.Context, cluster *gocql.ClusterConfig) (*gocql.Session, error) { - // 创建会话通道以支持Context超时 - sessionChan := make(chan struct { - session *gocql.Session - err error - }, 1) - // 在goroutine中创建会话 - 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}: - } - }() - // 等待会话创建或Context取消 - select { - case result := <-sessionChan: - return result.session, result.err - case <-ctx.Done(): - return nil, fmt.Errorf("创建会话超时: %v", ctx.Err()) - } -} - -// validateConnection 验证Cassandra连接 - 执行系统查询 -func (p *CassandraPlugin) validateConnection(ctx context.Context, session *gocql.Session) bool { - // 使用通道支持Context超时 - resultChan := make(chan bool, 1) - - go func() { - // 尝试执行系统查询 - // 先尝试查询节点信息,如果失败再尝试本地信息 - var dummy interface{} - - err := session.Query("SELECT peer FROM system.peers LIMIT 1").WithContext(ctx).Scan(&dummy) - if err != nil { - // 如果peer查询失败,尝试查询本地节点信息 - err = session.Query("SELECT now() FROM system.local").WithContext(ctx).Scan(&dummy) - } - - select { - case <-ctx.Done(): - case resultChan <- (err == nil): - } - }() - - // 等待查询结果或Context取消 - select { - case success := <-resultChan: - return success - case <-ctx.Done(): - return false - } -} - -// identifyService 服务识别 - 尝试连接Cassandra但不认证 func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - // 解析端口 port, err := strconv.Atoi(info.Ports) if err != nil { return &ScanResult{ @@ -217,34 +110,23 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host } } - // 创建基本集群配置(无认证) cluster := gocql.NewCluster(info.Host) cluster.Port = port - timeout := time.Duration(common.Timeout) * time.Second cluster.Timeout = timeout cluster.ConnectTimeout = timeout - cluster.ProtoVersion = 4 - // 支持代理 - if common.Socks5Proxy != "" { - cluster.Dialer = &cassandraProxyDialer{timeout: timeout} - } - - // 尝试创建会话 - session, err := p.createSessionWithTimeout(ctx, cluster) + session, err := cluster.CreateSession() if err != nil { - // 检查错误类型,判断是否为认证问题 - if p.isAuthenticationError(err) { - // 认证错误说明服务存在但需要密码 - common.LogSuccess(i18n.GetText("cassandra_service_identified", target, "Cassandra (需要认证)")) + if strings.Contains(strings.ToLower(err.Error()), "authentication") { + banner := "Cassandra (需要认证)" + common.LogSuccess(fmt.Sprintf("Cassandra %s %s", target, banner)) return &ScanResult{ Success: true, Service: "cassandra", - Banner: "Cassandra (需要认证)", + Banner: banner, } } - return &ScanResult{ Success: false, Service: "cassandra", @@ -253,10 +135,8 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host } defer session.Close() - // 连接成功,获取Cassandra版本信息 - banner := p.getCassandraVersion(ctx, session) - common.LogSuccess(i18n.GetText("cassandra_service_identified", target, banner)) - + banner := "Cassandra" + common.LogSuccess(fmt.Sprintf("Cassandra %s %s", target, banner)) return &ScanResult{ Success: true, Service: "cassandra", @@ -264,55 +144,9 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host } } -// getCassandraVersion 获取Cassandra版本信息 -func (p *CassandraPlugin) getCassandraVersion(ctx context.Context, session *gocql.Session) string { - // 尝试查询系统版本信息 - var releaseVersion string - err := session.Query("SELECT release_version FROM system.local").WithContext(ctx).Scan(&releaseVersion) - if err == nil && releaseVersion != "" { - return fmt.Sprintf("Cassandra %s", releaseVersion) - } - // 如果无法获取版本,返回通用描述 - return "Cassandra (版本未知)" -} -// isAuthenticationError 判断是否为认证错误 -func (p *CassandraPlugin) isAuthenticationError(err error) bool { - if err == nil { - return false - } - - errStr := err.Error() - // 检查常见的认证错误信息 - authErrors := []string{ - "authentication", - "password", - "credentials", - "unauthorized", - "login", - } - for _, authErr := range authErrors { - if strings.Contains(strings.ToLower(errStr), authErr) { - return true - } - } - - return false -} - -// cassandraProxyDialer 代理拨号器 -type cassandraProxyDialer struct { - timeout time.Duration -} - -// DialContext 实现gocql.Dialer接口 -func (d *cassandraProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - return common.WrapperTcpWithContext(ctx, network, addr) -} - -// init 自动注册插件 func init() { RegisterPlugin("cassandra", func() Plugin { return NewCassandraPlugin() diff --git a/plugins/services/elasticsearch.go b/plugins/services/elasticsearch.go index efe11f5..f8493fe 100644 --- a/plugins/services/elasticsearch.go +++ b/plugins/services/elasticsearch.go @@ -4,7 +4,6 @@ import ( "context" "crypto/tls" "encoding/base64" - "encoding/json" "fmt" "io" "net/http" @@ -14,87 +13,45 @@ import ( "github.com/shadow1ng/fscan/common" ) -// ElasticsearchPlugin Elasticsearch搜索引擎扫描插件 - 弱密码检测和未授权访问检测 type ElasticsearchPlugin struct { name string ports []int } -// NewElasticsearchPlugin 创建Elasticsearch插件 func NewElasticsearchPlugin() *ElasticsearchPlugin { return &ElasticsearchPlugin{ name: "elasticsearch", - ports: []int{9200, 9300}, // Elasticsearch端口 + ports: []int{9200, 9300}, } } -// GetName 实现Plugin接口 func (p *ElasticsearchPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *ElasticsearchPlugin) GetPorts() []int { return p.ports } -// Scan 执行Elasticsearch扫描 - 弱密码检测和未授权访问检测 func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 检查端口 - if info.Ports != "9200" && info.Ports != "9300" { - return &ScanResult{ - Success: false, - Service: "elasticsearch", - Error: fmt.Errorf("Elasticsearch插件仅支持9200和9300端口"), - } - } - - // 如果禁用暴力破解,只做服务识别 if common.DisableBrute { return p.identifyService(ctx, info) } - // 首先测试未授权访问 - if result := p.testUnauthAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(fmt.Sprintf("Elasticsearch %s 未授权访问", target)) - return result - } - - // 生成测试凭据 credentials := GenerateCredentials("elasticsearch") if len(credentials) == 0 { - // Elasticsearch默认凭据 - credentials = []Credential{ - {Username: "", Password: ""}, - {Username: "elastic", Password: ""}, - {Username: "elastic", Password: "elastic"}, - {Username: "elastic", Password: "password"}, - {Username: "elastic", Password: "123456"}, - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: ""}, + return &ScanResult{ + Success: false, + Service: "elasticsearch", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "elasticsearch", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // Elasticsearch认证成功 - common.LogSuccess(fmt.Sprintf("Elasticsearch %s 弱密码 %s:%s", target, cred.Username, cred.Password)) - + common.LogSuccess(fmt.Sprintf("Elasticsearch %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "elasticsearch", @@ -104,35 +61,14 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo) * } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "elasticsearch", - Error: fmt.Errorf("未发现弱密码或未授权访问"), + Error: fmt.Errorf("未发现弱密码"), } } -// ElasticsearchClusterInfo 集群信息结构 -type ElasticsearchClusterInfo struct { - ClusterName string - Version string - NodeName string -} - -// testUnauthAccess 测试未授权访问 -func (p *ElasticsearchPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo) *ScanResult { - if p.testCredential(ctx, info, Credential{Username: "", Password: ""}) { - return &ScanResult{ - Success: true, - Service: "elasticsearch", - Banner: "未授权访问", - } - } - return nil -} - -// testCredential 测试单个凭据 func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { client := &http.Client{ Timeout: time.Duration(common.Timeout) * time.Second, @@ -153,7 +89,6 @@ func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.H return false } - // 如果有凭据,添加认证头 if cred.Username != "" || cred.Password != "" { auth := base64.StdEncoding.EncodeToString([]byte(cred.Username + ":" + cred.Password)) req.Header.Set("Authorization", "Basic "+auth) @@ -165,9 +100,7 @@ func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.H } defer resp.Body.Close() - // 检查响应状态码 if resp.StatusCode == 200 { - // 读取响应内容验证是否为Elasticsearch body, err := io.ReadAll(resp.Body) if err != nil { return false @@ -180,215 +113,20 @@ func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.H return false } -// getClusterInfo 获取集群信息 -func (p *ElasticsearchPlugin) getClusterInfo(ctx context.Context, info *common.HostInfo, creds Credential) *ElasticsearchClusterInfo { - client := &http.Client{ - Timeout: time.Duration(common.Timeout) * time.Second, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } - protocol := "http" - if info.Ports == "9443" { - protocol = "https" - } - url := fmt.Sprintf("%s://%s:%s/", protocol, info.Host, info.Ports) - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return nil - } - if creds.Username != "" || creds.Password != "" { - auth := base64.StdEncoding.EncodeToString([]byte(creds.Username + ":" + creds.Password)) - req.Header.Set("Authorization", "Basic "+auth) - } - - resp, err := client.Do(req) - if err != nil { - return nil - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil - } - - // 解析JSON响应 - var clusterData map[string]interface{} - if err := json.Unmarshal(body, &clusterData); err != nil { - return nil - } - - clusterInfo := &ElasticsearchClusterInfo{} - - if clusterName, ok := clusterData["cluster_name"].(string); ok { - clusterInfo.ClusterName = clusterName - } - - if nodeName, ok := clusterData["name"].(string); ok { - clusterInfo.NodeName = nodeName - } - - if version, ok := clusterData["version"].(map[string]interface{}); ok { - if number, ok := version["number"].(string); ok { - clusterInfo.Version = number - } - } - - return clusterInfo -} - -// getIndices 获取索引列表 -func (p *ElasticsearchPlugin) getIndices(ctx context.Context, info *common.HostInfo, creds Credential) []string { - client := &http.Client{ - Timeout: time.Duration(common.Timeout) * time.Second, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } - - protocol := "http" - if info.Ports == "9443" { - protocol = "https" - } - url := fmt.Sprintf("%s://%s:%s/_cat/indices?format=json", protocol, info.Host, info.Ports) - - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return nil - } - - if creds.Username != "" || creds.Password != "" { - auth := base64.StdEncoding.EncodeToString([]byte(creds.Username + ":" + creds.Password)) - req.Header.Set("Authorization", "Basic "+auth) - } - - resp, err := client.Do(req) - if err != nil { - return nil - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil - } - - // 解析索引信息 - var indices []map[string]interface{} - if err := json.Unmarshal(body, &indices); err != nil { - return nil - } - - var indexNames []string - for _, index := range indices { - if indexName, ok := index["index"].(string); ok { - indexNames = append(indexNames, indexName) - } - } - - return indexNames -} - -// checkSensitiveData 检查敏感数据 -func (p *ElasticsearchPlugin) checkSensitiveData(ctx context.Context, info *common.HostInfo, creds Credential) map[string]int { - sensitiveData := make(map[string]int) - - client := &http.Client{ - Timeout: time.Duration(common.Timeout) * time.Second, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } - - protocol := "http" - if info.Ports == "9443" { - protocol = "https" - } - - // 获取所有索引 - indices := p.getIndices(ctx, info, creds) - if len(indices) == 0 { - return sensitiveData - } - - // 检查常见的敏感索引 - sensitivePatterns := []string{"user", "account", "password", "credential", "login", "auth", "admin", "config"} - - for _, index := range indices { - indexLower := strings.ToLower(index) - - for _, pattern := range sensitivePatterns { - if strings.Contains(indexLower, pattern) { - // 获取该索引的文档数量 - url := fmt.Sprintf("%s://%s:%s/%s/_count", protocol, info.Host, info.Ports, index) - - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - continue - } - - if creds.Username != "" || creds.Password != "" { - auth := base64.StdEncoding.EncodeToString([]byte(creds.Username + ":" + creds.Password)) - req.Header.Set("Authorization", "Basic "+auth) - } - - resp, err := client.Do(req) - if err != nil { - continue - } - - if resp.StatusCode == 200 { - body, err := io.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - continue - } - - var countData map[string]interface{} - if err := json.Unmarshal(body, &countData); err != nil { - continue - } - - if count, ok := countData["count"].(float64); ok { - sensitiveData[fmt.Sprintf("%s (索引: %s)", pattern, index)] = int(count) - } - } else { - resp.Body.Close() - } - break - } - } - } - - return sensitiveData -} - -// identifyService 服务识别 - 检测Elasticsearch服务 func (p *ElasticsearchPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { if p.testCredential(ctx, info, Credential{Username: "", Password: ""}) { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - banner := "Elasticsearch搜索引擎服务" + banner := "Elasticsearch" common.LogSuccess(fmt.Sprintf("Elasticsearch %s %s", target, banner)) - return &ScanResult{ Success: true, Service: "elasticsearch", Banner: banner, } } - return &ScanResult{ Success: false, Service: "elasticsearch", @@ -396,7 +134,6 @@ func (p *ElasticsearchPlugin) identifyService(ctx context.Context, info *common. } } -// init 自动注册插件 func init() { RegisterPlugin("elasticsearch", func() Plugin { return NewElasticsearchPlugin() diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index 00bb7df..66e3d5f 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -3,86 +3,52 @@ package services import ( "context" "fmt" - "strings" "time" ftplib "github.com/jlaffaye/ftp" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// FTPPlugin FTP服务扫描和利用插件 - 包含文件操作利用功能 type FTPPlugin struct { name string ports []int } -// NewFTPPlugin 创建FTP插件 func NewFTPPlugin() *FTPPlugin { return &FTPPlugin{ name: "ftp", - ports: []int{21, 2121, 990}, // FTP端口 + FTPS端口 + ports: []int{21, 2121, 990}, } } -// GetName 实现Plugin接口 func (p *FTPPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *FTPPlugin) GetPorts() []int { return p.ports } -// Scan 执行FTP扫描 - 弱密码检测和匿名访问检测 func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 如果禁用暴力破解,只做服务识别 if common.DisableBrute { return p.identifyService(info) } - // 首先检查匿名访问 - if result := p.testAnonymousAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("ftp_anonymous_success", target)) - return result - } - - // 生成测试凭据 credentials := GenerateCredentials("ftp") if len(credentials) == 0 { - // FTP默认凭据 - credentials = []Credential{ - {Username: "ftp", Password: "ftp"}, - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: ""}, - {Username: "user", Password: "user"}, - {Username: "test", Password: "test"}, + return &ScanResult{ + Success: false, + Service: "ftp", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "ftp", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if conn := p.testCredential(ctx, info, cred); conn != nil { - conn.Quit() // 关闭测试连接 - - // FTP认证成功 - common.LogSuccess(i18n.GetText("ftp_scan_success", target, cred.Username, cred.Password)) - + conn.Quit() + common.LogSuccess(fmt.Sprintf("FTP %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "ftp", @@ -92,114 +58,38 @@ func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "ftp", - Error: fmt.Errorf("未发现弱密码或匿名访问"), + Error: fmt.Errorf("未发现弱密码"), } } -// testAnonymousAccess 测试匿名访问 -func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostInfo) *ScanResult { - // 尝试匿名登录 - anonCreds := []Credential{ - {Username: "anonymous", Password: ""}, - {Username: "anonymous", Password: "anonymous"}, - {Username: "anonymous", Password: "guest@example.com"}, - {Username: "ftp", Password: ""}, - } - - for _, cred := range anonCreds { - if conn := p.testCredential(ctx, info, cred); conn != nil { - conn.Quit() - return &ScanResult{ - Success: true, - Service: "ftp", - Username: cred.Username, - Password: cred.Password, - Banner: "匿名访问", - } - } - } - - return nil -} - -// testCredential 测试单个凭据 - 返回FTP连接或nil func (p *FTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *ftplib.ServerConn { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second - // 使用Context控制超时的连接 - type ftpResult struct { - conn *ftplib.ServerConn - err error - } - - connChan := make(chan ftpResult, 1) - - go func() { - // 建立FTP连接 - conn, err := ftplib.DialTimeout(target, timeout) - if err != nil { - connChan <- ftpResult{nil, err} - return - } - - // 尝试登录 - err = conn.Login(cred.Username, cred.Password) - if err != nil { - conn.Quit() - connChan <- ftpResult{nil, err} - return - } - - connChan <- ftpResult{conn, nil} - }() - - // 等待连接结果或超时 - select { - case result := <-connChan: - if result.err != nil { - return nil - } - return result.conn - case <-ctx.Done(): + conn, err := ftplib.DialTimeout(target, timeout) + if err != nil { return nil } -} -// testWritePermission 测试写权限 -func (p *FTPPlugin) testWritePermission(conn *ftplib.ServerConn, filename, content string) error { - // 尝试创建文件并写入内容 - return conn.Stor(filename, strings.NewReader(content)) -} - -// getSystemInfo 获取系统信息 -func (p *FTPPlugin) getSystemInfo(conn *ftplib.ServerConn) string { - var info strings.Builder - - // 尝试获取当前目录作为系统信息 - if pwd, err := conn.CurrentDir(); err == nil { - info.WriteString(fmt.Sprintf("当前目录: %s\n", pwd)) + err = conn.Login(cred.Username, cred.Password) + if err != nil { + conn.Quit() + return nil } - // 尝试列出当前目录获取更多信息 - if entries, err := conn.List("."); err == nil && len(entries) > 0 { - info.WriteString(fmt.Sprintf("目录项数量: %d\n", len(entries))) - } - - return info.String() + return conn } -// identifyService 服务识别 - 检测FTP服务 + + func (p *FTPPlugin) identifyService(info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second - // 尝试连接FTP服务 conn, err := ftplib.DialTimeout(target, timeout) if err != nil { return &ScanResult{ @@ -210,16 +100,8 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo) *ScanResult { } defer conn.Quit() - // 获取FTP服务器信息 - var banner string - if pwd, err := conn.CurrentDir(); err == nil { - banner = fmt.Sprintf("FTP服务 (根目录: %s)", pwd) - } else { - banner = "FTP服务" - } - - common.LogSuccess(i18n.GetText("ftp_service_identified", target, banner)) - + banner := "FTP" + common.LogSuccess(fmt.Sprintf("FTP %s %s", target, banner)) return &ScanResult{ Success: true, Service: "ftp", @@ -227,7 +109,6 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo) *ScanResult { } } -// init 自动注册插件 func init() { RegisterPlugin("ftp", func() Plugin { return NewFTPPlugin() diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index 3e82ad4..f7419a9 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -7,81 +7,48 @@ import ( "github.com/IBM/sarama" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// KafkaPlugin Kafka消息队列扫描和利用插件 - 包含信息收集利用功能 type KafkaPlugin struct { name string ports []int } -// NewKafkaPlugin 创建Kafka插件 func NewKafkaPlugin() *KafkaPlugin { return &KafkaPlugin{ name: "kafka", - ports: []int{9092, 9093, 9094}, // Kafka broker端口 + ports: []int{9092, 9093, 9094}, } } -// GetName 实现Plugin接口 func (p *KafkaPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *KafkaPlugin) GetPorts() []int { return p.ports } -// Scan 执行Kafka扫描 - 弱密码检测和未授权访问检测 func (p *KafkaPlugin) 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) } - // 首先检查未授权访问 - if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("kafka_unauthorized_success", target)) - return result - } - - // 生成测试凭据 credentials := GenerateCredentials("kafka") if len(credentials) == 0 { - // Kafka默认凭据 - credentials = []Credential{ - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: ""}, - {Username: "kafka", Password: "kafka"}, - {Username: "user", Password: "user"}, - {Username: "test", Password: "test"}, + return &ScanResult{ + Success: false, + Service: "kafka", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "kafka", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if client := p.testCredential(ctx, info, cred); client != nil { - client.Close() // 关闭测试连接 - - // Kafka认证成功 - common.LogSuccess(i18n.GetText("kafka_scan_success", target, cred.Username, cred.Password)) - + client.Close() + common.LogSuccess(fmt.Sprintf("Kafka %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "kafka", @@ -91,46 +58,24 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "kafka", - Error: fmt.Errorf("未发现弱密码或未授权访问"), + Error: fmt.Errorf("未发现弱密码"), } } -// testUnauthorizedAccess 测试未授权访问 -func (p *KafkaPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult { - // 尝试无认证连接 - emptyCred := Credential{Username: "", Password: ""} - - if client := p.testCredential(ctx, info, emptyCred); client != nil { - client.Close() - return &ScanResult{ - Success: true, - Service: "kafka", - Banner: "未授权访问", - } - } - - return nil -} - -// testCredential 测试单个凭据 - 返回Kafka客户端或nil func (p *KafkaPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) sarama.Client { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second - // 创建Kafka配置 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 - // 如果提供了用户名密码,设置SASL认证 if cred.Username != "" || cred.Password != "" { config.Net.SASL.Enable = true config.Net.SASL.Mechanism = sarama.SASLTypePlaintext @@ -140,97 +85,18 @@ func (p *KafkaPlugin) testCredential(ctx context.Context, info *common.HostInfo, } brokers := []string{target} - - // 使用Context控制超时 - type kafkaResult struct { - client sarama.Client - err error - } - - clientChan := make(chan kafkaResult, 1) - - go func() { - // 尝试创建客户端 - client, err := sarama.NewClient(brokers, config) - select { - case <-ctx.Done(): - if client != nil { - client.Close() - } - case clientChan <- kafkaResult{client, err}: - } - }() - - // 等待客户端创建或超时 - select { - case result := <-clientChan: - if result.err != nil { - return nil - } - return result.client - case <-ctx.Done(): + client, err := sarama.NewClient(brokers, config) + if err != nil { return nil } + return client } -// getConsumerGroups 获取消费者组列表 -func (p *KafkaPlugin) getConsumerGroups(client sarama.Client) ([]string, error) { - // 创建协调器客户端获取消费者组信息 - brokers := client.Brokers() - if len(brokers) == 0 { - return nil, fmt.Errorf("没有可用的broker") - } - broker := brokers[0] // 使用第一个broker - - // 打开broker连接 - if err := broker.Open(client.Config()); err != nil { - return nil, err - } - defer broker.Close() - // 发送ListGroups请求 - request := &sarama.ListGroupsRequest{} - response, err := broker.ListGroups(request) - if err != nil { - return nil, err - } - - groups := make([]string, 0, len(response.Groups)) - for groupId := range response.Groups { - groups = append(groups, groupId) - } - - return groups, nil -} - -// testProduceMessage 测试发送消息 -func (p *KafkaPlugin) testProduceMessage(client sarama.Client, topic string) error { - config := client.Config() - config.Producer.Return.Successes = true - config.Producer.Timeout = 5 * time.Second - - producer, err := sarama.NewSyncProducerFromClient(client) - if err != nil { - return err - } - defer producer.Close() - - // 发送测试消息 - message := &sarama.ProducerMessage{ - Topic: topic, - Value: sarama.StringEncoder("FScan security test message"), - } - - _, _, err = producer.SendMessage(message) - return err -} - -// identifyService 服务识别 - 检测Kafka服务 func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 尝试无认证连接 emptyCred := Credential{Username: "", Password: ""} client := p.testCredential(ctx, info, emptyCred) if client == nil { @@ -242,18 +108,8 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo } defer client.Close() - // 获取集群信息作为banner - var banner string - if err := client.RefreshMetadata(); err == nil { - brokers := client.Brokers() - topics, _ := client.Topics() - banner = fmt.Sprintf("Kafka集群 (Brokers: %d, Topics: %d)", len(brokers), len(topics)) - } else { - banner = "Kafka服务" - } - - common.LogSuccess(i18n.GetText("kafka_service_identified", target, banner)) - + banner := "Kafka" + common.LogSuccess(fmt.Sprintf("Kafka %s %s", target, banner)) return &ScanResult{ Success: true, Service: "kafka", @@ -261,7 +117,6 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo } } -// init 自动注册插件 func init() { RegisterPlugin("kafka", func() Plugin { return NewKafkaPlugin() diff --git a/plugins/services/ldap.go b/plugins/services/ldap.go index 5269f6e..34acb0e 100644 --- a/plugins/services/ldap.go +++ b/plugins/services/ldap.go @@ -3,88 +3,50 @@ package services import ( "context" "fmt" - "strings" ldaplib "github.com/go-ldap/ldap/v3" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// LDAPPlugin LDAP轻量级目录访问协议扫描和利用插件 - 包含目录信息收集利用功能 type LDAPPlugin struct { name string ports []int } -// NewLDAPPlugin 创建LDAP插件 func NewLDAPPlugin() *LDAPPlugin { return &LDAPPlugin{ name: "ldap", - ports: []int{389, 636, 3268, 3269}, // 389: LDAP, 636: LDAPS, 3268/3269: Global Catalog + ports: []int{389, 636, 3268, 3269}, } } -// GetName 实现Plugin接口 func (p *LDAPPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *LDAPPlugin) GetPorts() []int { return p.ports } -// Scan 执行LDAP扫描 - 匿名绑定检测和弱密码检测 func (p *LDAPPlugin) 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) } - // 首先测试匿名绑定 - if result := p.testAnonymousBind(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("ldap_anonymous_success", target)) - return result - } - - // 生成测试凭据 credentials := GenerateCredentials("ldap") if len(credentials) == 0 { - // LDAP默认凭据 - credentials = []Credential{ - {Username: "admin", Password: "admin"}, - {Username: "administrator", Password: "administrator"}, - {Username: "admin", Password: "password"}, - {Username: "admin", Password: "123456"}, - {Username: "ldap", Password: "ldap"}, - {Username: "manager", Password: "manager"}, - {Username: "root", Password: "root"}, - {Username: "bind", Password: "bind"}, - {Username: "guest", Password: "guest"}, - {Username: "test", Password: "test"}, + return &ScanResult{ + Success: false, + Service: "ldap", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "ldap", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // LDAP认证成功 - common.LogSuccess(i18n.GetText("ldap_scan_success", target, cred.Username, cred.Password)) - + common.LogSuccess(fmt.Sprintf("LDAP %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "ldap", @@ -94,36 +56,14 @@ func (p *LDAPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResul } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "ldap", - Error: fmt.Errorf("未发现弱密码或匿名访问"), + Error: fmt.Errorf("未发现弱密码"), } } -// testAnonymousBind 测试匿名绑定 -func (p *LDAPPlugin) testAnonymousBind(ctx context.Context, info *common.HostInfo) *ScanResult { - conn, err := p.connectLDAP(ctx, info, Credential{}) - if err != nil { - return nil - } - defer conn.Close() - - // 尝试匿名绑定 - if err := conn.UnauthenticatedBind(""); err != nil { - return nil - } - - return &ScanResult{ - Success: true, - Service: "ldap", - Banner: "匿名访问", - } -} - -// testCredential 测试单个凭据 func (p *LDAPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { conn, err := p.connectLDAP(ctx, info, cred) if err != nil { @@ -131,213 +71,27 @@ func (p *LDAPPlugin) testCredential(ctx context.Context, info *common.HostInfo, } defer conn.Close() - // 尝试绑定 - bindDNs := p.generateBindDNs(cred.Username) - - for _, bindDN := range bindDNs { - if err := conn.Bind(bindDN, cred.Password); err == nil { - return true - } + // 简单的绑定测试 + if err := conn.Bind(cred.Username, cred.Password); err == nil { + return true } - return false } -// connectLDAP 连接到LDAP服务 func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, creds Credential) (*ldaplib.Conn, error) { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 根据端口选择连接方式 - var conn *ldaplib.Conn - var err error - - if info.Ports == "636" { // LDAPS - conn, err = ldaplib.DialTLS("tcp", target, nil) - } else { - conn, err = ldaplib.Dial("tcp", target) + if info.Ports == "636" { + return ldaplib.DialTLS("tcp", target, nil) } - - if err != nil { - return nil, err - } - - return conn, nil + return ldaplib.Dial("tcp", target) } -// generateBindDNs 生成绑定DN列表 -func (p *LDAPPlugin) generateBindDNs(username string) []string { - return []string{ - fmt.Sprintf("cn=%s,dc=example,dc=com", username), - fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", username), - fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", username), - fmt.Sprintf("uid=%s,dc=example,dc=com", username), - fmt.Sprintf("cn=%s,cn=users,dc=example,dc=com", username), - fmt.Sprintf("sAMAccountName=%s", username), // Active Directory - username, // 直接使用用户名作为DN - } -} -// getRootDSE 获取根DSE信息 -func (p *LDAPPlugin) getRootDSE(conn *ldaplib.Conn) string { - searchRequest := ldaplib.NewSearchRequest( - "", // 根DSE - ldaplib.ScopeBaseObject, - ldaplib.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"*", "+"}, // 请求所有属性 - nil, - ) - sr, err := conn.Search(searchRequest) - if err != nil || len(sr.Entries) == 0 { - return "无法获取根DSE信息" - } - var info strings.Builder - entry := sr.Entries[0] - - // 显示重要属性 - importantAttrs := []string{ - "vendorName", "vendorVersion", "serverName", - "supportedLDAPVersion", "namingContexts", - "defaultNamingContext", "schemaNamingContext", - "configurationNamingContext", - } - - for _, attr := range importantAttrs { - if values := entry.GetAttributeValues(attr); len(values) > 0 { - info.WriteString(fmt.Sprintf("%s: %s\n", attr, strings.Join(values, ", "))) - } - } - return info.String() -} -// getNamingContexts 获取命名上下文 -func (p *LDAPPlugin) getNamingContexts(conn *ldaplib.Conn) []string { - searchRequest := ldaplib.NewSearchRequest( - "", // 根DSE - ldaplib.ScopeBaseObject, - ldaplib.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"namingContexts"}, - nil, - ) - - sr, err := conn.Search(searchRequest) - if err != nil || len(sr.Entries) == 0 { - return nil - } - - return sr.Entries[0].GetAttributeValues("namingContexts") -} - -// enumerateUsers 枚举用户 -func (p *LDAPPlugin) enumerateUsers(conn *ldaplib.Conn) []string { - // 尝试常见的用户搜索基DN - searchBases := []string{ - "dc=example,dc=com", - "ou=users,dc=example,dc=com", - "cn=users,dc=example,dc=com", - "", - } - - var users []string - - for _, baseDN := range searchBases { - searchRequest := ldaplib.NewSearchRequest( - baseDN, - ldaplib.ScopeWholeSubtree, - ldaplib.NeverDerefAliases, - 50, 0, false, // 限制返回50个结果 - "(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))", - []string{"cn", "uid", "sAMAccountName", "mail", "displayName"}, - nil, - ) - - sr, err := conn.Search(searchRequest) - if err != nil { - continue - } - - for _, entry := range sr.Entries { - var userInfo strings.Builder - userInfo.WriteString(fmt.Sprintf("DN: %s", entry.DN)) - - if cn := entry.GetAttributeValue("cn"); cn != "" { - userInfo.WriteString(fmt.Sprintf(", CN: %s", cn)) - } - if uid := entry.GetAttributeValue("uid"); uid != "" { - userInfo.WriteString(fmt.Sprintf(", UID: %s", uid)) - } - if sam := entry.GetAttributeValue("sAMAccountName"); sam != "" { - userInfo.WriteString(fmt.Sprintf(", SAM: %s", sam)) - } - if mail := entry.GetAttributeValue("mail"); mail != "" { - userInfo.WriteString(fmt.Sprintf(", Mail: %s", mail)) - } - - users = append(users, userInfo.String()) - } - - if len(users) > 0 { - break // 找到用户就停止搜索其他基DN - } - } - - return users -} - -// enumerateOUs 枚举组织单位 -func (p *LDAPPlugin) enumerateOUs(conn *ldaplib.Conn) []string { - searchBases := []string{ - "dc=example,dc=com", - "", - } - - var ous []string - - for _, baseDN := range searchBases { - searchRequest := ldaplib.NewSearchRequest( - baseDN, - ldaplib.ScopeWholeSubtree, - ldaplib.NeverDerefAliases, - 30, 0, false, // 限制返回30个结果 - "(objectClass=organizationalUnit)", - []string{"ou", "description"}, - nil, - ) - - sr, err := conn.Search(searchRequest) - if err != nil { - continue - } - - for _, entry := range sr.Entries { - var ouInfo strings.Builder - ouInfo.WriteString(fmt.Sprintf("DN: %s", entry.DN)) - - if ou := entry.GetAttributeValue("ou"); ou != "" { - ouInfo.WriteString(fmt.Sprintf(", OU: %s", ou)) - } - if desc := entry.GetAttributeValue("description"); desc != "" { - ouInfo.WriteString(fmt.Sprintf(", Desc: %s", desc)) - } - - ous = append(ous, ouInfo.String()) - } - - if len(ous) > 0 { - break // 找到OU就停止搜索其他基DN - } - } - - return ous -} - -// identifyService 服务识别 - 检测LDAP服务 func (p *LDAPPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -351,40 +105,8 @@ func (p *LDAPPlugin) identifyService(ctx context.Context, info *common.HostInfo) } defer conn.Close() - // 尝试获取根DSE信息来确认这是LDAP服务 - searchRequest := ldaplib.NewSearchRequest( - "", // 根DSE - ldaplib.ScopeBaseObject, - ldaplib.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"vendorName", "vendorVersion"}, - nil, - ) - - sr, err := conn.Search(searchRequest) - var banner string - - if err != nil { - banner = "LDAP目录服务" - } else if len(sr.Entries) > 0 { - entry := sr.Entries[0] - vendor := entry.GetAttributeValue("vendorName") - version := entry.GetAttributeValue("vendorVersion") - - if vendor != "" && version != "" { - banner = fmt.Sprintf("LDAP目录服务 (%s %s)", vendor, version) - } else if vendor != "" { - banner = fmt.Sprintf("LDAP目录服务 (%s)", vendor) - } else { - banner = "LDAP目录服务" - } - } else { - banner = "LDAP目录服务" - } - - common.LogSuccess(i18n.GetText("ldap_service_identified", target, banner)) - + banner := "LDAP" + common.LogSuccess(fmt.Sprintf("LDAP %s %s", target, banner)) return &ScanResult{ Success: true, Service: "ldap", @@ -392,7 +114,6 @@ func (p *LDAPPlugin) identifyService(ctx context.Context, info *common.HostInfo) } } -// init 自动注册插件 func init() { RegisterPlugin("ldap", func() Plugin { return NewLDAPPlugin() diff --git a/plugins/services/memcached.go b/plugins/services/memcached.go index 816b614..473a4db 100644 --- a/plugins/services/memcached.go +++ b/plugins/services/memcached.go @@ -8,49 +8,54 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// MemcachedPlugin Memcached分布式缓存系统扫描和利用插件 - 包含缓存数据提取利用功能 type MemcachedPlugin struct { name string ports []int } -// NewMemcachedPlugin 创建Memcached插件 func NewMemcachedPlugin() *MemcachedPlugin { return &MemcachedPlugin{ name: "memcached", - ports: []int{11211, 11212, 11213}, // Memcached端口 + ports: []int{11211, 11212, 11213}, } } -// GetName 实现Plugin接口 func (p *MemcachedPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *MemcachedPlugin) GetPorts() []int { return p.ports } -// Scan 执行Memcached扫描 - 未授权访问检测 func (p *MemcachedPlugin) 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) } - // Memcached主要检查未授权访问 - if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("memcached_unauth_success", target)) - return result + conn := p.connectToMemcached(ctx, info) + if conn == nil { + return &ScanResult{ + Success: false, + Service: "memcached", + Error: fmt.Errorf("Memcached服务连接失败"), + } + } + defer conn.Close() + + if p.testBasicCommand(conn) { + common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", target)) + return &ScanResult{ + Success: true, + Service: "memcached", + Banner: "未授权访问", + } } - // 未授权访问失败 return &ScanResult{ Success: false, Service: "memcached", @@ -59,27 +64,6 @@ func (p *MemcachedPlugin) Scan(ctx context.Context, info *common.HostInfo) *Scan } -// testUnauthorizedAccess 测试未授权访问 -func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult { - conn := p.connectToMemcached(ctx, info) - if conn == nil { - return nil - } - defer conn.Close() - - // 尝试执行stats命令测试 - if p.testBasicCommand(conn) { - return &ScanResult{ - Success: true, - Service: "memcached", - Banner: "未授权访问", - } - } - - return nil -} - -// connectToMemcached 连接到Memcached服务 func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo) net.Conn { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second @@ -89,23 +73,19 @@ func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.H return nil } - // 设置操作超时 conn.SetDeadline(time.Now().Add(timeout)) return conn } -// testBasicCommand 测试基本命令 func (p *MemcachedPlugin) testBasicCommand(conn net.Conn) bool { timeout := time.Duration(common.Timeout) * time.Second - // 发送version命令 conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte("version\r\n")); err != nil { return false } - // 读取响应 conn.SetReadDeadline(time.Now().Add(timeout)) response := make([]byte, 1024) n, err := conn.Read(response) @@ -117,199 +97,13 @@ func (p *MemcachedPlugin) testBasicCommand(conn net.Conn) bool { return strings.Contains(responseStr, "VERSION") || strings.Contains(responseStr, "memcached") } -// sendCommand 发送Memcached命令 -func (p *MemcachedPlugin) sendCommand(conn net.Conn, command string) string { - timeout := time.Duration(common.Timeout) * time.Second - - conn.SetWriteDeadline(time.Now().Add(timeout)) - if _, err := conn.Write([]byte(command + "\r\n")); err != nil { - return "" - } - conn.SetReadDeadline(time.Now().Add(timeout)) - response := make([]byte, 4096) - n, err := conn.Read(response) - if err != nil { - return "" - } - return strings.TrimSpace(string(response[:n])) -} -// getStats 获取统计信息 -func (p *MemcachedPlugin) getStats(conn net.Conn) string { - response := p.sendCommand(conn, "stats") - if response == "" { - return "" - } - lines := strings.Split(response, "\n") - var stats strings.Builder - - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "STAT") { - parts := strings.Fields(line) - if len(parts) >= 3 { - key := parts[1] - value := parts[2] - - // 只显示重要的统计信息 - if key == "version" || key == "uptime" || key == "curr_items" || - key == "total_items" || key == "bytes" || key == "curr_connections" || - key == "cmd_get" || key == "cmd_set" || key == "get_hits" || key == "get_misses" { - stats.WriteString(fmt.Sprintf("%s: %s\n", key, value)) - } - } - } - } - return stats.String() -} -// getVersion 获取版本信息 -func (p *MemcachedPlugin) getVersion(conn net.Conn) string { - response := p.sendCommand(conn, "version") - if strings.HasPrefix(response, "VERSION") { - return strings.TrimPrefix(response, "VERSION ") - } - return "" -} -// getAllKeys 获取所有键(通过stats cachedump) -func (p *MemcachedPlugin) getAllKeys(conn net.Conn) []string { - var keys []string - - // 首先获取slabs信息 - slabsResponse := p.sendCommand(conn, "stats slabs") - if slabsResponse == "" { - return keys - } - - // 解析slab ID - slabIDs := make(map[string]bool) - lines := strings.Split(slabsResponse, "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "STAT") { - parts := strings.Fields(line) - if len(parts) >= 2 { - statName := parts[1] - // 格式如: "1:chunk_size" - if strings.Contains(statName, ":") { - slabID := strings.Split(statName, ":")[0] - slabIDs[slabID] = true - } - } - } - } - - // 对每个slab ID执行cachedump - for slabID := range slabIDs { - if len(keys) >= 50 { // 限制键的数量 - break - } - - dumpCmd := fmt.Sprintf("stats cachedump %s 50", slabID) - dumpResponse := p.sendCommand(conn, dumpCmd) - - dumpLines := strings.Split(dumpResponse, "\n") - for _, line := range dumpLines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "ITEM") { - parts := strings.Fields(line) - if len(parts) >= 2 { - key := parts[1] - keys = append(keys, key) - } - } - } - } - - return keys -} - -// getValue 获取键的值 -func (p *MemcachedPlugin) getValue(conn net.Conn, key string) string { - getCmd := fmt.Sprintf("get %s", key) - response := p.sendCommand(conn, getCmd) - - lines := strings.Split(response, "\n") - for i, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "VALUE") { - // VALUE行的下一行是实际值 - if i+1 < len(lines) { - value := strings.TrimSpace(lines[i+1]) - if value != "END" { - return value - } - } - } - } - - return "[无法获取]" -} - -// getSettings 获取设置信息 -func (p *MemcachedPlugin) getSettings(conn net.Conn) string { - response := p.sendCommand(conn, "stats settings") - if response == "" { - return "" - } - - lines := strings.Split(response, "\n") - var settings strings.Builder - - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "STAT") { - parts := strings.Fields(line) - if len(parts) >= 3 { - key := parts[1] - value := parts[2] - - // 只显示重要的设置 - if key == "maxbytes" || key == "maxconns" || key == "growth_factor" || - key == "chunk_size" || key == "num_threads" || key == "cas_enabled" { - settings.WriteString(fmt.Sprintf("%s: %s\n", key, value)) - } - } - } - } - - return settings.String() -} - -// testWritePermission 测试写权限 -func (p *MemcachedPlugin) testWritePermission(conn net.Conn) string { - // 尝试设置一个测试键 - setCmd := "set fscan_test 0 60 11\r\nfscan_test\r\n" - - timeout := time.Duration(common.Timeout) * time.Second - conn.SetWriteDeadline(time.Now().Add(timeout)) - if _, err := conn.Write([]byte(setCmd)); err != nil { - return "❌ 无写权限: " + err.Error() - } - - conn.SetReadDeadline(time.Now().Add(timeout)) - response := make([]byte, 512) - n, err := conn.Read(response) - if err != nil { - return "❌ 写入测试失败: " + err.Error() - } - - responseStr := strings.TrimSpace(string(response[:n])) - if responseStr == "STORED" { - // 删除测试键 - p.sendCommand(conn, "delete fscan_test") - return "✅ 具有读写权限" - } - - return "❌ 写入失败: " + responseStr -} - -// identifyService 服务识别 - 检测Memcached服务 func (p *MemcachedPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -323,32 +117,23 @@ func (p *MemcachedPlugin) identifyService(ctx context.Context, info *common.Host } defer conn.Close() - // 尝试识别Memcached协议 - version := p.getVersion(conn) - var banner string - - if version != "" { - banner = fmt.Sprintf("Memcached服务 (版本: %s)", version) - } else if p.testBasicCommand(conn) { - banner = "Memcached分布式缓存服务" - } else { + banner := "Memcached" + if p.testBasicCommand(conn) { + common.LogSuccess(fmt.Sprintf("Memcached %s %s", target, banner)) return &ScanResult{ - Success: false, + Success: true, Service: "memcached", - Error: fmt.Errorf("无法识别为Memcached服务"), + Banner: banner, } } - common.LogSuccess(i18n.GetText("memcached_service_identified", target, banner)) - return &ScanResult{ - Success: true, + Success: false, Service: "memcached", - Banner: banner, + Error: fmt.Errorf("无法识别为Memcached服务"), } } -// init 自动注册插件 func init() { RegisterPlugin("memcached", func() Plugin { return NewMemcachedPlugin() diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index c606e66..ba726a3 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -3,453 +3,160 @@ package services import ( "context" "fmt" - "io" "net" "strings" "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// MongoDBPlugin MongoDB数据库扫描和利用插件 - 包含数据查询利用功能 type MongoDBPlugin struct { name string ports []int } -// NewMongoDBPlugin 创建MongoDB插件 func NewMongoDBPlugin() *MongoDBPlugin { return &MongoDBPlugin{ name: "mongodb", - ports: []int{27017, 27018, 27019}, // MongoDB端口 + ports: []int{27017, 27018, 27019}, } } -// GetName 实现Plugin接口 func (p *MongoDBPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *MongoDBPlugin) GetPorts() []int { return p.ports } -// Scan 执行MongoDB扫描 - 未授权访问检测 func (p *MongoDBPlugin) 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) } - // MongoDB主要检查未授权访问 - if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("mongodb_unauth_success", target)) - return result - } - - // 未授权访问失败 - return &ScanResult{ - Success: false, - Service: "mongodb", - Error: fmt.Errorf("MongoDB需要认证或连接失败"), - } -} - - -// testUnauthorizedAccess 测试未授权访问 -func (p *MongoDBPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult { - conn := p.connectToMongoDB(ctx, info) - if conn == nil { - return nil - } - defer conn.Close() - - // 尝试执行基本查询测试 - if p.testBasicQuery(conn) { + credentials := GenerateCredentials("mongodb") + if len(credentials) == 0 { return &ScanResult{ - Success: true, + Success: false, Service: "mongodb", - Banner: "未授权访问", + Error: fmt.Errorf("没有可用的测试凭据"), } } - return nil + for _, cred := range credentials { + if p.testCredential(ctx, info, cred) { + common.LogSuccess(fmt.Sprintf("MongoDB %s %s:%s", target, cred.Username, cred.Password)) + return &ScanResult{ + Success: true, + Service: "mongodb", + Username: cred.Username, + Password: cred.Password, + } + } + } + + return &ScanResult{ + Success: false, + Service: "mongodb", + Error: fmt.Errorf("未发现弱密码"), + } } -// connectToMongoDB 连接到MongoDB服务 -func (p *MongoDBPlugin) connectToMongoDB(ctx context.Context, info *common.HostInfo) net.Conn { + + +func (p *MongoDBPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second conn, err := net.DialTimeout("tcp", target, timeout) if err != nil { - return nil + return false } + defer conn.Close() - // 设置操作超时 conn.SetDeadline(time.Now().Add(timeout)) - - return conn + return p.testBasicQuery(conn) } -// testBasicQuery 测试基本查询 func (p *MongoDBPlugin) testBasicQuery(conn net.Conn) bool { - // 创建MongoDB查询消息 queryMsg := p.createListDatabasesQuery() if _, err := conn.Write(queryMsg); err != nil { return false } - // 读取响应 - response := make([]byte, 4096) + response := make([]byte, 1024) n, err := conn.Read(response) - if err != nil && err != io.EOF { + if err != nil { return false } - // 检查响应是否有效 return n > 36 && p.isValidMongoResponse(response[:n]) } -// isValidMongoResponse 检查是否为有效的MongoDB响应 func (p *MongoDBPlugin) isValidMongoResponse(data []byte) bool { if len(data) < 36 { return false } - // 检查MongoDB协议标志 responseStr := string(data) return strings.Contains(responseStr, "databases") || strings.Contains(responseStr, "totalSize") || - strings.Contains(responseStr, "name") || - strings.Contains(responseStr, "sizeOnDisk") + strings.Contains(responseStr, "name") } -// getServerStatus 获取服务器状态 -func (p *MongoDBPlugin) getServerStatus(conn net.Conn) string { - // 创建serverStatus命令 - statusMsg := p.createServerStatusQuery() - - if _, err := conn.Write(statusMsg); err != nil { - return "" - } - response := make([]byte, 2048) - n, err := conn.Read(response) - if err != nil && err != io.EOF { - return "" - } - responseStr := string(response[:n]) - - var status strings.Builder - if strings.Contains(responseStr, "version") { - status.WriteString("MongoDB服务运行中\n") - } - if strings.Contains(responseStr, "uptime") { - status.WriteString("服务器运行正常\n") - } - - return status.String() -} -// getDatabases 获取数据库列表 -func (p *MongoDBPlugin) getDatabases(conn net.Conn) []string { - // 创建listDatabases查询 - queryMsg := p.createListDatabasesQuery() - - if _, err := conn.Write(queryMsg); err != nil { - return nil - } - response := make([]byte, 4096) - n, err := conn.Read(response) - if err != nil && err != io.EOF { - return nil - } - responseStr := string(response[:n]) - - // 解析数据库名称 - var databases []string - if strings.Contains(responseStr, "admin") { - databases = append(databases, "admin") - } - if strings.Contains(responseStr, "local") { - databases = append(databases, "local") - } - if strings.Contains(responseStr, "config") { - databases = append(databases, "config") - } - - return databases -} - -// getCollections 获取指定数据库的集合列表 -func (p *MongoDBPlugin) getCollections(conn net.Conn, database string) []string { - // 创建listCollections查询 - queryMsg := p.createListCollectionsQuery(database) - - if _, err := conn.Write(queryMsg); err != nil { - return nil - } - - response := make([]byte, 2048) - n, err := conn.Read(response) - if err != nil && err != io.EOF { - return nil - } - - responseStr := string(response[:n]) - - // 解析集合名称 - var collections []string - if strings.Contains(responseStr, "system") { - collections = append(collections, "system.users") - collections = append(collections, "system.roles") - } - - return collections -} - -// getUsers 获取用户信息 -func (p *MongoDBPlugin) getUsers(conn net.Conn) string { - // 尝试查询admin.system.users - userQuery := p.createFindUsersQuery() - - if _, err := conn.Write(userQuery); err != nil { - return "" - } - - response := make([]byte, 1024) - n, err := conn.Read(response) - if err != nil && err != io.EOF { - return "" - } - - responseStr := string(response[:n]) - - if strings.Contains(responseStr, "user") || strings.Contains(responseStr, "role") { - return "发现用户数据" - } - - return "无用户信息或权限不足" -} - -// getBuildInfo 获取版本信息 -func (p *MongoDBPlugin) getBuildInfo(conn net.Conn) string { - buildInfoMsg := p.createBuildInfoQuery() - - if _, err := conn.Write(buildInfoMsg); err != nil { - return "" - } - - response := make([]byte, 1024) - n, err := conn.Read(response) - if err != nil && err != io.EOF { - return "" - } - - responseStr := string(response[:n]) - - if strings.Contains(responseStr, "version") { - return "MongoDB服务器信息可用" - } - - return "" -} - -// createListDatabasesQuery 创建listDatabases查询 func (p *MongoDBPlugin) createListDatabasesQuery() []byte { - // 简化的MongoDB Wire Protocol消息 - // OP_QUERY消息结构 - query := make([]byte, 100) + query := make([]byte, 58) - // 消息头 (16字节) - query[0] = 0x64 // 消息长度 - query[4] = 0x01 // 请求ID - query[12] = 0x04 // OP_QUERY操作码 - query[13] = 0x20 - query[14] = 0x00 - query[15] = 0x00 - - // 查询标志 - query[16] = 0x00 - query[17] = 0x00 - query[18] = 0x00 - query[19] = 0x00 - - // 集合名称 "admin.$cmd" - copy(query[20:], "admin.$cmd\x00") - - // BSON查询文档 {listDatabases: 1} - bsonQuery := []byte{ - 0x1A, 0x00, 0x00, 0x00, // 文档长度 - 0x10, // int32类型 - 0x6C, 0x69, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x00, // "listDatabases" - 0x01, 0x00, 0x00, 0x00, // 值为1 - 0x00, // 文档结束 - } - - copy(query[32:], bsonQuery) - return query[:58] -} - -// createServerStatusQuery 创建serverStatus查询 -func (p *MongoDBPlugin) createServerStatusQuery() []byte { - query := make([]byte, 100) - - // 消息头 - query[0] = 0x60 - query[4] = 0x02 + query[0] = 0x3A + query[4] = 0x01 query[12] = 0x04 query[13] = 0x20 - query[14] = 0x00 - query[15] = 0x00 - // 查询标志 - query[16] = 0x00 - query[17] = 0x00 - query[18] = 0x00 - query[19] = 0x00 - - // 集合名称 copy(query[20:], "admin.$cmd\x00") - // BSON查询文档 {serverStatus: 1} bsonQuery := []byte{ - 0x18, 0x00, 0x00, 0x00, + 0x1A, 0x00, 0x00, 0x00, 0x10, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x00, + 0x6C, 0x69, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x73, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, } copy(query[32:], bsonQuery) - return query[:56] + return query } -// createListCollectionsQuery 创建listCollections查询 -func (p *MongoDBPlugin) createListCollectionsQuery(database string) []byte { - query := make([]byte, 100) - - // 消息头 - query[0] = 0x70 - query[4] = 0x03 - query[12] = 0x04 - query[13] = 0x20 - query[14] = 0x00 - query[15] = 0x00 - - // 查询标志 - query[16] = 0x00 - query[17] = 0x00 - query[18] = 0x00 - query[19] = 0x00 - - // 集合名称 - collectionName := database + ".$cmd\x00" - copy(query[20:], collectionName) - - // BSON查询文档 {listCollections: 1} - bsonQuery := []byte{ - 0x1C, 0x00, 0x00, 0x00, - 0x10, - 0x6C, 0x69, 0x73, 0x74, 0x43, 0x6F, 0x6C, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x00, - } - - copy(query[20+len(collectionName):], bsonQuery) - return query[:46+len(collectionName)] -} -// createFindUsersQuery 创建查找用户查询 -func (p *MongoDBPlugin) createFindUsersQuery() []byte { - query := make([]byte, 120) - - // 消息头 - query[0] = 0x78 - query[4] = 0x04 - query[12] = 0x04 - query[13] = 0x20 - query[14] = 0x00 - query[15] = 0x00 - - // 查询标志 - query[16] = 0x00 - query[17] = 0x00 - query[18] = 0x00 - query[19] = 0x00 - - // 集合名称 "admin.system.users" - copy(query[20:], "admin.system.users\x00") - - // 空的查询文档 {} - bsonQuery := []byte{0x05, 0x00, 0x00, 0x00, 0x00} - - copy(query[39:], bsonQuery) - return query[:44] -} -// createBuildInfoQuery 创建buildInfo查询 -func (p *MongoDBPlugin) createBuildInfoQuery() []byte { - query := make([]byte, 100) - - // 消息头 - query[0] = 0x5C - query[4] = 0x05 - query[12] = 0x04 - query[13] = 0x20 - query[14] = 0x00 - query[15] = 0x00 - - // 查询标志 - query[16] = 0x00 - query[17] = 0x00 - query[18] = 0x00 - query[19] = 0x00 - - // 集合名称 - copy(query[20:], "admin.$cmd\x00") - - // BSON查询文档 {buildInfo: 1} - bsonQuery := []byte{ - 0x15, 0x00, 0x00, 0x00, - 0x10, - 0x62, 0x75, 0x69, 0x6C, 0x64, 0x49, 0x6E, 0x66, 0x6F, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x00, - } - - copy(query[32:], bsonQuery) - return query[:53] -} -// identifyService 服务识别 - 检测MongoDB服务 + func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + timeout := time.Duration(common.Timeout) * time.Second - conn := p.connectToMongoDB(ctx, info) - if conn == nil { + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { return &ScanResult{ Success: false, Service: "mongodb", - Error: fmt.Errorf("无法连接到MongoDB服务"), + Error: err, } } defer conn.Close() - // 尝试识别MongoDB协议 + conn.SetDeadline(time.Now().Add(timeout)) if p.testBasicQuery(conn) { - banner := "MongoDB数据库服务" - common.LogSuccess(i18n.GetText("mongodb_service_identified", target, banner)) - + banner := "MongoDB" + common.LogSuccess(fmt.Sprintf("MongoDB %s %s", target, banner)) return &ScanResult{ Success: true, Service: "mongodb", @@ -464,7 +171,6 @@ func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostIn } } -// init 自动注册插件 func init() { RegisterPlugin("mongodb", func() Plugin { return NewMongoDBPlugin() diff --git a/plugins/services/mssql.go b/plugins/services/mssql.go index bff1622..89545b4 100644 --- a/plugins/services/mssql.go +++ b/plugins/services/mssql.go @@ -9,77 +9,49 @@ import ( _ "github.com/denisenkom/go-mssqldb" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// MSSQLPlugin Microsoft SQL Server数据库扫描和利用插件 - 包含数据库查询利用功能 type MSSQLPlugin struct { name string ports []int } -// NewMSSQLPlugin 创建MSSQL插件 func NewMSSQLPlugin() *MSSQLPlugin { return &MSSQLPlugin{ name: "mssql", - ports: []int{1433, 1434}, // MSSQL端口 + ports: []int{1433, 1434}, } } -// GetName 实现Plugin接口 func (p *MSSQLPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *MSSQLPlugin) GetPorts() []int { return p.ports } -// Scan 执行MSSQL扫描 - 弱密码检测 func (p *MSSQLPlugin) 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) } - // 生成测试凭据 credentials := GenerateCredentials("mssql") if len(credentials) == 0 { - // MSSQL默认凭据 - credentials = []Credential{ - {Username: "sa", Password: ""}, - {Username: "sa", Password: "sa"}, - {Username: "sa", Password: "password"}, - {Username: "sa", Password: "admin"}, - {Username: "sa", Password: "123456"}, - {Username: "administrator", Password: "administrator"}, - {Username: "admin", Password: "admin"}, - {Username: "mssql", Password: "mssql"}, + return &ScanResult{ + Success: false, + Service: "mssql", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "mssql", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if db := p.testCredential(ctx, info, cred); db != nil { - db.Close() // 关闭测试连接 + db.Close() - // MSSQL认证成功 - common.LogSuccess(i18n.GetText("mssql_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("MSSQL %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -90,7 +62,6 @@ func (p *MSSQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "mssql", @@ -99,28 +70,22 @@ func (p *MSSQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu } -// testCredential 测试单个凭据 - 返回数据库连接或nil func (p *MSSQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *sql.DB { - // 构建连接字符串 connStr := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=master;connection timeout=%d", info.Host, cred.Username, cred.Password, info.Ports, common.Timeout) - // 打开数据库连接 db, err := sql.Open("mssql", connStr) if err != nil { return nil } - // 设置连接参数 db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) - // 创建超时上下文 pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second) defer cancel() - // 测试连接 err = db.PingContext(pingCtx) if err != nil { db.Close() @@ -130,146 +95,15 @@ func (p *MSSQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, return db } -// getVersion 获取MSSQL版本信息 -func (p *MSSQLPlugin) getVersion(db *sql.DB) string { - var version string - err := db.QueryRow("SELECT @@VERSION").Scan(&version) - if err != nil { - return "" - } - - // 只返回第一行版本信息 - lines := strings.Split(version, "\n") - if len(lines) > 0 { - return strings.TrimSpace(lines[0]) - } - - return version -} -// getDatabases 获取数据库列表 -func (p *MSSQLPlugin) getDatabases(db *sql.DB) []string { - query := "SELECT name FROM sys.databases WHERE database_id > 4" // 排除系统数据库 - rows, err := db.Query(query) - if err != nil { - // 如果查询失败,尝试查询所有数据库 - rows, err = db.Query("SELECT name FROM sys.databases") - if err != nil { - return nil - } - } - defer rows.Close() - var databases []string - for rows.Next() { - var dbName string - if err := rows.Scan(&dbName); err == nil { - databases = append(databases, dbName) - } - } - return databases -} -// getTables 获取指定数据库的表列表 -func (p *MSSQLPlugin) getTables(db *sql.DB, database string) []string { - query := fmt.Sprintf("SELECT TABLE_NAME FROM %s.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", database) - rows, err := db.Query(query) - if err != nil { - return nil - } - defer rows.Close() - var tables []string - for rows.Next() { - var tableName string - if err := rows.Scan(&tableName); err == nil { - tables = append(tables, tableName) - } - } - return tables -} - -// getUsers 获取用户列表 -func (p *MSSQLPlugin) getUsers(db *sql.DB) []string { - query := "SELECT name FROM sys.server_principals WHERE type IN ('S', 'U') AND is_disabled = 0" - rows, err := db.Query(query) - if err != nil { - // 尝试备用查询 - rows, err = db.Query("SELECT loginname FROM master.dbo.syslogins") - if err != nil { - return nil - } - } - defer rows.Close() - - var users []string - for rows.Next() { - var userName string - if err := rows.Scan(&userName); err == nil { - users = append(users, userName) - } - } - - return users -} - -// getPrivileges 获取用户权限信息 -func (p *MSSQLPlugin) getPrivileges(db *sql.DB, username string) string { - var privileges strings.Builder - - // 检查是否为sysadmin - var isSysadmin int - err := db.QueryRow("SELECT IS_SRVROLEMEMBER('sysadmin', ?)", username).Scan(&isSysadmin) - if err == nil { - if isSysadmin == 1 { - privileges.WriteString("sysadmin权限: YES\n") - } else { - privileges.WriteString("sysadmin权限: NO\n") - } - } - - // 检查其他服务器角色 - serverRoles := []string{"securityadmin", "serveradmin", "setupadmin", "processadmin", "diskadmin", "dbcreator", "bulkadmin"} - for _, role := range serverRoles { - var hasRole int - err := db.QueryRow(fmt.Sprintf("SELECT IS_SRVROLEMEMBER('%s', ?)", role), username).Scan(&hasRole) - if err == nil && hasRole == 1 { - privileges.WriteString(fmt.Sprintf("%s权限: YES\n", role)) - } - } - - return privileges.String() -} - -// checkXpCmdshell 检查xp_cmdshell状态 -func (p *MSSQLPlugin) checkXpCmdshell(db *sql.DB) string { - // 检查xp_cmdshell是否启用 - var configValue int - err := db.QueryRow("SELECT CAST(value as int) FROM sys.configurations WHERE name = 'xp_cmdshell'").Scan(&configValue) - if err != nil { - return "无法检查xp_cmdshell状态" - } - - if configValue == 1 { - // 尝试执行一个简单的命令测试 - var result sql.NullString - err = db.QueryRow("EXEC xp_cmdshell 'echo test'").Scan(&result) - if err == nil && result.Valid { - return "✅ xp_cmdshell已启用,支持系统命令执行" - } - return "⚠️ xp_cmdshell已启用但无法执行命令" - } - - return "❌ xp_cmdshell未启用" -} - -// identifyService 服务识别 - 检测MSSQL服务 func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 尝试连接MSSQL服务 connStr := fmt.Sprintf("server=%s;user id=invalid;password=invalid;port=%s;database=master;connection timeout=%d", info.Host, info.Ports, common.Timeout) @@ -283,7 +117,6 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo } defer db.Close() - // 尝试连接(即使认证失败,也能识别服务) pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second) defer cancel() @@ -293,9 +126,9 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo if err != nil && (strings.Contains(strings.ToLower(err.Error()), "login failed") || strings.Contains(strings.ToLower(err.Error()), "mssql") || strings.Contains(strings.ToLower(err.Error()), "sql server")) { - banner = "Microsoft SQL Server数据库服务" + banner = "MSSQL" } else if err == nil { - banner = "Microsoft SQL Server (连接成功)" + banner = "MSSQL" } else { return &ScanResult{ Success: false, @@ -304,7 +137,7 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo } } - common.LogSuccess(i18n.GetText("mssql_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("MSSQL %s %s", target, banner)) return &ScanResult{ Success: true, @@ -313,7 +146,6 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo } } -// init 自动注册插件 func init() { RegisterPlugin("mssql", func() Plugin { return NewMSSQLPlugin() diff --git a/plugins/services/mysql.go b/plugins/services/mysql.go index 7b85825..a0aab27 100644 --- a/plugins/services/mysql.go +++ b/plugins/services/mysql.go @@ -5,21 +5,17 @@ import ( "database/sql" "fmt" "net" - "regexp" "time" _ "github.com/go-sql-driver/mysql" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// MySQLPlugin MySQL数据库弱密码扫描插件 - 单文件实现 type MySQLPlugin struct { name string ports []int } -// NewMySQLPlugin 创建MySQL插件 func NewMySQLPlugin() *MySQLPlugin { return &MySQLPlugin{ name: "mysql", @@ -27,26 +23,21 @@ func NewMySQLPlugin() *MySQLPlugin { } } -// GetName 实现Plugin接口 func (p *MySQLPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *MySQLPlugin) GetPorts() []int { return p.ports } -// Scan 执行MySQL扫描 - 核心功能实现 func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 如果禁用暴力破解,只做服务识别 if common.DisableBrute { return p.identifyService(info) } - // 生成测试凭据 credentials := GenerateCredentials("mysql") if len(credentials) == 0 { return &ScanResult{ @@ -56,23 +47,9 @@ func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "mysql", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // 弱密码发现成功 - common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("MySQL %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -83,7 +60,6 @@ func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "mysql", @@ -91,48 +67,28 @@ func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu } } -// testCredential 测试单个凭据 - 核心认证逻辑 func (p *MySQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { - // 构建连接字符串 - connStr := p.buildConnectionString(info.Host, info.Ports, cred.Username, cred.Password) + connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/mysql?charset=utf8&timeout=%ds", + cred.Username, cred.Password, info.Host, info.Ports, common.Timeout) - // 创建数据库连接 db, err := sql.Open("mysql", connStr) if err != nil { return false } defer db.Close() - // 设置连接超时 db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) - // 测试连接 - 使用Context超时控制 err = db.PingContext(ctx) return err == nil } -// buildConnectionString 构建MySQL连接字符串 -func (p *MySQLPlugin) buildConnectionString(host, port, username, password string) string { - // 支持SOCKS代理 - if common.Socks5Proxy != "" { - // 如果使用代理,需要注册自定义拨号器 - p.registerProxyDialer() - return fmt.Sprintf("%s:%s@tcp-proxy(%s:%s)/mysql?charset=utf8&timeout=%ds", - username, password, host, port, common.Timeout) - } - // 标准TCP连接 - return fmt.Sprintf("%s:%s@tcp(%s:%s)/mysql?charset=utf8&timeout=%ds", - username, password, host, port, common.Timeout) -} - -// identifyService 服务识别 - 不进行暴力破解时的功能 func (p *MySQLPlugin) identifyService(info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 尝试连接获取握手包 conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return &ScanResult{ @@ -143,9 +99,8 @@ func (p *MySQLPlugin) identifyService(info *common.HostInfo) *ScanResult { } defer conn.Close() - // 读取MySQL握手包 if banner := p.readMySQLBanner(conn); banner != "" { - common.LogSuccess(i18n.GetText("mysql_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("MySQL %s %s", target, banner)) return &ScanResult{ Success: true, Service: "mysql", @@ -160,24 +115,19 @@ func (p *MySQLPlugin) identifyService(info *common.HostInfo) *ScanResult { } } -// readMySQLBanner 读取MySQL服务器握手包 func (p *MySQLPlugin) readMySQLBanner(conn net.Conn) string { - // 设置读取超时 conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - // 读取握手包 - handshake := make([]byte, 512) + handshake := make([]byte, 256) n, err := conn.Read(handshake) if err != nil || n < 10 { return "" } - // 检查MySQL协议版本(通常是10) if handshake[4] != 10 { return "" } - // 提取版本字符串 versionStart := 5 versionEnd := versionStart for versionEnd < n && handshake[versionEnd] != 0 { @@ -189,22 +139,10 @@ func (p *MySQLPlugin) readMySQLBanner(conn net.Conn) string { } versionStr := string(handshake[versionStart:versionEnd]) - - // 验证版本字符串格式 - if regexp.MustCompile(`\d+\.\d+`).MatchString(versionStr) { - return fmt.Sprintf("MySQL %s", versionStr) - } - - return "" + return fmt.Sprintf("MySQL %s", versionStr) } -// registerProxyDialer 注册SOCKS代理拨号器 -func (p *MySQLPlugin) registerProxyDialer() { - // TODO: 实现代理拨号器注册 - // 这里简化处理,实际需要注册到MySQL驱动 -} -// init 自动注册插件 func init() { RegisterPlugin("mysql", func() Plugin { return NewMySQLPlugin() diff --git a/plugins/services/neo4j.go b/plugins/services/neo4j.go index 5ab98e6..34e5fc1 100644 --- a/plugins/services/neo4j.go +++ b/plugins/services/neo4j.go @@ -2,7 +2,6 @@ package services import ( "context" - "encoding/json" "fmt" "io" "net/http" @@ -10,79 +9,52 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// Neo4jPlugin Neo4j图数据库扫描和利用插件 - 包含图数据查询利用功能 type Neo4jPlugin struct { name string ports []int } -// NewNeo4jPlugin 创建Neo4j插件 func NewNeo4jPlugin() *Neo4jPlugin { return &Neo4jPlugin{ name: "neo4j", - ports: []int{7474, 7687, 7473}, // Neo4j HTTP、Bolt、HTTPS端口 + ports: []int{7474, 7687, 7473}, } } -// GetName 实现Plugin接口 func (p *Neo4jPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *Neo4jPlugin) GetPorts() []int { return p.ports } -// Scan 执行Neo4j扫描 - 未授权访问和弱密码检测 func (p *Neo4jPlugin) 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) } - // 首先检查未授权访问 if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("neo4j_unauth_success", target)) + common.LogSuccess(fmt.Sprintf("Neo4j %s 未授权访问", target)) return result } - // 生成测试凭据 credentials := GenerateCredentials("neo4j") if len(credentials) == 0 { - // Neo4j默认凭据 - credentials = []Credential{ - {Username: "neo4j", Password: "neo4j"}, - {Username: "neo4j", Password: "admin"}, - {Username: "neo4j", Password: "password"}, - {Username: "neo4j", Password: "123456"}, - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: "neo4j"}, + return &ScanResult{ + Success: false, + Service: "neo4j", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "neo4j", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // Neo4j认证成功 - common.LogSuccess(i18n.GetText("neo4j_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("Neo4j %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -93,25 +65,21 @@ func (p *Neo4jPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResu } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "neo4j", - Error: fmt.Errorf("未发现弱密码或未授权访问"), + Error: fmt.Errorf("未发现弱密码"), } } -// testUnauthorizedAccess 测试未授权访问 func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult { - // 尝试无认证访问 baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) client := &http.Client{ Timeout: time.Duration(common.Timeout) * time.Second, } - // 检查是否可以直接访问数据库 req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil) if err != nil { return nil @@ -134,7 +102,6 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H return nil } -// testCredential 测试单个凭据 func (p *Neo4jPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) @@ -142,7 +109,6 @@ func (p *Neo4jPlugin) testCredential(ctx context.Context, info *common.HostInfo, Timeout: time.Duration(common.Timeout) * time.Second, } - // 尝试认证 req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil) if err != nil { return false @@ -160,240 +126,12 @@ func (p *Neo4jPlugin) testCredential(ctx context.Context, info *common.HostInfo, return resp.StatusCode == 200 } -// executeQuery 执行Cypher查询 -func (p *Neo4jPlugin) executeQuery(ctx context.Context, info *common.HostInfo, creds Credential, query string) (map[string]interface{}, error) { - baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) - - client := &http.Client{ - Timeout: time.Duration(common.Timeout) * time.Second, - } - // 构建查询请求 - queryData := map[string]interface{}{ - "statements": []map[string]interface{}{ - { - "statement": query, - }, - }, - } - jsonData, err := json.Marshal(queryData) - if err != nil { - return nil, err - } - req, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/db/data/transaction/commit", strings.NewReader(string(jsonData))) - if err != nil { - return nil, err - } - req.SetBasicAuth(creds.Username, creds.Password) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - return nil, fmt.Errorf("查询失败,状态码: %d", resp.StatusCode) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - return result, nil -} - -// getServerInfo 获取服务器信息 -func (p *Neo4jPlugin) getServerInfo(ctx context.Context, info *common.HostInfo, creds Credential) string { - baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) - - client := &http.Client{ - Timeout: time.Duration(common.Timeout) * time.Second, - } - - req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil) - if err != nil { - return "" - } - - req.SetBasicAuth(creds.Username, creds.Password) - req.Header.Set("Accept", "application/json") - - resp, err := client.Do(req) - if err != nil { - return "" - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return "" - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "" - } - - var serverInfo map[string]interface{} - err = json.Unmarshal(body, &serverInfo) - if err != nil { - return "" - } - - var info_str strings.Builder - if version, ok := serverInfo["neo4j_version"]; ok { - info_str.WriteString(fmt.Sprintf("Neo4j版本: %v\n", version)) - } - - if extensions, ok := serverInfo["extensions"]; ok { - info_str.WriteString(fmt.Sprintf("扩展信息: %v\n", extensions)) - } - - return info_str.String() -} - -// getDatabaseStats 获取数据库统计信息 -func (p *Neo4jPlugin) getDatabaseStats(ctx context.Context, info *common.HostInfo, creds Credential) string { - // 执行统计查询 - result, err := p.executeQuery(ctx, info, creds, "MATCH (n) RETURN count(n) as node_count") - if err != nil { - return "" - } - - var stats strings.Builder - - // 解析结果 - if results, ok := result["results"].([]interface{}); ok && len(results) > 0 { - if firstResult, ok := results[0].(map[string]interface{}); ok { - if data, ok := firstResult["data"].([]interface{}); ok && len(data) > 0 { - if row, ok := data[0].(map[string]interface{}); ok { - if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 { - stats.WriteString(fmt.Sprintf("节点总数: %v\n", rowData[0])) - } - } - } - } - } - - // 获取关系统计 - relResult, err := p.executeQuery(ctx, info, creds, "MATCH ()-[r]->() RETURN count(r) as rel_count") - if err == nil { - if results, ok := relResult["results"].([]interface{}); ok && len(results) > 0 { - if firstResult, ok := results[0].(map[string]interface{}); ok { - if data, ok := firstResult["data"].([]interface{}); ok && len(data) > 0 { - if row, ok := data[0].(map[string]interface{}); ok { - if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 { - stats.WriteString(fmt.Sprintf("关系总数: %v\n", rowData[0])) - } - } - } - } - } - } - - return stats.String() -} - -// getNodeLabels 获取节点标签列表 -func (p *Neo4jPlugin) getNodeLabels(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.executeQuery(ctx, info, creds, "CALL db.labels()") - if err != nil { - return nil - } - - var labels []string - - // 解析结果 - if results, ok := result["results"].([]interface{}); ok && len(results) > 0 { - if firstResult, ok := results[0].(map[string]interface{}); ok { - if data, ok := firstResult["data"].([]interface{}); ok { - for _, item := range data { - if row, ok := item.(map[string]interface{}); ok { - if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 { - if label, ok := rowData[0].(string); ok { - labels = append(labels, label) - } - } - } - } - } - } - } - - return labels -} - -// getRelationshipTypes 获取关系类型列表 -func (p *Neo4jPlugin) getRelationshipTypes(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.executeQuery(ctx, info, creds, "CALL db.relationshipTypes()") - if err != nil { - return nil - } - - var relationships []string - - // 解析结果 - if results, ok := result["results"].([]interface{}); ok && len(results) > 0 { - if firstResult, ok := results[0].(map[string]interface{}); ok { - if data, ok := firstResult["data"].([]interface{}); ok { - for _, item := range data { - if row, ok := item.(map[string]interface{}); ok { - if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 { - if rel, ok := rowData[0].(string); ok { - relationships = append(relationships, rel) - } - } - } - } - } - } - } - - return relationships -} - -// getProcedures 获取存储过程列表 -func (p *Neo4jPlugin) getProcedures(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.executeQuery(ctx, info, creds, "CALL dbms.procedures() YIELD name RETURN name LIMIT 10") - if err != nil { - return nil - } - - var procedures []string - - // 解析结果 - if results, ok := result["results"].([]interface{}); ok && len(results) > 0 { - if firstResult, ok := results[0].(map[string]interface{}); ok { - if data, ok := firstResult["data"].([]interface{}); ok { - for _, item := range data { - if row, ok := item.(map[string]interface{}); ok { - if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 { - if proc, ok := rowData[0].(string); ok { - procedures = append(procedures, proc) - } - } - } - } - } - } - } - - return procedures -} - -// identifyService 服务识别 - 检测Neo4j服务 func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) @@ -421,17 +159,15 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo } defer resp.Body.Close() - // 检查响应头或内容是否包含Neo4j特征 var banner string if resp.Header.Get("Server") != "" && strings.Contains(strings.ToLower(resp.Header.Get("Server")), "neo4j") { - banner = "Neo4j图数据库 (HTTP接口)" + banner = "Neo4j" } else if resp.StatusCode == 200 || resp.StatusCode == 401 { - // 尝试检查根路径响应 body, _ := io.ReadAll(resp.Body) if strings.Contains(strings.ToLower(string(body)), "neo4j") { - banner = "Neo4j图数据库服务" + banner = "Neo4j" } else { - banner = "Neo4j服务 (协议识别)" + banner = "Neo4j" } } else { return &ScanResult{ @@ -441,7 +177,7 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo } } - common.LogSuccess(i18n.GetText("neo4j_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("Neo4j %s %s", target, banner)) return &ScanResult{ Success: true, @@ -450,7 +186,6 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo } } -// init 自动注册插件 func init() { RegisterPlugin("neo4j", func() Plugin { return NewNeo4jPlugin() diff --git a/plugins/services/oracle.go b/plugins/services/oracle.go index be0623a..ce0fbe0 100644 --- a/plugins/services/oracle.go +++ b/plugins/services/oracle.go @@ -2,276 +2,36 @@ package services import ( "context" - "database/sql" "fmt" - "strings" - // _ "github.com/mattn/go-oci8" // Oracle驱动需要特殊安装,暂时注释 "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// OraclePlugin Oracle数据库扫描和利用插件 - 包含数据库查询利用功能 type OraclePlugin struct { name string ports []int } -// NewOraclePlugin 创建Oracle插件 func NewOraclePlugin() *OraclePlugin { return &OraclePlugin{ name: "oracle", - ports: []int{1521, 1522, 1525}, // Oracle端口 + ports: []int{1521, 1522, 1525}, } } -// GetName 实现Plugin接口 func (p *OraclePlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *OraclePlugin) GetPorts() []int { return p.ports } -// Scan 执行Oracle扫描 - 弱密码检测 func (p *OraclePlugin) 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) } - // 生成测试凭据 - credentials := GenerateCredentials("oracle") - if len(credentials) == 0 { - // Oracle默认凭据 - credentials = []Credential{ - {Username: "sys", Password: "sys"}, - {Username: "sys", Password: "system"}, - {Username: "sys", Password: "oracle"}, - {Username: "system", Password: "system"}, - {Username: "system", Password: "oracle"}, - {Username: "system", Password: "manager"}, - {Username: "scott", Password: "tiger"}, - {Username: "oracle", Password: "oracle"}, - {Username: "admin", Password: "admin"}, - } - } - - // 逐个测试凭据 - for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "oracle", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 - if db := p.testCredential(ctx, info, cred); db != nil { - db.Close() // 关闭测试连接 - - // Oracle认证成功 - common.LogSuccess(i18n.GetText("oracle_scan_success", target, cred.Username, cred.Password)) - - return &ScanResult{ - Success: true, - Service: "oracle", - Username: cred.Username, - Password: cred.Password, - } - } - } - - // 所有凭据都失败 - return &ScanResult{ - Success: false, - Service: "oracle", - Error: fmt.Errorf("未发现弱密码"), - } -} - - -// testCredential 测试单个凭据 - 返回数据库连接或nil -func (p *OraclePlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *sql.DB { - // Oracle驱动需要特殊安装,这里简化实现 - // 在实际环境中需要安装Oracle客户端和go-oci8驱动 - return nil -} - -// getVersion 获取Oracle版本信息 -func (p *OraclePlugin) getVersion(db *sql.DB) string { - var version string - err := db.QueryRow("SELECT banner FROM v$version WHERE rownum = 1").Scan(&version) - if err != nil { - // 尝试备用查询 - err = db.QueryRow("SELECT version FROM product_component_version WHERE rownum = 1").Scan(&version) - if err != nil { - return "" - } - } - return version -} - -// getDatabaseInfo 获取数据库基本信息 -func (p *OraclePlugin) getDatabaseInfo(db *sql.DB) string { - var info strings.Builder - - // 获取数据库名 - var dbName string - err := db.QueryRow("SELECT name FROM v$database").Scan(&dbName) - if err == nil { - info.WriteString(fmt.Sprintf("数据库名: %s\n", dbName)) - } - - // 获取实例名 - var instanceName string - err = db.QueryRow("SELECT instance_name FROM v$instance").Scan(&instanceName) - if err == nil { - info.WriteString(fmt.Sprintf("实例名: %s\n", instanceName)) - } - - // 获取字符集 - var charset string - err = db.QueryRow("SELECT value FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET'").Scan(&charset) - if err == nil { - info.WriteString(fmt.Sprintf("字符集: %s\n", charset)) - } - - return info.String() -} - -// getTablespaces 获取表空间列表 -func (p *OraclePlugin) getTablespaces(db *sql.DB) []string { - query := "SELECT tablespace_name FROM dba_tablespaces" - rows, err := db.Query(query) - if err != nil { - // 尝试用户表空间查询 - query = "SELECT tablespace_name FROM user_tablespaces" - rows, err = db.Query(query) - if err != nil { - return nil - } - } - defer rows.Close() - - var tablespaces []string - for rows.Next() { - var tsName string - if err := rows.Scan(&tsName); err == nil { - tablespaces = append(tablespaces, tsName) - } - } - - return tablespaces -} - -// getUsers 获取用户列表 -func (p *OraclePlugin) getUsers(db *sql.DB) []string { - query := "SELECT username FROM dba_users ORDER BY username" - rows, err := db.Query(query) - if err != nil { - // 尝试all_users - query = "SELECT username FROM all_users ORDER BY username" - rows, err = db.Query(query) - if err != nil { - return nil - } - } - defer rows.Close() - - var users []string - for rows.Next() { - var userName string - if err := rows.Scan(&userName); err == nil { - users = append(users, userName) - } - } - - return users -} - -// getTables 获取指定用户的表列表 -func (p *OraclePlugin) getTables(db *sql.DB, owner string) []string { - query := "SELECT table_name FROM dba_tables WHERE owner = :1 ORDER BY table_name" - rows, err := db.Query(query, strings.ToUpper(owner)) - if err != nil { - // 尝试用户表查询 - query = "SELECT table_name FROM user_tables ORDER BY table_name" - rows, err = db.Query(query) - if err != nil { - return nil - } - } - defer rows.Close() - - var tables []string - for rows.Next() { - var tableName string - if err := rows.Scan(&tableName); err == nil { - tables = append(tables, tableName) - } - } - - return tables -} - -// getPrivileges 获取用户权限信息 -func (p *OraclePlugin) getPrivileges(db *sql.DB, username string) string { - var privileges strings.Builder - - // 检查DBA权限 - var dbaRole int - err := db.QueryRow("SELECT COUNT(*) FROM dba_role_privs WHERE grantee = :1 AND granted_role = 'DBA'", - strings.ToUpper(username)).Scan(&dbaRole) - if err == nil && dbaRole > 0 { - privileges.WriteString("DBA权限: YES\n") - } else { - privileges.WriteString("DBA权限: NO\n") - } - - // 检查SYSDBA权限 - var sysdbaCount int - err = db.QueryRow("SELECT COUNT(*) FROM v$pwfile_users WHERE username = :1 AND sysdba = 'TRUE'", - strings.ToUpper(username)).Scan(&sysdbaCount) - if err == nil && sysdbaCount > 0 { - privileges.WriteString("SYSDBA权限: YES\n") - } - - // 获取角色列表 - query := "SELECT granted_role FROM dba_role_privs WHERE grantee = :1 AND rownum <= 5" - rows, err := db.Query(query, strings.ToUpper(username)) - if err == nil { - defer rows.Close() - privileges.WriteString("已授予角色: ") - var roles []string - for rows.Next() { - var role string - if err := rows.Scan(&role); err == nil { - roles = append(roles, role) - } - } - if len(roles) > 0 { - privileges.WriteString(strings.Join(roles, ", ") + "\n") - } else { - privileges.WriteString("无\n") - } - } - - return privileges.String() -} - -// identifyService 服务识别 - 检测Oracle服务 -func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { - // Oracle驱动需要特殊安装,这里简化实现 - // 在实际环境中需要安装Oracle客户端和go-oci8驱动 return &ScanResult{ Success: false, Service: "oracle", @@ -279,7 +39,22 @@ func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInf } } -// init 自动注册插件 + + + + + + + + +func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { + return &ScanResult{ + Success: false, + Service: "oracle", + Error: fmt.Errorf("Oracle驱动未安装"), + } +} + func init() { RegisterPlugin("oracle", func() Plugin { return NewOraclePlugin() diff --git a/plugins/services/postgresql.go b/plugins/services/postgresql.go index 3ea7989..8162884 100644 --- a/plugins/services/postgresql.go +++ b/plugins/services/postgresql.go @@ -9,76 +9,49 @@ import ( _ "github.com/lib/pq" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// PostgreSQLPlugin PostgreSQL数据库扫描和利用插件 - 包含数据库查询利用功能 type PostgreSQLPlugin struct { name string ports []int } -// NewPostgreSQLPlugin 创建PostgreSQL插件 func NewPostgreSQLPlugin() *PostgreSQLPlugin { return &PostgreSQLPlugin{ name: "postgresql", - ports: []int{5432, 5433, 5434}, // PostgreSQL端口 + ports: []int{5432, 5433, 5434}, } } -// GetName 实现Plugin接口 func (p *PostgreSQLPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *PostgreSQLPlugin) GetPorts() []int { return p.ports } -// Scan 执行PostgreSQL扫描 - 弱密码检测 func (p *PostgreSQLPlugin) 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) } - // 生成测试凭据 credentials := GenerateCredentials("postgresql") if len(credentials) == 0 { - // PostgreSQL默认凭据 - credentials = []Credential{ - {Username: "postgres", Password: ""}, - {Username: "postgres", Password: "postgres"}, - {Username: "postgres", Password: "password"}, - {Username: "postgres", Password: "admin"}, - {Username: "postgres", Password: "123456"}, - {Username: "admin", Password: "admin"}, - {Username: "root", Password: "root"}, + return &ScanResult{ + Success: false, + Service: "postgresql", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "postgresql", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if db := p.testCredential(ctx, info, cred); db != nil { - db.Close() // 关闭测试连接 + db.Close() - // PostgreSQL认证成功 - common.LogSuccess(i18n.GetText("postgresql_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("PostgreSQL %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -89,7 +62,6 @@ func (p *PostgreSQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *Sca } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "postgresql", @@ -98,28 +70,22 @@ func (p *PostgreSQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *Sca } -// testCredential 测试单个凭据 - 返回数据库连接或nil func (p *PostgreSQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *sql.DB { - // 构建连接字符串 connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/postgres?sslmode=disable&connect_timeout=%d", cred.Username, cred.Password, info.Host, info.Ports, common.Timeout) - // 打开数据库连接 db, err := sql.Open("postgres", connStr) if err != nil { return nil } - // 设置连接参数 db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) - // 创建超时上下文 pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second) defer cancel() - // 测试连接 err = db.PingContext(pingCtx) if err != nil { db.Close() @@ -129,111 +95,14 @@ func (p *PostgreSQLPlugin) testCredential(ctx context.Context, info *common.Host return db } -// getVersion 获取PostgreSQL版本信息 -func (p *PostgreSQLPlugin) getVersion(db *sql.DB) string { - var version string - err := db.QueryRow("SELECT version()").Scan(&version) - if err != nil { - return "" - } - return version -} -// getDatabases 获取数据库列表 -func (p *PostgreSQLPlugin) getDatabases(db *sql.DB) []string { - query := "SELECT datname FROM pg_database WHERE datistemplate = false" - rows, err := db.Query(query) - if err != nil { - return nil - } - defer rows.Close() - var databases []string - for rows.Next() { - var dbName string - if err := rows.Scan(&dbName); err == nil { - databases = append(databases, dbName) - } - } - return databases -} -// getTables 获取表列表 -func (p *PostgreSQLPlugin) getTables(db *sql.DB) []string { - query := `SELECT tablename FROM pg_tables WHERE schemaname = 'public' - UNION SELECT viewname FROM pg_views WHERE schemaname = 'public' - ORDER BY 1 LIMIT 20` - - rows, err := db.Query(query) - if err != nil { - return nil - } - defer rows.Close() - var tables []string - for rows.Next() { - var tableName string - if err := rows.Scan(&tableName); err == nil { - tables = append(tables, tableName) - } - } - - return tables -} - -// getUsers 获取用户列表 -func (p *PostgreSQLPlugin) getUsers(db *sql.DB) []string { - query := "SELECT usename FROM pg_user ORDER BY usename" - rows, err := db.Query(query) - if err != nil { - return nil - } - defer rows.Close() - - var users []string - for rows.Next() { - var userName string - if err := rows.Scan(&userName); err == nil { - users = append(users, userName) - } - } - - return users -} - -// getPrivileges 获取用户权限信息 -func (p *PostgreSQLPlugin) getPrivileges(db *sql.DB, username string) string { - var privileges strings.Builder - - // 检查是否为超级用户 - var isSuperUser bool - err := db.QueryRow("SELECT usesuper FROM pg_user WHERE usename = $1", username).Scan(&isSuperUser) - if err == nil { - if isSuperUser { - privileges.WriteString("超级用户权限: YES\n") - } else { - privileges.WriteString("超级用户权限: NO\n") - } - } - - // 获取用户属性 - var createDB, createRole bool - err = db.QueryRow("SELECT usecreatedb, usecreaterole FROM pg_user WHERE usename = $1", - username).Scan(&createDB, &createRole) - if err == nil { - privileges.WriteString(fmt.Sprintf("创建数据库权限: %v\n", createDB)) - privileges.WriteString(fmt.Sprintf("创建角色权限: %v\n", createRole)) - } - - return privileges.String() -} - -// identifyService 服务识别 - 检测PostgreSQL服务 func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 尝试连接PostgreSQL服务 connStr := fmt.Sprintf("postgres://invalid:invalid@%s:%s/postgres?sslmode=disable&connect_timeout=%d", info.Host, info.Ports, common.Timeout) @@ -247,7 +116,6 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos } defer db.Close() - // 尝试连接(即使认证失败,也能识别服务) pingCtx, cancel := context.WithTimeout(ctx, time.Duration(common.Timeout)*time.Second) defer cancel() @@ -255,9 +123,9 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos var banner string if err != nil && strings.Contains(strings.ToLower(err.Error()), "postgres") { - banner = "PostgreSQL数据库服务" + banner = "PostgreSQL" } else if err == nil { - banner = "PostgreSQL数据库 (连接成功)" + banner = "PostgreSQL" } else { return &ScanResult{ Success: false, @@ -266,7 +134,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos } } - common.LogSuccess(i18n.GetText("postgresql_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("PostgreSQL %s %s", target, banner)) return &ScanResult{ Success: true, @@ -275,7 +143,6 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos } } -// init 自动注册插件 func init() { RegisterPlugin("postgresql", func() Plugin { return NewPostgreSQLPlugin() diff --git a/plugins/services/rabbitmq.go b/plugins/services/rabbitmq.go index 9a7c030..c94ee1c 100644 --- a/plugins/services/rabbitmq.go +++ b/plugins/services/rabbitmq.go @@ -2,7 +2,6 @@ package services import ( "context" - "encoding/json" "fmt" "io" "net/http" @@ -10,79 +9,51 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// RabbitMQPlugin RabbitMQ消息队列扫描和利用插件 - 包含队列信息提取利用功能 type RabbitMQPlugin struct { name string ports []int } -// NewRabbitMQPlugin 创建RabbitMQ插件 func NewRabbitMQPlugin() *RabbitMQPlugin { return &RabbitMQPlugin{ name: "rabbitmq", - ports: []int{5672, 15672, 5671}, // AMQP、管理界面、AMQPS端口 + ports: []int{5672, 15672, 5671}, } } -// GetName 实现Plugin接口 func (p *RabbitMQPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *RabbitMQPlugin) GetPorts() []int { return p.ports } -// Scan 执行RabbitMQ扫描 - 弱密码检测 func (p *RabbitMQPlugin) 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) } - // RabbitMQ管理端口通常是15672,AMQP端口是5672 if info.Ports == "5672" || info.Ports == "5671" { - // AMQP端口,进行协议检测 return p.testAMQPProtocol(ctx, info) } - // 生成测试凭据 credentials := GenerateCredentials("rabbitmq") if len(credentials) == 0 { - // RabbitMQ默认凭据 - credentials = []Credential{ - {Username: "guest", Password: "guest"}, - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: "password"}, - {Username: "admin", Password: "123456"}, - {Username: "rabbitmq", Password: "rabbitmq"}, - {Username: "user", Password: "user"}, + return &ScanResult{ + Success: false, + Service: "rabbitmq", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据(针对管理接口) for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "rabbitmq", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // RabbitMQ认证成功 - common.LogSuccess(i18n.GetText("rabbitmq_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -93,7 +64,6 @@ func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "rabbitmq", @@ -102,27 +72,21 @@ func (p *RabbitMQPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR } -// testAMQPProtocol 测试AMQP协议 func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo) *ScanResult { - // 对于AMQP端口,我们只做协议识别 - // AMQP协议检测比较复杂,这里简化处理 return &ScanResult{ Success: true, Service: "rabbitmq", - Banner: "RabbitMQ AMQP协议端口", + Banner: "RabbitMQ AMQP", } } -// testCredential 测试单个凭据(通过管理API) func (p *RabbitMQPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { - // 构建管理API URL baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) client := &http.Client{ Timeout: time.Duration(common.Timeout) * time.Second, } - // 尝试访问API overview req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil) if err != nil { return false @@ -140,235 +104,24 @@ func (p *RabbitMQPlugin) testCredential(ctx context.Context, info *common.HostIn return resp.StatusCode == 200 } -// makeAPIRequest 执行API请求 -func (p *RabbitMQPlugin) makeAPIRequest(ctx context.Context, info *common.HostInfo, creds Credential, endpoint string) (map[string]interface{}, error) { - baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) - - client := &http.Client{ - Timeout: time.Duration(common.Timeout) * time.Second, - } - req, err := http.NewRequestWithContext(ctx, "GET", baseURL+endpoint, nil) - if err != nil { - return nil, err - } - req.SetBasicAuth(creds.Username, creds.Password) - req.Header.Set("Accept", "application/json") - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - return nil, fmt.Errorf("API请求失败,状态码: %d", resp.StatusCode) - } - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - // 尝试解析为数组 - var arrayResult []map[string]interface{} - if err2 := json.Unmarshal(body, &arrayResult); err2 == nil { - // 如果是数组,返回包装的结果 - return map[string]interface{}{"data": arrayResult}, nil - } - return nil, err - } - return result, nil -} - -// getClusterInfo 获取集群信息 -func (p *RabbitMQPlugin) getClusterInfo(ctx context.Context, info *common.HostInfo, creds Credential) string { - result, err := p.makeAPIRequest(ctx, info, creds, "/api/overview") - if err != nil { - return "" - } - - var info_str strings.Builder - - if managementVersion, ok := result["management_version"]; ok { - info_str.WriteString(fmt.Sprintf("管理版本: %v\n", managementVersion)) - } - - if rabbitMQVersion, ok := result["rabbitmq_version"]; ok { - info_str.WriteString(fmt.Sprintf("RabbitMQ版本: %v\n", rabbitMQVersion)) - } - - if clusterName, ok := result["cluster_name"]; ok { - info_str.WriteString(fmt.Sprintf("集群名称: %v\n", clusterName)) - } - - return info_str.String() -} - -// getNodes 获取节点列表 -func (p *RabbitMQPlugin) getNodes(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.makeAPIRequest(ctx, info, creds, "/api/nodes") - if err != nil { - return nil - } - - var nodes []string - - if data, ok := result["data"].([]interface{}); ok { - for _, item := range data { - if node, ok := item.(map[string]interface{}); ok { - if name, ok := node["name"]; ok { - if nameStr, ok := name.(string); ok { - nodes = append(nodes, nameStr) - } - } - } - } - } - - return nodes -} - -// getVHosts 获取虚拟主机列表 -func (p *RabbitMQPlugin) getVHosts(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.makeAPIRequest(ctx, info, creds, "/api/vhosts") - if err != nil { - return nil - } - - var vhosts []string - - if data, ok := result["data"].([]interface{}); ok { - for _, item := range data { - if vhost, ok := item.(map[string]interface{}); ok { - if name, ok := vhost["name"]; ok { - if nameStr, ok := name.(string); ok { - vhosts = append(vhosts, nameStr) - } - } - } - } - } - - return vhosts -} - -// getUsers 获取用户列表 -func (p *RabbitMQPlugin) getUsers(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.makeAPIRequest(ctx, info, creds, "/api/users") - if err != nil { - return nil - } - - var users []string - - if data, ok := result["data"].([]interface{}); ok { - for _, item := range data { - if user, ok := item.(map[string]interface{}); ok { - if name, ok := user["name"]; ok { - if nameStr, ok := name.(string); ok { - // 获取用户标签信息 - var tags string - if tagsInterface, ok := user["tags"]; ok { - if tagsStr, ok := tagsInterface.(string); ok { - tags = fmt.Sprintf(" [%s]", tagsStr) - } - } - users = append(users, nameStr+tags) - } - } - } - } - } - - return users -} - -// getQueues 获取队列列表 -func (p *RabbitMQPlugin) getQueues(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.makeAPIRequest(ctx, info, creds, "/api/queues") - if err != nil { - return nil - } - - var queues []string - - if data, ok := result["data"].([]interface{}); ok { - for _, item := range data { - if queue, ok := item.(map[string]interface{}); ok { - if name, ok := queue["name"]; ok { - if nameStr, ok := name.(string); ok { - // 获取队列消息数 - var messages string - if messagesInterface, ok := queue["messages"]; ok { - messages = fmt.Sprintf(" (消息数: %v)", messagesInterface) - } - queues = append(queues, nameStr+messages) - } - } - } - } - } - - return queues -} - -// getExchanges 获取交换器列表 -func (p *RabbitMQPlugin) getExchanges(ctx context.Context, info *common.HostInfo, creds Credential) []string { - result, err := p.makeAPIRequest(ctx, info, creds, "/api/exchanges") - if err != nil { - return nil - } - - var exchanges []string - - if data, ok := result["data"].([]interface{}); ok { - for _, item := range data { - if exchange, ok := item.(map[string]interface{}); ok { - if name, ok := exchange["name"]; ok { - if nameStr, ok := name.(string); ok { - // 过滤掉空名称的默认交换器 - if nameStr == "" { - nameStr = "(default)" - } - - // 获取交换器类型 - var exchType string - if typeInterface, ok := exchange["type"]; ok { - if typeStr, ok := typeInterface.(string); ok { - exchType = fmt.Sprintf(" [%s]", typeStr) - } - } - exchanges = append(exchanges, nameStr+exchType) - } - } - } - } - } - - return exchanges -} - -// identifyService 服务识别 - 检测RabbitMQ服务 func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 根据端口类型进行不同的识别 if info.Ports == "5672" || info.Ports == "5671" { - // AMQP端口 return &ScanResult{ Success: true, Service: "rabbitmq", - Banner: "RabbitMQ AMQP协议服务", + Banner: "RabbitMQ AMQP", } } - // 管理端口,尝试HTTP请求 baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports) client := &http.Client{ @@ -394,18 +147,17 @@ func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostI } defer resp.Body.Close() - // 检查响应是否包含RabbitMQ特征 var banner string if resp.StatusCode == 200 || resp.StatusCode == 401 { body, _ := io.ReadAll(resp.Body) bodyStr := strings.ToLower(string(body)) if strings.Contains(bodyStr, "rabbitmq") { - banner = "RabbitMQ管理界面" + banner = "RabbitMQ" } else if strings.Contains(bodyStr, "management") { - banner = "RabbitMQ服务 (管理端口)" + banner = "RabbitMQ" } else { - banner = "RabbitMQ消息队列服务" + banner = "RabbitMQ" } } else { return &ScanResult{ @@ -415,7 +167,7 @@ func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostI } } - common.LogSuccess(i18n.GetText("rabbitmq_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("RabbitMQ %s %s", target, banner)) return &ScanResult{ Success: true, diff --git a/plugins/services/rsync.go b/plugins/services/rsync.go index 4a27816..3bd414c 100644 --- a/plugins/services/rsync.go +++ b/plugins/services/rsync.go @@ -9,64 +9,70 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// RsyncPlugin Rsync文件同步服务扫描和利用插件 - 包含文件列表利用功能 type RsyncPlugin struct { name string ports []int } -// NewRsyncPlugin 创建Rsync插件 func NewRsyncPlugin() *RsyncPlugin { return &RsyncPlugin{ name: "rsync", - ports: []int{873}, // Rsync端口 + ports: []int{873}, } } -// GetName 实现Plugin接口 func (p *RsyncPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *RsyncPlugin) GetPorts() []int { return p.ports } -// Scan 执行Rsync扫描 - 未授权访问检测 func (p *RsyncPlugin) 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) } - // Rsync主要检查未授权访问和弱密码 if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("rsync_unauth_success", target)) + common.LogSuccess(fmt.Sprintf("Rsync %s 未授权访问", target)) return result } - // 如果未授权访问失败,尝试弱密码 - if result := p.testWeakPasswords(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("rsync_weak_pwd_success", target)) - return result + credentials := GenerateCredentials("rsync") + if len(credentials) == 0 { + return &ScanResult{ + Success: false, + Service: "rsync", + Error: fmt.Errorf("没有可用的测试凭据"), + } + } + + for _, cred := range credentials { + if p.testCredential(ctx, info, cred) { + common.LogSuccess(fmt.Sprintf("Rsync %s %s:%s", target, cred.Username, cred.Password)) + + return &ScanResult{ + Success: true, + Service: "rsync", + Username: cred.Username, + Password: cred.Password, + } + } } - // 所有尝试都失败 return &ScanResult{ Success: false, Service: "rsync", - Error: fmt.Errorf("Rsync服务连接失败或需要认证"), + Error: fmt.Errorf("未发现弱密码"), } } -// testUnauthorizedAccess 测试未授权访问 func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult { conn := p.connectToRsync(ctx, info) if conn == nil { @@ -74,7 +80,6 @@ func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H } defer conn.Close() - // 尝试列出模块 modules := p.getModules(conn) if len(modules) > 0 { return &ScanResult{ @@ -87,14 +92,10 @@ func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H return nil } -// testWeakPasswords 测试弱密码 -func (p *RsyncPlugin) testWeakPasswords(ctx context.Context, info *common.HostInfo) *ScanResult { - // Rsync密码通常在模块级别设置,这里简化处理 - // 实际场景中需要针对具体模块进行认证 - return nil +func (p *RsyncPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { + return false } -// connectToRsync 连接到Rsync服务 func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo) net.Conn { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second @@ -104,15 +105,11 @@ func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo) return nil } - // 设置操作超时 conn.SetDeadline(time.Now().Add(timeout)) - return conn } -// getModules 获取Rsync模块列表 func (p *RsyncPlugin) getModules(conn net.Conn) []string { - // 发送RSYNCD协议的模块列表请求 timeout := time.Duration(common.Timeout) * time.Second conn.SetWriteDeadline(time.Now().Add(timeout)) @@ -130,12 +127,10 @@ func (p *RsyncPlugin) getModules(conn net.Conn) []string { continue } - // Rsync协议结束标记 if strings.HasPrefix(line, "@RSYNCD: EXIT") { break } - // 跳过协议头 if strings.HasPrefix(line, "@RSYNCD:") { continue } @@ -146,68 +141,6 @@ func (p *RsyncPlugin) getModules(conn net.Conn) []string { return modules } -// getModuleFiles 获取指定模块的文件列表 -func (p *RsyncPlugin) getModuleFiles(ctx context.Context, info *common.HostInfo, module string) []string { - conn := p.connectToRsync(ctx, info) - if conn == nil { - return nil - } - defer conn.Close() - - timeout := time.Duration(common.Timeout) * time.Second - - // 发送模块名和协议版本 - request := fmt.Sprintf("%s\n", module) - - conn.SetWriteDeadline(time.Now().Add(timeout)) - if _, err := conn.Write([]byte(request)); err != nil { - return nil - } - - conn.SetReadDeadline(time.Now().Add(timeout)) - scanner := bufio.NewScanner(conn) - - var files []string - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == "" { - continue - } - - // 检查错误响应 - if strings.Contains(line, "@ERROR") { - break - } - - // Rsync协议结束标记 - if strings.HasPrefix(line, "@RSYNCD: EXIT") { - break - } - - // 跳过协议头 - if strings.HasPrefix(line, "@RSYNCD:") { - continue - } - - files = append(files, line) - - // 限制文件数量避免过多输出 - if len(files) >= 50 { - break - } - } - - return files -} - -// testWritePermission 测试写权限 -func (p *RsyncPlugin) testWritePermission(ctx context.Context, info *common.HostInfo) string { - // Rsync写权限测试比较复杂,需要实际的rsync客户端 - // 这里简化为检测是否支持上传 - return "❌ 写权限检测需要完整的rsync客户端支持" -} - -// identifyService 服务识别 - 检测Rsync服务 func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -221,7 +154,6 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } defer conn.Close() - // 尝试Rsync协议握手 timeout := time.Duration(common.Timeout) * time.Second conn.SetWriteDeadline(time.Now().Add(timeout)) @@ -248,7 +180,6 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo var banner string if strings.Contains(responseStr, "@RSYNCD") { - // 解析版本信息 lines := strings.Split(responseStr, "\n") for _, line := range lines { if strings.HasPrefix(line, "@RSYNCD:") { @@ -267,7 +198,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } } - common.LogSuccess(i18n.GetText("rsync_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("Rsync %s %s", target, banner)) return &ScanResult{ Success: true, @@ -276,7 +207,6 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } } -// init 自动注册插件 func init() { RegisterPlugin("rsync", func() Plugin { return NewRsyncPlugin() diff --git a/plugins/services/smtp.go b/plugins/services/smtp.go index 70dedbd..c7636bb 100644 --- a/plugins/services/smtp.go +++ b/plugins/services/smtp.go @@ -10,80 +10,52 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// SMTPPlugin SMTP邮件服务扫描和利用插件 - 包含用户枚举利用功能 type SMTPPlugin struct { name string ports []int } -// NewSMTPPlugin 创建SMTP插件 func NewSMTPPlugin() *SMTPPlugin { return &SMTPPlugin{ name: "smtp", - ports: []int{25, 465, 587, 2525}, // SMTP端口 + ports: []int{25, 465, 587, 2525}, } } -// GetName 实现Plugin接口 func (p *SMTPPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *SMTPPlugin) GetPorts() []int { return p.ports } -// Scan 执行SMTP扫描 - 弱密码检测和开放中继检测 func (p *SMTPPlugin) 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) } - // 首先检查开放中继 if result := p.testOpenRelay(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("smtp_open_relay_success", target)) + common.LogSuccess(fmt.Sprintf("SMTP %s 开放中继", target)) return result } - // 生成测试凭据 credentials := GenerateCredentials("smtp") if len(credentials) == 0 { - // SMTP默认凭据 - credentials = []Credential{ - {Username: "admin", Password: "admin"}, - {Username: "admin", Password: "password"}, - {Username: "admin", Password: "123456"}, - {Username: "test", Password: "test"}, - {Username: "user", Password: "user"}, - {Username: "mail", Password: "mail"}, - {Username: "postmaster", Password: "postmaster"}, + return &ScanResult{ + Success: false, + Service: "smtp", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "smtp", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // SMTP认证成功 - common.LogSuccess(i18n.GetText("smtp_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("SMTP %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -94,36 +66,49 @@ func (p *SMTPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResul } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "smtp", - Error: fmt.Errorf("未发现弱密码或开放中继"), + Error: fmt.Errorf("未发现弱密码"), } } -// testOpenRelay 测试开放中继 func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo) *ScanResult { - if p.checkOpenRelay(ctx, info) != "" { - return &ScanResult{ - Success: true, - Service: "smtp", - Banner: "开放中继", - } + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + client, err := smtp.Dial(target) + if err != nil { + return nil + } + defer client.Quit() + + if err := client.Hello("fscan.test"); err != nil { + return nil + } + + if err := client.Mail("test@fscan.test"); err != nil { + return nil + } + + if err := client.Rcpt("external@example.com"); err != nil { + return nil + } + + return &ScanResult{ + Success: true, + Service: "smtp", + Banner: "开放中继", } - return nil } -// testCredential 测试单个凭据 func (p *SMTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - // 根据端口选择连接方式 var client *smtp.Client var err error - if info.Ports == "465" { // SMTPS + if info.Ports == "465" { conn, err := tls.Dial("tcp", target, &tls.Config{InsecureSkipVerify: true}) if err != nil { return false @@ -142,14 +127,12 @@ func (p *SMTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, } defer client.Quit() - // 尝试STARTTLS if ok, _ := client.Extension("STARTTLS"); ok { if err := client.StartTLS(&tls.Config{InsecureSkipVerify: true}); err != nil { - // STARTTLS失败,继续明文认证 + } } - // 尝试认证 auth := smtp.PlainAuth("", cred.Username, cred.Password, info.Host) if err := client.Auth(auth); err != nil { return false @@ -158,7 +141,6 @@ func (p *SMTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, return true } -// getServerInfo 获取服务器信息 func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo) string { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -168,7 +150,6 @@ func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo) s } defer conn.Close() - // 读取欢迎消息 conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) buffer := make([]byte, 1024) n, err := conn.Read(buffer) @@ -184,108 +165,6 @@ func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo) s return welcome } -// getExtensions 获取SMTP扩展 -func (p *SMTPPlugin) getExtensions(ctx context.Context, info *common.HostInfo) []string { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - client, err := smtp.Dial(target) - if err != nil { - return nil - } - defer client.Quit() - - // 发送EHLO获取扩展 - if err := client.Hello("fscan.test"); err != nil { - return nil - } - - var extensions []string - if exts, _ := client.Extension("AUTH"); exts { - extensions = append(extensions, "AUTH (认证支持)") - } - if exts, _ := client.Extension("STARTTLS"); exts { - extensions = append(extensions, "STARTTLS (TLS支持)") - } - if exts, _ := client.Extension("SIZE"); exts { - extensions = append(extensions, "SIZE (邮件大小限制)") - } - if exts, _ := client.Extension("PIPELINING"); exts { - extensions = append(extensions, "PIPELINING (管道支持)") - } - if exts, _ := client.Extension("8BITMIME"); exts { - extensions = append(extensions, "8BITMIME (8位MIME)") - } - - return extensions -} - -// enumerateUsers 枚举用户 -func (p *SMTPPlugin) enumerateUsers(ctx context.Context, info *common.HostInfo) []string { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - client, err := smtp.Dial(target) - if err != nil { - return nil - } - defer client.Quit() - - if err := client.Hello("fscan.test"); err != nil { - return nil - } - - // 常见用户名列表 - commonUsers := []string{"admin", "administrator", "root", "user", "test", "mail", "postmaster", "webmaster", "info", "support", "sales", "marketing"} - var validUsers []string - - for _, user := range commonUsers { - // 使用VRFY命令验证用户 - if err := p.sendRawCommand(client, fmt.Sprintf("VRFY %s", user)); err == nil { - validUsers = append(validUsers, user) - } - - // 限制数量 - if len(validUsers) >= 10 { - break - } - } - - return validUsers -} - -// sendRawCommand 发送原始SMTP命令 -func (p *SMTPPlugin) sendRawCommand(client *smtp.Client, command string) error { - // 这里简化实现,实际需要直接操作连接 - // smtp包没有直接的原始命令接口 - return fmt.Errorf("VRFY command not supported") -} - -// checkOpenRelay 检查开放中继 -func (p *SMTPPlugin) checkOpenRelay(ctx context.Context, info *common.HostInfo) string { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - client, err := smtp.Dial(target) - if err != nil { - return "" - } - defer client.Quit() - - if err := client.Hello("fscan.test"); err != nil { - return "" - } - - // 尝试发送外部邮件测试开放中继 - if err := client.Mail("test@fscan.test"); err != nil { - return "❌ 邮件发送测试失败" - } - - if err := client.Rcpt("external@example.com"); err != nil { - return "❌ 不是开放中继" - } - - return "⚠️ 可能存在开放中继风险" -} - -// identifyService 服务识别 - 检测SMTP服务 func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -295,7 +174,6 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo) if serverInfo != "" { banner = fmt.Sprintf("SMTP邮件服务 (%s)", serverInfo) } else { - // 尝试简单连接测试 conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return &ScanResult{ @@ -308,7 +186,7 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo) banner = "SMTP邮件服务" } - common.LogSuccess(i18n.GetText("smtp_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("SMTP %s %s", target, banner)) return &ScanResult{ Success: true, @@ -317,7 +195,6 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo) } } -// init 自动注册插件 func init() { RegisterPlugin("smtp", func() Plugin { return NewSMTPPlugin() diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index d690a30..e22ca4d 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -5,77 +5,60 @@ import ( "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端口 + ports: []int{161, 162}, } } -// 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: + credentials := GenerateCredentials("snmp") + if len(credentials) == 0 { + return &ScanResult{ + Success: false, + Service: "snmp", + Error: fmt.Errorf("没有可用的测试凭据"), } + } - // 测试团体字符串 - if p.testCommunity(ctx, info, community) { - // SNMP团体字符串测试成功 - common.LogSuccess(i18n.GetText("snmp_scan_success", target, community)) + for _, cred := range credentials { + if p.testCredential(ctx, info, cred) { + common.LogSuccess(fmt.Sprintf("SNMP %s %s", target, cred.Username)) return &ScanResult{ Success: true, Service: "snmp", - Username: community, // 团体字符串作为用户名 + Username: cred.Username, Password: "", } } } - // 所有团体字符串都失败 return &ScanResult{ Success: false, Service: "snmp", @@ -83,29 +66,7 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResul } } - -// 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 { +func (p *SNMPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second) @@ -114,8 +75,7 @@ func (p *SNMPPlugin) testCommunity(ctx context.Context, info *common.HostInfo, c } 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") + packet := p.buildSNMPGetRequest(cred.Username, "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 { @@ -129,109 +89,36 @@ func (p *SNMPPlugin) testCommunity(ctx context.Context, info *common.HostInfo, c 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") { + cred := Credential{Username: "public", Password: ""} + if p.testCredential(ctx, info, cred) { banner := "SNMP网络管理服务 (public团体可访问)" - common.LogSuccess(i18n.GetText("snmp_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("SNMP %s %s", target, banner)) return &ScanResult{ Success: true, @@ -240,7 +127,6 @@ func (p *SNMPPlugin) identifyService(ctx context.Context, info *common.HostInfo) } } - // 尝试UDP连接测试 conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return &ScanResult{ @@ -251,8 +137,8 @@ func (p *SNMPPlugin) identifyService(ctx context.Context, info *common.HostInfo) } defer conn.Close() - banner := "SNMP网络管理服务 (需要有效团体字符串)" - common.LogSuccess(i18n.GetText("snmp_service_identified", target, banner)) + banner := "SNMP网络管理服务" + common.LogSuccess(fmt.Sprintf("SNMP %s %s", target, banner)) return &ScanResult{ Success: true, @@ -261,7 +147,6 @@ func (p *SNMPPlugin) identifyService(ctx context.Context, info *common.HostInfo) } } -// init 自动注册插件 func init() { RegisterPlugin("snmp", func() Plugin { return NewSNMPPlugin() diff --git a/plugins/services/telnet.go b/plugins/services/telnet.go index fae0865..1b9bb2d 100644 --- a/plugins/services/telnet.go +++ b/plugins/services/telnet.go @@ -8,81 +8,52 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// TelnetPlugin Telnet远程终端服务扫描和利用插件 - 包含命令执行利用功能 type TelnetPlugin struct { name string ports []int } -// NewTelnetPlugin 创建Telnet插件 func NewTelnetPlugin() *TelnetPlugin { return &TelnetPlugin{ name: "telnet", - ports: []int{23, 2323}, // Telnet端口 + ports: []int{23, 2323}, } } -// GetName 实现Plugin接口 func (p *TelnetPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *TelnetPlugin) GetPorts() []int { return p.ports } -// Scan 执行Telnet扫描 - 弱密码检测和未授权访问检测 func (p *TelnetPlugin) 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) } - // 首先测试无认证访问 if result := p.testUnauthAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("telnet_unauth_success", target)) + common.LogSuccess(fmt.Sprintf("Telnet %s 未授权访问", target)) return result } - // 生成测试凭据 credentials := GenerateCredentials("telnet") if len(credentials) == 0 { - // Telnet默认凭据 - credentials = []Credential{ - {Username: "admin", Password: "admin"}, - {Username: "root", Password: "root"}, - {Username: "admin", Password: "password"}, - {Username: "admin", Password: "123456"}, - {Username: "user", Password: "user"}, - {Username: "test", Password: "test"}, - {Username: "guest", Password: "guest"}, - {Username: "admin", Password: ""}, + return &ScanResult{ + Success: false, + Service: "telnet", + Error: fmt.Errorf("没有可用的测试凭据"), } } - // 逐个测试凭据 for _, cred := range credentials { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "telnet", - Error: ctx.Err(), - } - default: - } - - // 测试凭据 if p.testCredential(ctx, info, cred) { - // Telnet认证成功 - common.LogSuccess(i18n.GetText("telnet_scan_success", target, cred.Username, cred.Password)) + common.LogSuccess(fmt.Sprintf("Telnet %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, @@ -93,16 +64,14 @@ func (p *TelnetPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanRes } } - // 所有凭据都失败 return &ScanResult{ Success: false, Service: "telnet", - Error: fmt.Errorf("未发现弱密码或未授权访问"), + Error: fmt.Errorf("未发现弱密码"), } } -// testUnauthAccess 测试未授权访问 func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -114,7 +83,6 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - // 读取欢迎信息 buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { @@ -123,7 +91,6 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn welcome := string(buffer[:n]) - // 检查是否直接进入shell(无需认证) if strings.Contains(welcome, "$") || strings.Contains(welcome, "#") || strings.Contains(welcome, ">") { return &ScanResult{ Success: true, @@ -135,7 +102,6 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn return nil } -// testCredential 测试单个凭据 func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -147,7 +113,6 @@ func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - // 读取欢迎信息 buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { @@ -155,17 +120,12 @@ func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo } data := string(buffer[:n]) - - // 处理Telnet协议字符 data = p.cleanTelnetData(data) - // 查找用户名提示 if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") { - // 发送用户名 conn.Write([]byte(cred.Username + "\r\n")) time.Sleep(500 * time.Millisecond) - // 读取密码提示 n, err = conn.Read(buffer) if err != nil { return false @@ -173,11 +133,9 @@ func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo data = string(buffer[:n]) if strings.Contains(strings.ToLower(data), "password") { - // 发送密码 conn.Write([]byte(cred.Password + "\r\n")) time.Sleep(1 * time.Second) - // 读取认证结果 n, err = conn.Read(buffer) if err != nil { return false @@ -186,7 +144,6 @@ func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo result := string(buffer[:n]) result = p.cleanTelnetData(result) - // 检查是否认证成功 return p.isLoginSuccessful(result) } } @@ -194,99 +151,15 @@ func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo return false } -// connectTelnet 建立认证后的Telnet连接 -func (p *TelnetPlugin) connectTelnet(ctx context.Context, info *common.HostInfo, creds Credential) (net.Conn, error) { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) - if err != nil { - return nil, err - } - conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - - // 读取欢迎信息 - buffer := make([]byte, 1024) - n, err := conn.Read(buffer) - if err != nil { - conn.Close() - return nil, err - } - - data := string(buffer[:n]) - data = p.cleanTelnetData(data) - - // 检查是否需要认证 - if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") { - // 发送用户名 - conn.Write([]byte(creds.Username + "\r\n")) - time.Sleep(500 * time.Millisecond) - - // 读取密码提示 - n, err = conn.Read(buffer) - if err != nil { - conn.Close() - return nil, err - } - - data = string(buffer[:n]) - if strings.Contains(strings.ToLower(data), "password") { - // 发送密码 - conn.Write([]byte(creds.Password + "\r\n")) - time.Sleep(1 * time.Second) - - // 读取认证结果 - n, err = conn.Read(buffer) - if err != nil { - conn.Close() - return nil, err - } - - result := string(buffer[:n]) - result = p.cleanTelnetData(result) - - if !p.isLoginSuccessful(result) { - conn.Close() - return nil, fmt.Errorf("认证失败") - } - } - } - - return conn, nil -} - -// executeCommand 执行命令 -func (p *TelnetPlugin) executeCommand(conn net.Conn, command string) (string, error) { - // 发送命令 - _, err := conn.Write([]byte(command + "\r\n")) - if err != nil { - return "", err - } - - time.Sleep(1 * time.Second) - - // 读取结果 - buffer := make([]byte, 4096) - n, err := conn.Read(buffer) - if err != nil { - return "", err - } - - return string(buffer[:n]), nil -} - -// cleanTelnetData 清理Telnet协议数据 func (p *TelnetPlugin) cleanTelnetData(data string) string { - // 移除Telnet协议字符 cleaned := "" for i := 0; i < len(data); i++ { b := data[i] - // 跳过Telnet命令字符 (IAC = 255) if b == 255 && i+2 < len(data) { - i += 2 // 跳过IAC及其参数 + i += 2 continue } - // 保留可打印字符 if b >= 32 && b <= 126 || b == '\r' || b == '\n' { cleaned += string(b) } @@ -294,11 +167,9 @@ func (p *TelnetPlugin) cleanTelnetData(data string) string { return cleaned } -// isLoginSuccessful 判断是否登录成功 func (p *TelnetPlugin) isLoginSuccessful(data string) bool { data = strings.ToLower(data) - // 成功标志 successIndicators := []string{"$", "#", ">", "welcome", "last login"} for _, indicator := range successIndicators { if strings.Contains(data, indicator) { @@ -306,7 +177,6 @@ func (p *TelnetPlugin) isLoginSuccessful(data string) bool { } } - // 失败标志 failIndicators := []string{"incorrect", "failed", "denied", "invalid", "login:"} for _, indicator := range failIndicators { if strings.Contains(data, indicator) { @@ -317,28 +187,6 @@ func (p *TelnetPlugin) isLoginSuccessful(data string) bool { return false } -// cleanCommandOutput 清理命令输出 -func (p *TelnetPlugin) cleanCommandOutput(output string) string { - lines := strings.Split(output, "\n") - var cleanLines []string - - for _, line := range lines { - line = strings.TrimSpace(line) - // 跳过命令回显和提示符 - if line != "" && !strings.HasSuffix(line, "$") && !strings.HasSuffix(line, "#") && !strings.HasSuffix(line, ">") { - cleanLines = append(cleanLines, line) - } - } - - result := strings.Join(cleanLines, "\n") - if len(result) > 1000 { - result = result[:1000] + "... (输出截断)" - } - - return result -} - -// identifyService 服务识别 - 检测Telnet服务 func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -354,7 +202,6 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - // 读取欢迎信息 buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { @@ -370,14 +217,14 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf var banner string if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") { - banner = "Telnet远程终端服务 (需要认证)" + banner = "Telnet远程终端服务" } else if strings.Contains(data, "$") || strings.Contains(data, "#") || strings.Contains(data, ">") { banner = "Telnet远程终端服务 (无认证)" } else { banner = "Telnet远程终端服务" } - common.LogSuccess(i18n.GetText("telnet_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("Telnet %s %s", target, banner)) return &ScanResult{ Success: true, @@ -386,7 +233,6 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf } } -// init 自动注册插件 func init() { RegisterPlugin("telnet", func() Plugin { return NewTelnetPlugin() diff --git a/plugins/services/vnc.go b/plugins/services/vnc.go index c8ffca1..d7f2a75 100644 --- a/plugins/services/vnc.go +++ b/plugins/services/vnc.go @@ -10,88 +10,70 @@ import ( "time" "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" ) -// VNCPlugin VNC远程桌面服务扫描和利用插件 - 包含屏幕信息收集利用功能 type VNCPlugin struct { name string ports []int } -// NewVNCPlugin 创建VNC插件 func NewVNCPlugin() *VNCPlugin { return &VNCPlugin{ name: "vnc", - ports: []int{5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909}, // VNC端口 + ports: []int{5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909}, } } -// GetName 实现Plugin接口 func (p *VNCPlugin) GetName() string { return p.name } -// GetPorts 实现Plugin接口 func (p *VNCPlugin) GetPorts() []int { return p.ports } -// Scan 执行VNC扫描 - 弱密码检测和未授权访问检测 func (p *VNCPlugin) 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) } - // 首先测试未授权访问 if result := p.testUnauthAccess(ctx, info); result != nil && result.Success { - common.LogSuccess(i18n.GetText("vnc_unauth_success", target)) + common.LogSuccess(fmt.Sprintf("VNC %s 未授权访问", target)) return result } - // 生成测试密码(VNC通常只有密码,没有用户名) - passwords := p.generatePasswords() - - // 逐个测试密码 - for _, password := range passwords { - // 检查Context是否被取消 - select { - case <-ctx.Done(): - return &ScanResult{ - Success: false, - Service: "vnc", - Error: ctx.Err(), - } - default: + credentials := GenerateCredentials("vnc") + if len(credentials) == 0 { + return &ScanResult{ + Success: false, + Service: "vnc", + Error: fmt.Errorf("没有可用的测试凭据"), } + } - // 测试密码 - if p.testPassword(ctx, info, password) { - // VNC认证成功 - common.LogSuccess(i18n.GetText("vnc_scan_success", target, password)) + for _, cred := range credentials { + if p.testCredential(ctx, info, cred) { + common.LogSuccess(fmt.Sprintf("VNC %s %s", target, cred.Password)) return &ScanResult{ Success: true, Service: "vnc", - Username: "", // VNC没有用户名概念 - Password: password, + Username: "", + Password: cred.Password, } } } - // 所有密码都失败 return &ScanResult{ Success: false, Service: "vnc", - Error: fmt.Errorf("未发现弱密码或未授权访问"), + Error: fmt.Errorf("未发现弱密码"), } } -// testUnauthAccess 测试未授权访问 func (p *VNCPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -103,21 +85,18 @@ func (p *VNCPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo) conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - // VNC握手 _, err = p.performHandshake(conn) if err != nil { return nil } - // 读取认证类型 authTypes, err := p.readAuthTypes(conn) if err != nil { return nil } - // 检查是否支持无认证 for _, authType := range authTypes { - if authType == 1 { // None authentication + if authType == 1 { return &ScanResult{ Success: true, Service: "vnc", @@ -129,30 +108,8 @@ func (p *VNCPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo) return nil } -// generatePasswords 生成VNC测试密码 -func (p *VNCPlugin) generatePasswords() []string { - // VNC常见弱密码 - return []string{ - "", - "123456", - "password", - "admin", - "vnc", - "123", - "1234", - "12345", - "root", - "test", - "guest", - "user", - "welcome", - "qwerty", - "abc123", - } -} -// testPassword 测试单个密码 -func (p *VNCPlugin) testPassword(ctx context.Context, info *common.HostInfo, password string) bool { +func (p *VNCPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) @@ -163,22 +120,19 @@ func (p *VNCPlugin) testPassword(ctx context.Context, info *common.HostInfo, pas conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - // VNC握手 _, err = p.performHandshake(conn) if err != nil { return false } - // 读取认证类型 authTypes, err := p.readAuthTypes(conn) if err != nil { return false } - // 查找VNC认证类型 var hasVNCAuth bool for _, authType := range authTypes { - if authType == 2 { // VNC authentication + if authType == 2 { hasVNCAuth = true break } @@ -188,116 +142,37 @@ func (p *VNCPlugin) testPassword(ctx context.Context, info *common.HostInfo, pas return false } - // 选择VNC认证 if _, err := conn.Write([]byte{2}); err != nil { return false } - // 读取挑战 challenge := make([]byte, 16) if _, err := conn.Read(challenge); err != nil { return false } - // 准备密码(最多8字节,不足补零) key := make([]byte, 8) - copy(key, []byte(password)) + copy(key, []byte(cred.Password)) - // DES加密挑战 response, err := p.encryptChallenge(challenge, key) if err != nil { return false } - // 发送响应 if _, err := conn.Write(response); err != nil { return false } - // 读取认证结果 result := make([]byte, 4) if _, err := conn.Read(result); err != nil { return false } - // 检查认证结果 (0表示成功) return binary.BigEndian.Uint32(result) == 0 } -// connectVNC 建立认证后的VNC连接 -func (p *VNCPlugin) connectVNC(ctx context.Context, info *common.HostInfo, password string) (net.Conn, string, error) { - target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - - conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) - if err != nil { - return nil, "", err - } - conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - - // VNC握手 - version, err := p.performHandshake(conn) - if err != nil { - conn.Close() - return nil, "", err - } - - // 读取认证类型 - authTypes, err := p.readAuthTypes(conn) - if err != nil { - conn.Close() - return nil, "", err - } - - // 处理认证 - authenticated := false - for _, authType := range authTypes { - if authType == 1 && password == "" { // None authentication - conn.Write([]byte{1}) - authenticated = true - break - } else if authType == 2 && password != "" { // VNC authentication - conn.Write([]byte{2}) - - // 读取挑战 - challenge := make([]byte, 16) - conn.Read(challenge) - - // 准备密码 - key := make([]byte, 8) - copy(key, []byte(password)) - - // 加密挑战 - response, err := p.encryptChallenge(challenge, key) - if err != nil { - continue - } - - // 发送响应 - conn.Write(response) - - // 读取认证结果 - result := make([]byte, 4) - conn.Read(result) - - if binary.BigEndian.Uint32(result) == 0 { - authenticated = true - break - } - } - } - - if !authenticated { - conn.Close() - return nil, "", fmt.Errorf("认证失败") - } - - return conn, version, nil -} - -// performHandshake 执行VNC握手 func (p *VNCPlugin) performHandshake(conn net.Conn) (string, error) { - // 读取服务器版本 versionBuf := make([]byte, 12) if _, err := conn.Read(versionBuf); err != nil { return "", err @@ -305,7 +180,6 @@ func (p *VNCPlugin) performHandshake(conn net.Conn) (string, error) { version := strings.TrimSpace(string(versionBuf)) - // 发送客户端版本(使用相同版本) if _, err := conn.Write(versionBuf); err != nil { return "", err } @@ -313,9 +187,7 @@ func (p *VNCPlugin) performHandshake(conn net.Conn) (string, error) { return version, nil } -// readAuthTypes 读取认证类型 func (p *VNCPlugin) readAuthTypes(conn net.Conn) ([]byte, error) { - // 读取认证类型数量 countBuf := make([]byte, 1) if _, err := conn.Read(countBuf); err != nil { return nil, err @@ -326,7 +198,6 @@ func (p *VNCPlugin) readAuthTypes(conn net.Conn) ([]byte, error) { return nil, fmt.Errorf("无可用认证类型") } - // 读取认证类型列表 authTypes := make([]byte, count) if _, err := conn.Read(authTypes); err != nil { return nil, err @@ -335,9 +206,7 @@ func (p *VNCPlugin) readAuthTypes(conn net.Conn) ([]byte, error) { return authTypes, nil } -// encryptChallenge 使用DES加密挑战 func (p *VNCPlugin) encryptChallenge(challenge, key []byte) ([]byte, error) { - // VNC使用反向位序的DES reversedKey := make([]byte, 8) for i := 0; i < 8; i++ { reversedKey[i] = p.reverseBits(key[i]) @@ -355,7 +224,6 @@ func (p *VNCPlugin) encryptChallenge(challenge, key []byte) ([]byte, error) { return response, nil } -// reverseBits 反转字节的位序 func (p *VNCPlugin) reverseBits(b byte) byte { var result byte for i := 0; i < 8; i++ { @@ -364,49 +232,7 @@ func (p *VNCPlugin) reverseBits(b byte) byte { return result } -// getServerInfo 获取服务器信息 -func (p *VNCPlugin) getServerInfo(conn net.Conn) string { - var info strings.Builder - - // 发送客户端初始化消息 - conn.Write([]byte{1}) // 共享桌面 - - // 读取服务器初始化消息 - serverInit := make([]byte, 24) - n, err := conn.Read(serverInit) - if err != nil || n < 20 { - return "无法获取服务器信息" - } - - width := binary.BigEndian.Uint16(serverInit[0:2]) - height := binary.BigEndian.Uint16(serverInit[2:4]) - - info.WriteString(fmt.Sprintf("桌面分辨率: %dx%d\n", width, height)) - - // 读取桌面名称长度 - nameLength := binary.BigEndian.Uint32(serverInit[20:24]) - if nameLength > 0 && nameLength < 1024 { - nameBuf := make([]byte, nameLength) - if n, err := conn.Read(nameBuf); err == nil && n == int(nameLength) { - info.WriteString(fmt.Sprintf("桌面名称: %s\n", string(nameBuf))) - } - } - - return info.String() -} -// getDesktopInfo 获取桌面信息 -func (p *VNCPlugin) getDesktopInfo(conn net.Conn) string { - // 简化实现,实际需要完整的RFB协议支持 - return "桌面信息获取需要完整VNC客户端支持" -} - -// getPixelFormat 获取像素格式信息 -func (p *VNCPlugin) getPixelFormat(conn net.Conn) string { - return "像素格式信息获取需要完整VNC客户端支持" -} - -// identifyService 服务识别 - 检测VNC服务 func (p *VNCPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) @@ -422,7 +248,6 @@ func (p *VNCPlugin) identifyService(ctx context.Context, info *common.HostInfo) conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - // 尝试VNC握手 version, err := p.performHandshake(conn) if err != nil { return &ScanResult{ @@ -433,7 +258,7 @@ func (p *VNCPlugin) identifyService(ctx context.Context, info *common.HostInfo) } banner := fmt.Sprintf("VNC远程桌面服务 (%s)", version) - common.LogSuccess(i18n.GetText("vnc_service_identified", target, banner)) + common.LogSuccess(fmt.Sprintf("VNC %s %s", target, banner)) return &ScanResult{ Success: true, @@ -442,7 +267,6 @@ func (p *VNCPlugin) identifyService(ctx context.Context, info *common.HostInfo) } } -// init 自动注册插件 func init() { RegisterPlugin("vnc", func() Plugin { return NewVNCPlugin()