From 1c6f3b80d0e7bd23966a81b5dcf4b3955fc74289 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Tue, 16 Jun 2026 11:29:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Cassandra=20CQL=20=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E5=A4=B4=E7=BC=BA=E5=B0=91=20flags=20=E5=AD=97=E8=8A=82=20+=20?= =?UTF-8?q?version=20=E6=96=B9=E5=90=91=E4=BD=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cqlSend 写 8 字节头(缺 flags),实际 CQL v4 需要 9 字节。 version byte 0x84 是 response 方向,request 应为 0x04。 同时扩展集成测试至 17 个协议:新增 Memcached、Elasticsearch、 MSSQL、RabbitMQ、MQTT、LDAP、Cassandra、Neo4j、Kafka、SMTP。 --- plugins/services/cassandra.go | 15 +- tests/integration/docker-compose.yml | 114 +++++++++++++ tests/integration/integration_test.go | 223 ++++++++++++++++++++++++++ tests/integration/mosquitto.conf | 2 + 4 files changed, 347 insertions(+), 7 deletions(-) create mode 100644 tests/integration/mosquitto.conf diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index 2e8a14c..bc505d3 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -74,7 +74,7 @@ func (p *CassandraPlugin) createAuthFunc(info *common.HostInfo, config *common.C // // [1B version|flags] [2B stream] [1B opcode] [4B length] [body] const ( - cqlVersion = 0x84 // version=4, direction=request + cqlVersion = 0x04 // version=4, direction=request cqlOpStartup = 0x01 cqlOpAuthRsp = 0x0f cqlOpQuery = 0x07 @@ -178,12 +178,13 @@ func nextCQLStreamID() uint16 { func cqlSend(conn net.Conn, opcode byte, body []byte) error { id := nextCQLStreamID() - // frame: [1B version|flags] [2B stream] [1B opcode] [4B length] [body] - header := make([]byte, 8) - header[0] = cqlVersion - binary.BigEndian.PutUint16(header[1:3], id) - header[3] = opcode - binary.BigEndian.PutUint32(header[4:8], uint32(len(body))) + // CQL v4 frame: [1B version] [1B flags] [2B stream] [1B opcode] [4B length] [body] + header := make([]byte, 9) + header[0] = cqlVersion // 0x04 = request, version 4 + header[1] = 0x00 // flags + binary.BigEndian.PutUint16(header[2:4], id) + header[4] = opcode + binary.BigEndian.PutUint32(header[5:9], uint32(len(body))) buf := append(header, body...) _, err := conn.Write(buf) diff --git a/tests/integration/docker-compose.yml b/tests/integration/docker-compose.yml index 6455d9f..47bb720 100644 --- a/tests/integration/docker-compose.yml +++ b/tests/integration/docker-compose.yml @@ -87,3 +87,117 @@ services: test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')", "-u", "admin", "-p", "mongo123"] interval: 5s retries: 20 + + memcached: + image: memcached:1-alpine + ports: + - "11211:11211" + healthcheck: + test: ["CMD-SHELL", "echo stats | nc localhost 11211 | grep -q pid"] + interval: 3s + retries: 10 + + elasticsearch: + image: elasticsearch:7.17.24 + environment: + discovery.type: single-node + xpack.security.enabled: "false" + ES_JAVA_OPTS: "-Xms256m -Xmx256m" + ports: + - "19200:9200" + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health || exit 1"] + interval: 5s + retries: 20 + + mssql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + ACCEPT_EULA: "Y" + SA_PASSWORD: "MssqlTest123!" + MSSQL_PID: Express + ports: + - "11433:1433" + healthcheck: + test: ["CMD-SHELL", "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'MssqlTest123!' -Q 'SELECT 1' || exit 1"] + interval: 5s + retries: 30 + + rabbitmq: + image: rabbitmq:3-management-alpine + environment: + RABBITMQ_DEFAULT_USER: admin + RABBITMQ_DEFAULT_PASS: rabbit123 + ports: + - "15672:15672" + - "15673:5672" + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "check_running"] + interval: 5s + retries: 20 + + mqtt: + image: eclipse-mosquitto:2 + ports: + - "11883:1883" + volumes: + - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro + healthcheck: + test: ["CMD-SHELL", "mosquitto_sub -t '$$SYS/#' -C 1 -W 2 || exit 1"] + interval: 5s + retries: 10 + +openldap: + image: osixia/openldap:1.5.0 + environment: + LDAP_ORGANISATION: "Test" + LDAP_DOMAIN: "test.local" + LDAP_ADMIN_PASSWORD: "ldap123" + ports: + - "10389:389" + healthcheck: + test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -b 'dc=test,dc=local' -D 'cn=admin,dc=test,dc=local' -w ldap123 || exit 1"] + interval: 5s + retries: 10 + + cassandra: + image: cassandra:4.1 + environment: + CASSANDRA_AUTHENTICATOR: AllowAllAuthenticator + ports: + - "19042:9042" + healthcheck: + test: ["CMD-SHELL", "cqlsh -e 'DESCRIBE CLUSTER' || exit 1"] + interval: 10s + retries: 30 + + neo4j: + image: neo4j:5 + environment: + NEO4J_AUTH: "neo4j/neo4jtest123" + ports: + - "17687:7687" + - "17474:7474" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:7474 || exit 1"] + interval: 5s + retries: 20 + + kafka: + image: apache/kafka:3.7.0 + ports: + - "19092:9092" + healthcheck: + test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list || exit 1"] + interval: 10s + retries: 20 + + smtp: + image: mailhog/mailhog + ports: + - "11025:1025" + - "18025:8025" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8025/api/v2/messages || exit 1"] + interval: 5s + retries: 10 diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index f8e03c8..fad38d2 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -204,6 +204,229 @@ func TestMongoDBBrute(t *testing.T) { t.Logf("mongodb brute: user=%s pass=%s", result.Username, result.Password) } +// ── Memcached ────────────────────────────────────────────────── + +func TestMemcachedUnauthorized(t *testing.T) { + session := testSession() + info := hostInfo(testHost, 11211) + plugin := services.NewMemcachedPlugin() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + result := plugin.Scan(ctx, info, session) + if result == nil { + t.Fatal("result is nil") + } + if !result.Success { + t.Fatalf("expected memcached to succeed, got error: %v", result.Error) + } + t.Logf("memcached: type=%s banner=%s", result.Type, result.Banner) +} + +// ── Elasticsearch ────────────────────────────────────────────── + +func TestElasticsearchUnauthorized(t *testing.T) { + session := testSession() + info := hostInfo(testHost, 19200) + plugin := services.NewElasticsearchPlugin() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + result := plugin.Scan(ctx, info, session) + if result == nil { + t.Fatal("result is nil") + } + if !result.Success { + t.Fatalf("expected elasticsearch to succeed, got error: %v", result.Error) + } + t.Logf("elasticsearch: type=%s vulinfo=%s", result.Type, result.VulInfo) +} + +// ── MSSQL ────────────────────────────────────────────────────── + +func TestMSSQLBrute(t *testing.T) { + session := testSession() + session.Config.Credentials.UserPassPairs = []config.CredentialPair{ + {Username: "sa", Password: "wrong"}, + {Username: "sa", Password: "MssqlTest123!"}, + } + info := hostInfo(testHost, 11433) + plugin := services.NewMSSQLPlugin() + + 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 mssql brute to succeed, got error: %v", result.Error) + } + t.Logf("mssql brute: user=%s pass=%s", result.Username, result.Password) +} + +// ── RabbitMQ ─────────────────────────────────────────────────── + +func TestRabbitMQBrute(t *testing.T) { + session := testSession() + session.Config.Credentials.UserPassPairs = []config.CredentialPair{ + {Username: "admin", Password: "wrong"}, + {Username: "admin", Password: "rabbit123"}, + } + info := hostInfo(testHost, 15672) + plugin := services.NewRabbitMQPlugin() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + result := plugin.Scan(ctx, info, session) + if result == nil { + t.Fatal("result is nil") + } + if !result.Success { + t.Fatalf("expected rabbitmq brute to succeed, got error: %v", result.Error) + } + t.Logf("rabbitmq brute: user=%s pass=%s", result.Username, result.Password) +} + +// ── MQTT ─────────────────────────────────────────────────────── + +func TestMQTTServiceDetect(t *testing.T) { + session := testSession() + info := hostInfo(testHost, 11883) + plugin := services.NewMQTTPlugin() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + result := plugin.Scan(ctx, info, session) + if result == nil { + t.Fatal("result is nil") + } + if !result.Success { + t.Fatalf("expected mqtt service detect to succeed, got error: %v", result.Error) + } + t.Logf("mqtt: service=%s banner=%s", result.Service, result.Banner) +} + +// ── SMB ──────────────────────────────────────────────────────── + +func TestSMBBrute(t *testing.T) { + t.Skip("SMB requires port 445 which is reserved on WSL2") +} + +// ── LDAP ─────────────────────────────────────────────────────── + +func TestLDAPBrute(t *testing.T) { + session := testSession() + session.Config.Credentials.UserPassPairs = []config.CredentialPair{ + {Username: "cn=admin,dc=test,dc=local", Password: "wrong"}, + {Username: "cn=admin,dc=test,dc=local", Password: "ldap123"}, + } + info := hostInfo(testHost, 10389) + plugin := services.NewLDAPPlugin() + + 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 ldap brute to succeed, got error: %v", result.Error) + } + t.Logf("ldap brute: user=%s pass=%s", result.Username, result.Password) +} + +// ── Cassandra ────────────────────────────────────────────────── + +func TestCassandraServiceDetect(t *testing.T) { + session := testSession() + session.Config.DisableBrute = true + info := hostInfo(testHost, 19042) + plugin := services.NewCassandraPlugin() + + 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 cassandra service detect to succeed, got error: %v", result.Error) + } + t.Logf("cassandra: type=%s banner=%s", result.Type, result.Banner) +} + +// ── Neo4j ────────────────────────────────────────────────────── + +func TestNeo4jBrute(t *testing.T) { + session := testSession() + session.Config.Credentials.UserPassPairs = []config.CredentialPair{ + {Username: "neo4j", Password: "wrong"}, + {Username: "neo4j", Password: "neo4jtest123"}, + } + info := hostInfo(testHost, 17687) + plugin := services.NewNeo4jPlugin() + + 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 neo4j brute to succeed, got error: %v", result.Error) + } + t.Logf("neo4j brute: user=%s pass=%s", result.Username, result.Password) +} + +// ── Kafka ────────────────────────────────────────────────────── + +func TestKafkaNoAuth(t *testing.T) { + session := testSession() + info := hostInfo(testHost, 19092) + plugin := services.NewKafkaPlugin() + + 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 kafka to succeed, got error: %v", result.Error) + } + t.Logf("kafka: type=%s banner=%s", result.Type, result.Banner) +} + +// ── SMTP ─────────────────────────────────────────────────────── + +func TestSMTPServiceDetect(t *testing.T) { + session := testSession() + info := hostInfo(testHost, 11025) + plugin := services.NewSMTPPlugin() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + result := plugin.Scan(ctx, info, session) + if result == nil { + t.Fatal("result is nil") + } + if !result.Success { + t.Fatalf("expected smtp to succeed, got error: %v", result.Error) + } + t.Logf("smtp: type=%s banner=%s", result.Type, result.Banner) +} + // ── 连接失败场景 ────────────────────────────────────────────── func TestRedisConnectionRefused(t *testing.T) { diff --git a/tests/integration/mosquitto.conf b/tests/integration/mosquitto.conf new file mode 100644 index 0000000..c8348ac --- /dev/null +++ b/tests/integration/mosquitto.conf @@ -0,0 +1,2 @@ +listener 1883 +allow_anonymous true