重构服务插件架构,移除冗余功能

- 统一所有服务插件的实现模式,移除i18n国际化依赖
- 删除硬编码的备份凭据列表,统一使用GenerateCredentials()
- 移除过度工程化的Context取消检查
- 清理exploitation功能,专注于弱密码检测和服务识别
- 简化代码结构,移除冗余注释和说明文档
- 优化19个服务插件:activemq, cassandra, elasticsearch, ftp, kafka, ldap, memcached, mongodb, mssql, mysql, neo4j, oracle, postgresql, rabbitmq, rsync, smtp, snmp, telnet, vnc
- 代码总量减少约40%,提升维护效率

此次重构确保插件架构的一致性和简洁性
This commit is contained in:
ZacharyZcR 2025-08-26 17:44:43 +08:00
parent 8a79f3cf0f
commit 2e449c74ef
19 changed files with 328 additions and 3619 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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管理端口通常是15672AMQP端口是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,

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()