From 7e4d5a0bcd9216f6d22224e3572f90c9ab436db1 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 8 Aug 2025 14:02:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0MongoDB=20NoSQL?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E4=B8=93=E4=B8=9A=E6=89=AB=E6=8F=8F?= =?UTF-8?q?=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增MongoDB协议识别和未授权访问检测 - 支持Wire Protocol(OP_MSG和OP_QUERY) - 实现connector/exploiter/plugin三层架构 - 添加MongoDB专用国际化消息 - 自动注册到插件系统 --- Common/i18n/messages/plugins.go | 18 ++ Core/Registry.go | 1 + Plugins/services/mongodb/connector.go | 194 ++++++++++++++++++++ Plugins/services/mongodb/exploiter.go | 36 ++++ Plugins/services/mongodb/plugin.go | 247 ++++++++++++++++++++++++++ 5 files changed, 496 insertions(+) create mode 100644 Plugins/services/mongodb/connector.go create mode 100644 Plugins/services/mongodb/exploiter.go create mode 100644 Plugins/services/mongodb/plugin.go diff --git a/Common/i18n/messages/plugins.go b/Common/i18n/messages/plugins.go index 2accd71..0696212 100644 --- a/Common/i18n/messages/plugins.go +++ b/Common/i18n/messages/plugins.go @@ -682,4 +682,22 @@ var PluginMessages = map[string]map[string]string{ LangZH: "Modbus连接失败: %v", LangEN: "Modbus connection failed: %v", }, + + // ========================= MongoDB插件消息 ========================= + "mongodb_unauth_access": { + LangZH: "MongoDB服务 %s 未授权访问成功", + LangEN: "MongoDB service %s unauthorized access successful", + }, + "mongodb_service_identified": { + LangZH: "MongoDB服务识别成功: %s - %s", + LangEN: "MongoDB service identified: %s - %s", + }, + "mongodb_connection_failed": { + LangZH: "MongoDB连接失败: %v", + LangEN: "MongoDB connection failed: %v", + }, + "mongodb_auth_failed": { + LangZH: "MongoDB认证失败: %v", + LangEN: "MongoDB authentication failed: %v", + }, } \ No newline at end of file diff --git a/Core/Registry.go b/Core/Registry.go index 90dcd84..810f3d9 100644 --- a/Core/Registry.go +++ b/Core/Registry.go @@ -14,6 +14,7 @@ import ( _ "github.com/shadow1ng/fscan/plugins/services/ldap" _ "github.com/shadow1ng/fscan/plugins/services/memcached" _ "github.com/shadow1ng/fscan/plugins/services/modbus" + _ "github.com/shadow1ng/fscan/plugins/services/mongodb" _ "github.com/shadow1ng/fscan/plugins/services/mysql" _ "github.com/shadow1ng/fscan/plugins/services/redis" _ "github.com/shadow1ng/fscan/plugins/services/ssh" diff --git a/Plugins/services/mongodb/connector.go b/Plugins/services/mongodb/connector.go new file mode 100644 index 0000000..e27b1cd --- /dev/null +++ b/Plugins/services/mongodb/connector.go @@ -0,0 +1,194 @@ +package mongodb + +import ( + "context" + "fmt" + "io" + "net" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MongoDBConnection MongoDB连接包装器 +type MongoDBConnection struct { + client net.Conn + target string +} + +// MongoDBConnector MongoDB连接器实现 +type MongoDBConnector struct { + host string + port string +} + +// NewMongoDBConnector 创建MongoDB连接器 +func NewMongoDBConnector() *MongoDBConnector { + return &MongoDBConnector{} +} + +// Connect 连接到MongoDB服务 +func (c *MongoDBConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { + c.host = info.Host + c.port = info.Ports + + target := fmt.Sprintf("%s:%s", c.host, c.port) + timeout := time.Duration(common.Timeout) * time.Second + + // 建立TCP连接 + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { + return nil, fmt.Errorf("MongoDB连接失败: %v", err) + } + + return &MongoDBConnection{ + client: conn, + target: target, + }, nil +} + +// Authenticate 认证 - MongoDB通常检查未授权访问 +func (c *MongoDBConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { + mongoConn, ok := conn.(*MongoDBConnection) + if !ok { + return fmt.Errorf("无效的连接类型") + } + + // 在goroutine中进行操作,支持Context取消 + resultChan := make(chan error, 1) + + go func() { + // 设置操作超时 + timeout := time.Duration(common.Timeout) * time.Second + if err := mongoConn.client.SetDeadline(time.Now().Add(timeout)); err != nil { + resultChan <- fmt.Errorf("设置超时失败: %v", err) + return + } + + // 尝试未授权访问检测 + isUnauth, err := c.checkMongoDBUnauth(ctx, mongoConn) + if err != nil { + resultChan <- fmt.Errorf("MongoDB检测失败: %v", err) + return + } + + if isUnauth { + // 未授权访问成功 + resultChan <- nil + return + } + + // 如果没有未授权访问,尝试使用提供的凭据(如果有的话) + if cred.Username != "" && cred.Password != "" { + // TODO: 实现MongoDB认证 + resultChan <- fmt.Errorf("MongoDB需要认证但暂不支持密码认证") + return + } + + resultChan <- fmt.Errorf("MongoDB服务需要认证") + }() + + // 等待操作结果或Context取消 + select { + case err := <-resultChan: + return err + case <-ctx.Done(): + return fmt.Errorf("MongoDB操作超时: %v", ctx.Err()) + } +} + +// checkMongoDBUnauth 检测MongoDB未授权访问 +func (c *MongoDBConnector) checkMongoDBUnauth(ctx context.Context, mongoConn *MongoDBConnection) (bool, error) { + // 先尝试OP_MSG查询 + msgPacket := createOpMsgPacket() + reply, err := c.sendMongoQuery(ctx, mongoConn, msgPacket) + if err != nil { + // 失败则尝试OP_QUERY查询 + queryPacket := createOpQueryPacket() + reply, err = c.sendMongoQuery(ctx, mongoConn, queryPacket) + if err != nil { + return false, err + } + } + + // 检查响应结果 + if strings.Contains(reply, "totalLinesWritten") { + return true, nil + } + + return false, nil +} + +// sendMongoQuery 发送MongoDB查询 +func (c *MongoDBConnector) sendMongoQuery(ctx context.Context, mongoConn *MongoDBConnection, packet []byte) (string, error) { + // 检查上下文是否已取消 + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + // 发送查询包 + if _, err := mongoConn.client.Write(packet); err != nil { + return "", fmt.Errorf("发送查询失败: %v", err) + } + + // 再次检查上下文是否已取消 + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + // 读取响应 + reply := make([]byte, 2048) + count, err := mongoConn.client.Read(reply) + if err != nil && err != io.EOF { + return "", fmt.Errorf("读取响应失败: %v", err) + } + + if count == 0 { + return "", fmt.Errorf("收到空响应") + } + + return string(reply[:count]), nil +} + +// Close 关闭连接 +func (c *MongoDBConnector) Close(conn interface{}) error { + if mongoConn, ok := conn.(*MongoDBConnection); ok && mongoConn.client != nil { + return mongoConn.client.Close() + } + return nil +} + +// createOpMsgPacket 创建OP_MSG查询包 +func createOpMsgPacket() []byte { + return []byte{ + 0x69, 0x00, 0x00, 0x00, // messageLength + 0x39, 0x00, 0x00, 0x00, // requestID + 0x00, 0x00, 0x00, 0x00, // responseTo + 0xdd, 0x07, 0x00, 0x00, // opCode OP_MSG + 0x00, 0x00, 0x00, 0x00, // flagBits + // sections db.adminCommand({getLog: "startupWarnings"}) + 0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x02, 0x24, 0x64, 0x62, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x00, 0x03, 0x6c, 0x73, 0x69, 0x64, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x69, 0x64, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x81, 0xf8, 0x8e, 0x37, 0x7b, 0x4c, 0x97, 0x84, 0x4e, 0x90, 0x62, 0x5a, 0x54, 0x3c, 0x93, 0x00, 0x00, + } +} + +// createOpQueryPacket 创建OP_QUERY查询包 +func createOpQueryPacket() []byte { + return []byte{ + 0x48, 0x00, 0x00, 0x00, // messageLength + 0x02, 0x00, 0x00, 0x00, // requestID + 0x00, 0x00, 0x00, 0x00, // responseTo + 0xd4, 0x07, 0x00, 0x00, // opCode OP_QUERY + 0x00, 0x00, 0x00, 0x00, // flags + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x24, 0x63, 0x6d, 0x64, 0x00, // fullCollectionName admin.$cmd + 0x00, 0x00, 0x00, 0x00, // numberToSkip + 0x01, 0x00, 0x00, 0x00, // numberToReturn + // query db.adminCommand({getLog: "startupWarnings"}) + 0x21, 0x00, 0x00, 0x00, 0x2, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00, + } +} \ No newline at end of file diff --git a/Plugins/services/mongodb/exploiter.go b/Plugins/services/mongodb/exploiter.go new file mode 100644 index 0000000..46b311e --- /dev/null +++ b/Plugins/services/mongodb/exploiter.go @@ -0,0 +1,36 @@ +package mongodb + +import ( + "context" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MongoDBExploiter MongoDB利用器实现 - 最小化版本,不提供利用功能 +type MongoDBExploiter struct { + *base.BaseExploiter +} + +// NewMongoDBExploiter 创建MongoDB利用器 +func NewMongoDBExploiter() *MongoDBExploiter { + exploiter := &MongoDBExploiter{ + BaseExploiter: base.NewBaseExploiter("mongodb"), + } + + // MongoDB插件不提供利用功能 + exploiter.setupExploitMethods() + + return exploiter +} + +// setupExploitMethods 设置利用方法 +func (e *MongoDBExploiter) setupExploitMethods() { + // MongoDB插件不提供利用功能,仅进行未授权访问检测 +} + +// Exploit 利用接口实现 - 空实现 +func (e *MongoDBExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + // MongoDB插件不提供利用功能 + return nil, nil +} \ No newline at end of file diff --git a/Plugins/services/mongodb/plugin.go b/Plugins/services/mongodb/plugin.go new file mode 100644 index 0000000..1bf63dc --- /dev/null +++ b/Plugins/services/mongodb/plugin.go @@ -0,0 +1,247 @@ +package mongodb + +import ( + "context" + "fmt" + "io" + "net" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MongoDBPlugin MongoDB插件实现 +type MongoDBPlugin struct { + *base.ServicePlugin + exploiter *MongoDBExploiter +} + +// NewMongoDBPlugin 创建MongoDB插件 +func NewMongoDBPlugin() *MongoDBPlugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "mongodb", + Version: "2.0.0", + Author: "fscan-team", + Description: "MongoDB NoSQL数据库扫描和利用插件", + Category: "service", + Ports: []int{27017, 27018, 27019}, // 默认MongoDB端口 + Protocols: []string{"tcp"}, + Tags: []string{"mongodb", "nosql", "database", "unauthorized"}, + } + + // 创建连接器和服务插件 + connector := NewMongoDBConnector() + servicePlugin := base.NewServicePlugin(metadata, connector) + + // 创建MongoDB插件 + plugin := &MongoDBPlugin{ + ServicePlugin: servicePlugin, + exploiter: NewMongoDBExploiter(), + } + + // 设置能力 + plugin.SetCapabilities([]base.Capability{ + base.CapUnauthorized, + base.CapDataExtraction, + base.CapWeakPassword, // 将来可能支持弱密码扫描 + }) + + return plugin +} + +// Scan 重写扫描方法,检测未授权访问 +func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + // 如果禁用了暴力破解,只进行服务识别 + if common.DisableBrute { + return p.performServiceIdentification(ctx, info) + } + + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // MongoDB主要检查未授权访问 + unauthCred := &base.Credential{Username: "", Password: ""} + result, err := p.ScanCredential(ctx, info, unauthCred) + if err == nil && result.Success { + // 未授权访问成功 + common.LogSuccess(i18n.GetText("mongodb_unauth_access", target)) + + return &base.ScanResult{ + Success: true, + Service: "MongoDB", + Credentials: []*base.Credential{unauthCred}, + Extra: map[string]interface{}{ + "service": "MongoDB", + "port": info.Ports, + "unauthorized": true, + "access_type": "no_authentication", + }, + }, nil + } + + // 如果未授权访问失败,返回失败结果 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("MongoDB服务需要认证或连接失败"), + }, nil +} + +// generateCredentials MongoDB主要用于未授权访问检测 +func (p *MongoDBPlugin) generateCredentials() []*base.Credential { + // MongoDB主要检查未授权访问,但也可以尝试一些默认凭据 + credentials := []*base.Credential{ + {Username: "", Password: ""}, // 未授权访问 + } + + // 如果有MongoDB专用字典,添加常见凭据 + usernames := common.Userdict["mongodb"] + if len(usernames) == 0 { + usernames = []string{"admin", "root", "mongo", "mongodb"} + } + + // 添加一些常见的MongoDB凭据 + for _, username := range usernames { + credentials = append(credentials, &base.Credential{ + Username: username, + Password: "", // MongoDB常见空密码 + }) + credentials = append(credentials, &base.Credential{ + Username: username, + Password: username, // 用户名等于密码 + }) + } + + return credentials +} + +// Exploit 使用exploiter执行利用 +func (p *MongoDBPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + return p.exploiter.Exploit(ctx, info, creds) +} + +// GetExploitMethods 获取利用方法 +func (p *MongoDBPlugin) GetExploitMethods() []base.ExploitMethod { + return p.exploiter.GetExploitMethods() +} + +// IsExploitSupported 检查利用支持 +func (p *MongoDBPlugin) IsExploitSupported(method base.ExploitType) bool { + return p.exploiter.IsExploitSupported(method) +} + +// performServiceIdentification 执行MongoDB服务识别(-nobr模式) +func (p *MongoDBPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 尝试识别MongoDB服务 + mongoInfo, isMongo := p.identifyMongoDBService(ctx, info) + if isMongo { + // 记录服务识别成功 + common.LogSuccess(i18n.GetText("mongodb_service_identified", target, mongoInfo)) + + return &base.ScanResult{ + Success: true, + Service: "MongoDB", + Banner: mongoInfo, + Extra: map[string]interface{}{ + "service": "MongoDB", + "port": info.Ports, + "info": mongoInfo, + }, + }, nil + } + + // 如果无法识别为MongoDB,返回失败 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("无法识别为MongoDB服务"), + }, nil +} + +// identifyMongoDBService 通过协议识别MongoDB服务 +func (p *MongoDBPlugin) identifyMongoDBService(ctx context.Context, info *common.HostInfo) (string, bool) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + timeout := time.Duration(common.Timeout) * time.Second + + // 尝试建立TCP连接 + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { + return "", false + } + defer conn.Close() + + // 设置操作超时 + conn.SetDeadline(time.Now().Add(timeout)) + + // 尝试发送MongoDB查询来识别服务 + msgPacket := createOpMsgPacket() + if _, err := conn.Write(msgPacket); err != nil { + return "", false + } + + // 读取响应 + reply := make([]byte, 1024) + n, err := conn.Read(reply) + if err != nil && err != io.EOF { + // 尝试OP_QUERY查询 + queryPacket := createOpQueryPacket() + if _, err := conn.Write(queryPacket); err != nil { + return "", false + } + + n, err = conn.Read(reply) + if err != nil && err != io.EOF { + return "", false + } + } + + if n > 0 { + response := string(reply[:n]) + // 检查是否包含MongoDB相关内容 + if strings.Contains(response, "totalLinesWritten") || + strings.Contains(response, "MongoDB") || + strings.Contains(response, "WiredTiger") || + strings.Contains(response, "unauthorized") { + // 尝试提取版本信息 + if strings.Contains(response, "MongoDB") { + return "MongoDB服务 (已识别协议响应)", true + } + return "MongoDB服务", true + } + } + + return "", false +} + +// ============================================================================= +// 插件注册 +// ============================================================================= + +// RegisterMongoDBPlugin 注册MongoDB插件 +func RegisterMongoDBPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "mongodb", + Version: "2.0.0", + Author: "fscan-team", + Description: "MongoDB NoSQL数据库扫描和利用插件", + Category: "service", + Ports: []int{27017, 27018, 27019}, // 默认MongoDB端口 + Protocols: []string{"tcp"}, + Tags: []string{"mongodb", "nosql", "database", "unauthorized"}, + }, + func() base.Plugin { + return NewMongoDBPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("mongodb", factory) +} + +// 自动注册 +func init() { + RegisterMongoDBPlugin() +} \ No newline at end of file