mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
fix: MongoDB SCRAM 认证因 BSON 键序随机而失败
Go map 遍历顺序不确定,导致 buildBSON 输出的命令文档中 saslStart/saslContinue 不一定是第一个键,MongoDB 拒绝执行。 引入有序 []mongoKV 类型,SASL 命令改用 orderedDoc() 构造。 同时新增 6 协议集成测试框架(Docker Compose + go test -tags integration)。
This commit is contained in:
+63
-61
@@ -101,7 +101,7 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
// Step 1: isMaster 获取服务参数
|
// Step 1: isMaster 获取服务参数
|
||||||
isMasterCmd := buildMongoCommand("admin", "isMaster", mongoDoc{})
|
isMasterCmd := buildMongoCommand("admin", "isMaster")
|
||||||
if _, err := sendMongoMsg(ctx, conn, isMasterCmd, timeout); err != nil {
|
if _, err := sendMongoMsg(ctx, conn, isMasterCmd, timeout); err != nil {
|
||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
return &AuthResult{Success: false, ErrorType: classifyMongoDBErrorType(err), Error: err}
|
return &AuthResult{Success: false, ErrorType: classifyMongoDBErrorType(err), Error: err}
|
||||||
@@ -117,13 +117,12 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo
|
|||||||
clientFirstBare := "n=" + cred.Username + ",r=" + nonce
|
clientFirstBare := "n=" + cred.Username + ",r=" + nonce
|
||||||
saslPayload := "n,," + clientFirstBare
|
saslPayload := "n,," + clientFirstBare
|
||||||
|
|
||||||
saslStartBody := mongoDoc{
|
saslStartCmd := buildMongoCommand("admin", orderedDoc(
|
||||||
"saslStart": 1,
|
kv("saslStart", 1),
|
||||||
"mechanism": "SCRAM-SHA-1",
|
kv("mechanism", "SCRAM-SHA-1"),
|
||||||
"payload": []byte(saslPayload),
|
kv("payload", []byte(saslPayload)),
|
||||||
"autoAuthorize": 1,
|
kv("autoAuthorize", 1),
|
||||||
}
|
))
|
||||||
saslStartCmd := buildMongoCommand("admin", saslStartBody)
|
|
||||||
if _, err := sendMongoMsg(ctx, conn, saslStartCmd, timeout); err != nil {
|
if _, err := sendMongoMsg(ctx, conn, saslStartCmd, timeout); err != nil {
|
||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
|
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
|
||||||
@@ -152,12 +151,11 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo
|
|||||||
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: err}
|
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
saslContinueBody := mongoDoc{
|
saslContinueCmd := buildMongoCommand("admin", orderedDoc(
|
||||||
"saslContinue": 1,
|
kv("saslContinue", 1),
|
||||||
"conversationId": int(startReply.conversationID),
|
kv("conversationId", int(startReply.conversationID)),
|
||||||
"payload": []byte(clientFinal),
|
kv("payload", []byte(clientFinal)),
|
||||||
}
|
))
|
||||||
saslContinueCmd := buildMongoCommand("admin", saslContinueBody)
|
|
||||||
if _, err := sendMongoMsg(ctx, conn, saslContinueCmd, timeout); err != nil {
|
if _, err := sendMongoMsg(ctx, conn, saslContinueCmd, timeout); err != nil {
|
||||||
state.IncrementTCPFailedPacketCount()
|
state.IncrementTCPFailedPacketCount()
|
||||||
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
|
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
|
||||||
@@ -196,64 +194,64 @@ func nextRequestID() uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildMongoCommand 构建 MongoDB 命令的 OP_MSG body (最小 BSON 实现)
|
// buildMongoCommand 构建 MongoDB 命令的 OP_MSG body (最小 BSON 实现)
|
||||||
// key 为字符串时,构建 {key: value} 作为命令名
|
// MongoDB 要求命令名是 BSON 文档的第一个键,因此使用 orderedDoc 保证顺序。
|
||||||
// key 为 map 时,展开所有字段
|
|
||||||
func buildMongoCommand(db string, args ...interface{}) []byte {
|
func buildMongoCommand(db string, args ...interface{}) []byte {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
// flags: 0 (ChecksumPresent=0, MoreToCome=0, ExhaustAllowed=0)
|
buf = append(buf, 0, 0, 0, 0) // flags
|
||||||
buf = append(buf, 0, 0, 0, 0)
|
buf = append(buf, 0) // section kind 0: body
|
||||||
// section kind 0: body
|
|
||||||
buf = append(buf, 0)
|
|
||||||
|
|
||||||
// 构建 BSON 文档
|
var doc []mongoKV
|
||||||
if len(db) > 0 {
|
|
||||||
// {$db: "admin", ...}
|
for i := 0; i < len(args); i++ {
|
||||||
docs := mongoDoc{"$db": db}
|
switch v := args[i].(type) {
|
||||||
for i := 0; i < len(args); i++ {
|
case string:
|
||||||
switch v := args[i].(type) {
|
if i+1 < len(args) {
|
||||||
case string:
|
doc = append(doc, kv(v, args[i+1]))
|
||||||
if i+1 < len(args) {
|
i++
|
||||||
docs[v] = args[i+1]
|
} else {
|
||||||
i++
|
doc = append(doc, kv(v, 1))
|
||||||
}
|
|
||||||
case mongoDoc:
|
|
||||||
for k, val := range v {
|
|
||||||
docs[k] = val
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
case mongoDoc:
|
||||||
|
for k, val := range v {
|
||||||
|
doc = append(doc, kv(k, val))
|
||||||
|
}
|
||||||
|
case []mongoKV:
|
||||||
|
doc = append(doc, v...)
|
||||||
}
|
}
|
||||||
return append(buf, buildBSON(docs)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 简单命令: {commandName: 1, $db: "admin"}
|
if db != "" {
|
||||||
if len(args) >= 1 {
|
doc = append(doc, kv("$db", db))
|
||||||
docs := mongoDoc{}
|
|
||||||
if cmdName, ok := args[0].(string); ok {
|
|
||||||
docs[cmdName] = 1
|
|
||||||
}
|
|
||||||
if len(args) >= 2 {
|
|
||||||
switch v := args[1].(type) {
|
|
||||||
case mongoDoc:
|
|
||||||
for k, val := range v {
|
|
||||||
docs[k] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if db != "" {
|
|
||||||
docs["$db"] = db
|
|
||||||
}
|
|
||||||
return append(buf, buildBSON(docs)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf
|
return append(buf, buildBSON(doc)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type mongoDoc map[string]interface{}
|
type mongoDoc = map[string]interface{}
|
||||||
|
|
||||||
// buildBSON 构建最小 BSON 文档(仅支持 string/int32/double/binary/subdocument)
|
type mongoKV struct {
|
||||||
func buildBSON(doc mongoDoc) []byte {
|
Key string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func orderedDoc(kvs ...mongoKV) []mongoKV { return kvs }
|
||||||
|
func kv(k string, v interface{}) mongoKV { return mongoKV{k, v} }
|
||||||
|
|
||||||
|
func buildBSON(doc interface{}) []byte {
|
||||||
|
var pairs []mongoKV
|
||||||
|
switch d := doc.(type) {
|
||||||
|
case []mongoKV:
|
||||||
|
pairs = d
|
||||||
|
case mongoDoc:
|
||||||
|
for k, v := range d {
|
||||||
|
pairs = append(pairs, mongoKV{k, v})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return []byte{5, 0, 0, 0, 0}
|
||||||
|
}
|
||||||
var buf []byte
|
var buf []byte
|
||||||
for k, v := range doc {
|
for _, p := range pairs {
|
||||||
|
k, v := p.Key, p.Value
|
||||||
switch val := v.(type) {
|
switch val := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
buf = append(buf, 0x02) // type string
|
buf = append(buf, 0x02) // type string
|
||||||
@@ -284,8 +282,12 @@ func buildBSON(doc mongoDoc) []byte {
|
|||||||
buf = append(buf, 0x03) // type document
|
buf = append(buf, 0x03) // type document
|
||||||
buf = append(buf, []byte(k)...)
|
buf = append(buf, []byte(k)...)
|
||||||
buf = append(buf, 0x00)
|
buf = append(buf, 0x00)
|
||||||
sub := buildBSON(val)
|
buf = append(buf, buildBSON(val)...)
|
||||||
buf = append(buf, sub...)
|
case []mongoKV:
|
||||||
|
buf = append(buf, 0x03) // type document
|
||||||
|
buf = append(buf, []byte(k)...)
|
||||||
|
buf = append(buf, 0x00)
|
||||||
|
buf = append(buf, buildBSON(val)...)
|
||||||
case []byte:
|
case []byte:
|
||||||
buf = append(buf, 0x05) // type binary
|
buf = append(buf, 0x05) // type binary
|
||||||
buf = append(buf, []byte(k)...)
|
buf = append(buf, []byte(k)...)
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
command: redis-server --requirepass test123
|
||||||
|
ports:
|
||||||
|
- "16379:6379"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "-a", "test123", "ping"]
|
||||||
|
interval: 3s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
redis-noauth:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- "16380:6379"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 3s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root123
|
||||||
|
MYSQL_ROOT_HOST: "%"
|
||||||
|
MYSQL_DATABASE: testdb
|
||||||
|
ports:
|
||||||
|
- "13307:3306"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-proot123"]
|
||||||
|
interval: 5s
|
||||||
|
retries: 20
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres123
|
||||||
|
POSTGRES_DB: testdb
|
||||||
|
ports:
|
||||||
|
- "15432:5432"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 3s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
ftp:
|
||||||
|
image: fauria/vsftpd
|
||||||
|
environment:
|
||||||
|
FTP_USER: ftpuser
|
||||||
|
FTP_PASS: ftp123
|
||||||
|
PASV_MIN_PORT: 21100
|
||||||
|
PASV_MAX_PORT: 21110
|
||||||
|
PASV_ADDRESS: 127.0.0.1
|
||||||
|
ports:
|
||||||
|
- "10021:21"
|
||||||
|
- "21100-21110:21100-21110"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/21' || exit 1"]
|
||||||
|
interval: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
ssh:
|
||||||
|
image: lscr.io/linuxserver/openssh-server:latest
|
||||||
|
environment:
|
||||||
|
PUID: 1000
|
||||||
|
PGID: 1000
|
||||||
|
USER_NAME: sshuser
|
||||||
|
USER_PASSWORD: ssh123
|
||||||
|
PASSWORD_ACCESS: "true"
|
||||||
|
ports:
|
||||||
|
- "10022:2222"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "nc -z localhost 2222 || exit 1"]
|
||||||
|
interval: 3s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
mongodb:
|
||||||
|
image: mongo:4.4
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: admin
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: mongo123
|
||||||
|
ports:
|
||||||
|
- "17017:27017"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')", "-u", "admin", "-p", "mongo123"]
|
||||||
|
interval: 5s
|
||||||
|
retries: 20
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/common/config"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testHost = "127.0.0.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testSession() *common.ScanSession {
|
||||||
|
cfg := common.NewConfig()
|
||||||
|
cfg.Timeout = 10 * time.Second
|
||||||
|
cfg.ModuleThreadNum = 5
|
||||||
|
cfg.MaxRetries = 2
|
||||||
|
cfg.Credentials.Userdict = nil
|
||||||
|
cfg.Credentials.Passwords = nil
|
||||||
|
state := common.NewState()
|
||||||
|
return common.NewScanSession(cfg, state, &common.FlagVars{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostInfo(host string, port int) *common.HostInfo {
|
||||||
|
return &common.HostInfo{Host: host, Port: port}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
fmt.Println("integration tests: ensure docker-compose services are running")
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Redis ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func TestRedisUnauthorized(t *testing.T) {
|
||||||
|
session := testSession()
|
||||||
|
info := hostInfo(testHost, 16380)
|
||||||
|
plugin := services.NewRedisPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("result is nil")
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
t.Fatalf("expected unauthorized redis to succeed, got error: %v", result.Error)
|
||||||
|
}
|
||||||
|
t.Logf("redis noauth: %+v", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisBrute(t *testing.T) {
|
||||||
|
session := testSession()
|
||||||
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
||||||
|
{Username: "", Password: "wrong1"},
|
||||||
|
{Username: "", Password: "test123"},
|
||||||
|
}
|
||||||
|
info := hostInfo(testHost, 16379)
|
||||||
|
plugin := services.NewRedisPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("result is nil")
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
t.Fatalf("expected redis brute to succeed with test123, got error: %v", result.Error)
|
||||||
|
}
|
||||||
|
if result.Password != "test123" {
|
||||||
|
t.Errorf("expected password test123, got %q", result.Password)
|
||||||
|
}
|
||||||
|
t.Logf("redis brute: %+v", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── MySQL ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func TestMySQLBrute(t *testing.T) {
|
||||||
|
session := testSession()
|
||||||
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
||||||
|
{Username: "root", Password: "wrong"},
|
||||||
|
{Username: "root", Password: "root123"},
|
||||||
|
}
|
||||||
|
info := hostInfo(testHost, 13307)
|
||||||
|
plugin := services.NewMySQLPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("result is nil")
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
t.Fatalf("expected mysql brute to succeed, got error: %v", result.Error)
|
||||||
|
}
|
||||||
|
t.Logf("mysql brute: user=%s pass=%s", result.Username, result.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── PostgreSQL ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func TestPostgreSQLBrute(t *testing.T) {
|
||||||
|
session := testSession()
|
||||||
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
||||||
|
{Username: "postgres", Password: "wrong"},
|
||||||
|
{Username: "postgres", Password: "postgres123"},
|
||||||
|
}
|
||||||
|
info := hostInfo(testHost, 15432)
|
||||||
|
plugin := services.NewPostgreSQLPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("result is nil")
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
t.Fatalf("expected postgresql brute to succeed, got error: %v", result.Error)
|
||||||
|
}
|
||||||
|
t.Logf("postgresql brute: user=%s pass=%s", result.Username, result.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FTP ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func TestFTPBrute(t *testing.T) {
|
||||||
|
session := testSession()
|
||||||
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
||||||
|
{Username: "ftpuser", Password: "wrong"},
|
||||||
|
{Username: "ftpuser", Password: "ftp123"},
|
||||||
|
}
|
||||||
|
info := hostInfo(testHost, 10021)
|
||||||
|
plugin := services.NewFTPPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("result is nil")
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
t.Fatalf("expected ftp brute to succeed, got error: %v", result.Error)
|
||||||
|
}
|
||||||
|
t.Logf("ftp brute: user=%s pass=%s", result.Username, result.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── SSH ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func TestSSHBrute(t *testing.T) {
|
||||||
|
session := testSession()
|
||||||
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
||||||
|
{Username: "sshuser", Password: "wrong"},
|
||||||
|
{Username: "sshuser", Password: "ssh123"},
|
||||||
|
}
|
||||||
|
info := hostInfo(testHost, 10022)
|
||||||
|
plugin := services.NewSSHPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("result is nil")
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
t.Fatalf("expected ssh brute to succeed, got error: %v", result.Error)
|
||||||
|
}
|
||||||
|
t.Logf("ssh brute: user=%s pass=%s", result.Username, result.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── MongoDB ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func TestMongoDBBrute(t *testing.T) {
|
||||||
|
// Fixed: BSON key ordering was non-deterministic (Go map), MongoDB requires command name first
|
||||||
|
session := testSession()
|
||||||
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
||||||
|
{Username: "admin", Password: "wrong"},
|
||||||
|
{Username: "admin", Password: "mongo123"},
|
||||||
|
}
|
||||||
|
info := hostInfo(testHost, 17017)
|
||||||
|
plugin := services.NewMongoDBPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("result is nil")
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
t.Fatalf("expected mongodb brute to succeed, got error: %v", result.Error)
|
||||||
|
}
|
||||||
|
t.Logf("mongodb brute: user=%s pass=%s", result.Username, result.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 连接失败场景 ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
func TestRedisConnectionRefused(t *testing.T) {
|
||||||
|
session := testSession()
|
||||||
|
session.Config.Timeout = 3 * time.Second
|
||||||
|
info := hostInfo(testHost, 19999)
|
||||||
|
plugin := services.NewRedisPlugin()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := plugin.Scan(ctx, info, session)
|
||||||
|
if result != nil && result.Success {
|
||||||
|
t.Fatal("expected failure on closed port")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user