fscan/Plugins/services/mongodb/connector.go
ZacharyZcR 7e4d5a0bcd feat: 实现MongoDB NoSQL数据库专业扫描插件
- 新增MongoDB协议识别和未授权访问检测
- 支持Wire Protocol(OP_MSG和OP_QUERY)
- 实现connector/exploiter/plugin三层架构
- 添加MongoDB专用国际化消息
- 自动注册到插件系统
2025-08-08 14:02:13 +08:00

194 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}
}