mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
feat: 实现MongoDB NoSQL数据库专业扫描插件
- 新增MongoDB协议识别和未授权访问检测 - 支持Wire Protocol(OP_MSG和OP_QUERY) - 实现connector/exploiter/plugin三层架构 - 添加MongoDB专用国际化消息 - 自动注册到插件系统
This commit is contained in:
parent
fbe141cc80
commit
7e4d5a0bcd
@ -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",
|
||||
},
|
||||
}
|
@ -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"
|
||||
|
194
Plugins/services/mongodb/connector.go
Normal file
194
Plugins/services/mongodb/connector.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
36
Plugins/services/mongodb/exploiter.go
Normal file
36
Plugins/services/mongodb/exploiter.go
Normal file
@ -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
|
||||
}
|
247
Plugins/services/mongodb/plugin.go
Normal file
247
Plugins/services/mongodb/plugin.go
Normal file
@ -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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user