Skip to content

Commit 3c128ca

Browse files
committed
fix: Cassandra CQL 协议头缺少 flags 字节 + version 方向位错误
cqlSend 写 8 字节头(缺 flags),实际 CQL v4 需要 9 字节。 version byte 0x84 是 response 方向,request 应为 0x04。 同时扩展集成测试至 17 个协议:新增 Memcached、Elasticsearch、 MSSQL、RabbitMQ、MQTT、LDAP、Cassandra、Neo4j、Kafka、SMTP。
1 parent 226748f commit 3c128ca

4 files changed

Lines changed: 347 additions & 7 deletions

File tree

plugins/services/cassandra.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (p *CassandraPlugin) createAuthFunc(info *common.HostInfo, config *common.C
7474
//
7575
// [1B version|flags] [2B stream] [1B opcode] [4B length] [body]
7676
const (
77-
cqlVersion = 0x84 // version=4, direction=request
77+
cqlVersion = 0x04 // version=4, direction=request
7878
cqlOpStartup = 0x01
7979
cqlOpAuthRsp = 0x0f
8080
cqlOpQuery = 0x07
@@ -178,12 +178,13 @@ func nextCQLStreamID() uint16 {
178178
func cqlSend(conn net.Conn, opcode byte, body []byte) error {
179179
id := nextCQLStreamID()
180180

181-
// frame: [1B version|flags] [2B stream] [1B opcode] [4B length] [body]
182-
header := make([]byte, 8)
183-
header[0] = cqlVersion
184-
binary.BigEndian.PutUint16(header[1:3], id)
185-
header[3] = opcode
186-
binary.BigEndian.PutUint32(header[4:8], uint32(len(body)))
181+
// CQL v4 frame: [1B version] [1B flags] [2B stream] [1B opcode] [4B length] [body]
182+
header := make([]byte, 9)
183+
header[0] = cqlVersion // 0x04 = request, version 4
184+
header[1] = 0x00 // flags
185+
binary.BigEndian.PutUint16(header[2:4], id)
186+
header[4] = opcode
187+
binary.BigEndian.PutUint32(header[5:9], uint32(len(body)))
187188

188189
buf := append(header, body...)
189190
_, err := conn.Write(buf)

tests/integration/docker-compose.yml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,117 @@ services:
8787
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')", "-u", "admin", "-p", "mongo123"]
8888
interval: 5s
8989
retries: 20
90+
91+
memcached:
92+
image: memcached:1-alpine
93+
ports:
94+
- "11211:11211"
95+
healthcheck:
96+
test: ["CMD-SHELL", "echo stats | nc localhost 11211 | grep -q pid"]
97+
interval: 3s
98+
retries: 10
99+
100+
elasticsearch:
101+
image: elasticsearch:7.17.24
102+
environment:
103+
discovery.type: single-node
104+
xpack.security.enabled: "false"
105+
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
106+
ports:
107+
- "19200:9200"
108+
healthcheck:
109+
test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health || exit 1"]
110+
interval: 5s
111+
retries: 20
112+
113+
mssql:
114+
image: mcr.microsoft.com/mssql/server:2019-latest
115+
environment:
116+
ACCEPT_EULA: "Y"
117+
SA_PASSWORD: "MssqlTest123!"
118+
MSSQL_PID: Express
119+
ports:
120+
- "11433:1433"
121+
healthcheck:
122+
test: ["CMD-SHELL", "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'MssqlTest123!' -Q 'SELECT 1' || exit 1"]
123+
interval: 5s
124+
retries: 30
125+
126+
rabbitmq:
127+
image: rabbitmq:3-management-alpine
128+
environment:
129+
RABBITMQ_DEFAULT_USER: admin
130+
RABBITMQ_DEFAULT_PASS: rabbit123
131+
ports:
132+
- "15672:15672"
133+
- "15673:5672"
134+
healthcheck:
135+
test: ["CMD", "rabbitmq-diagnostics", "check_running"]
136+
interval: 5s
137+
retries: 20
138+
139+
mqtt:
140+
image: eclipse-mosquitto:2
141+
ports:
142+
- "11883:1883"
143+
volumes:
144+
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
145+
healthcheck:
146+
test: ["CMD-SHELL", "mosquitto_sub -t '$$SYS/#' -C 1 -W 2 || exit 1"]
147+
interval: 5s
148+
retries: 10
149+
150+
openldap:
151+
image: osixia/openldap:1.5.0
152+
environment:
153+
LDAP_ORGANISATION: "Test"
154+
LDAP_DOMAIN: "test.local"
155+
LDAP_ADMIN_PASSWORD: "ldap123"
156+
ports:
157+
- "10389:389"
158+
healthcheck:
159+
test: ["CMD-SHELL", "ldapsearch -x -H ldap://localhost -b 'dc=test,dc=local' -D 'cn=admin,dc=test,dc=local' -w ldap123 || exit 1"]
160+
interval: 5s
161+
retries: 10
162+
163+
cassandra:
164+
image: cassandra:4.1
165+
environment:
166+
CASSANDRA_AUTHENTICATOR: AllowAllAuthenticator
167+
ports:
168+
- "19042:9042"
169+
healthcheck:
170+
test: ["CMD-SHELL", "cqlsh -e 'DESCRIBE CLUSTER' || exit 1"]
171+
interval: 10s
172+
retries: 30
173+
174+
neo4j:
175+
image: neo4j:5
176+
environment:
177+
NEO4J_AUTH: "neo4j/neo4jtest123"
178+
ports:
179+
- "17687:7687"
180+
- "17474:7474"
181+
healthcheck:
182+
test: ["CMD-SHELL", "wget -qO- http://localhost:7474 || exit 1"]
183+
interval: 5s
184+
retries: 20
185+
186+
kafka:
187+
image: apache/kafka:3.7.0
188+
ports:
189+
- "19092:9092"
190+
healthcheck:
191+
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list || exit 1"]
192+
interval: 10s
193+
retries: 20
194+
195+
smtp:
196+
image: mailhog/mailhog
197+
ports:
198+
- "11025:1025"
199+
- "18025:8025"
200+
healthcheck:
201+
test: ["CMD-SHELL", "wget -qO- http://localhost:8025/api/v2/messages || exit 1"]
202+
interval: 5s
203+
retries: 10

tests/integration/integration_test.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,229 @@ func TestMongoDBBrute(t *testing.T) {
204204
t.Logf("mongodb brute: user=%s pass=%s", result.Username, result.Password)
205205
}
206206

207+
// ── Memcached ──────────────────────────────────────────────────
208+
209+
func TestMemcachedUnauthorized(t *testing.T) {
210+
session := testSession()
211+
info := hostInfo(testHost, 11211)
212+
plugin := services.NewMemcachedPlugin()
213+
214+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
215+
defer cancel()
216+
217+
result := plugin.Scan(ctx, info, session)
218+
if result == nil {
219+
t.Fatal("result is nil")
220+
}
221+
if !result.Success {
222+
t.Fatalf("expected memcached to succeed, got error: %v", result.Error)
223+
}
224+
t.Logf("memcached: type=%s banner=%s", result.Type, result.Banner)
225+
}
226+
227+
// ── Elasticsearch ──────────────────────────────────────────────
228+
229+
func TestElasticsearchUnauthorized(t *testing.T) {
230+
session := testSession()
231+
info := hostInfo(testHost, 19200)
232+
plugin := services.NewElasticsearchPlugin()
233+
234+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
235+
defer cancel()
236+
237+
result := plugin.Scan(ctx, info, session)
238+
if result == nil {
239+
t.Fatal("result is nil")
240+
}
241+
if !result.Success {
242+
t.Fatalf("expected elasticsearch to succeed, got error: %v", result.Error)
243+
}
244+
t.Logf("elasticsearch: type=%s vulinfo=%s", result.Type, result.VulInfo)
245+
}
246+
247+
// ── MSSQL ──────────────────────────────────────────────────────
248+
249+
func TestMSSQLBrute(t *testing.T) {
250+
session := testSession()
251+
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
252+
{Username: "sa", Password: "wrong"},
253+
{Username: "sa", Password: "MssqlTest123!"},
254+
}
255+
info := hostInfo(testHost, 11433)
256+
plugin := services.NewMSSQLPlugin()
257+
258+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
259+
defer cancel()
260+
261+
result := plugin.Scan(ctx, info, session)
262+
if result == nil {
263+
t.Fatal("result is nil")
264+
}
265+
if !result.Success {
266+
t.Fatalf("expected mssql brute to succeed, got error: %v", result.Error)
267+
}
268+
t.Logf("mssql brute: user=%s pass=%s", result.Username, result.Password)
269+
}
270+
271+
// ── RabbitMQ ───────────────────────────────────────────────────
272+
273+
func TestRabbitMQBrute(t *testing.T) {
274+
session := testSession()
275+
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
276+
{Username: "admin", Password: "wrong"},
277+
{Username: "admin", Password: "rabbit123"},
278+
}
279+
info := hostInfo(testHost, 15672)
280+
plugin := services.NewRabbitMQPlugin()
281+
282+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
283+
defer cancel()
284+
285+
result := plugin.Scan(ctx, info, session)
286+
if result == nil {
287+
t.Fatal("result is nil")
288+
}
289+
if !result.Success {
290+
t.Fatalf("expected rabbitmq brute to succeed, got error: %v", result.Error)
291+
}
292+
t.Logf("rabbitmq brute: user=%s pass=%s", result.Username, result.Password)
293+
}
294+
295+
// ── MQTT ───────────────────────────────────────────────────────
296+
297+
func TestMQTTServiceDetect(t *testing.T) {
298+
session := testSession()
299+
info := hostInfo(testHost, 11883)
300+
plugin := services.NewMQTTPlugin()
301+
302+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
303+
defer cancel()
304+
305+
result := plugin.Scan(ctx, info, session)
306+
if result == nil {
307+
t.Fatal("result is nil")
308+
}
309+
if !result.Success {
310+
t.Fatalf("expected mqtt service detect to succeed, got error: %v", result.Error)
311+
}
312+
t.Logf("mqtt: service=%s banner=%s", result.Service, result.Banner)
313+
}
314+
315+
// ── SMB ────────────────────────────────────────────────────────
316+
317+
func TestSMBBrute(t *testing.T) {
318+
t.Skip("SMB requires port 445 which is reserved on WSL2")
319+
}
320+
321+
// ── LDAP ───────────────────────────────────────────────────────
322+
323+
func TestLDAPBrute(t *testing.T) {
324+
session := testSession()
325+
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
326+
{Username: "cn=admin,dc=test,dc=local", Password: "wrong"},
327+
{Username: "cn=admin,dc=test,dc=local", Password: "ldap123"},
328+
}
329+
info := hostInfo(testHost, 10389)
330+
plugin := services.NewLDAPPlugin()
331+
332+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
333+
defer cancel()
334+
335+
result := plugin.Scan(ctx, info, session)
336+
if result == nil {
337+
t.Fatal("result is nil")
338+
}
339+
if !result.Success {
340+
t.Fatalf("expected ldap brute to succeed, got error: %v", result.Error)
341+
}
342+
t.Logf("ldap brute: user=%s pass=%s", result.Username, result.Password)
343+
}
344+
345+
// ── Cassandra ──────────────────────────────────────────────────
346+
347+
func TestCassandraServiceDetect(t *testing.T) {
348+
session := testSession()
349+
session.Config.DisableBrute = true
350+
info := hostInfo(testHost, 19042)
351+
plugin := services.NewCassandraPlugin()
352+
353+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
354+
defer cancel()
355+
356+
result := plugin.Scan(ctx, info, session)
357+
if result == nil {
358+
t.Fatal("result is nil")
359+
}
360+
if !result.Success {
361+
t.Fatalf("expected cassandra service detect to succeed, got error: %v", result.Error)
362+
}
363+
t.Logf("cassandra: type=%s banner=%s", result.Type, result.Banner)
364+
}
365+
366+
// ── Neo4j ──────────────────────────────────────────────────────
367+
368+
func TestNeo4jBrute(t *testing.T) {
369+
session := testSession()
370+
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
371+
{Username: "neo4j", Password: "wrong"},
372+
{Username: "neo4j", Password: "neo4jtest123"},
373+
}
374+
info := hostInfo(testHost, 17687)
375+
plugin := services.NewNeo4jPlugin()
376+
377+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
378+
defer cancel()
379+
380+
result := plugin.Scan(ctx, info, session)
381+
if result == nil {
382+
t.Fatal("result is nil")
383+
}
384+
if !result.Success {
385+
t.Fatalf("expected neo4j brute to succeed, got error: %v", result.Error)
386+
}
387+
t.Logf("neo4j brute: user=%s pass=%s", result.Username, result.Password)
388+
}
389+
390+
// ── Kafka ──────────────────────────────────────────────────────
391+
392+
func TestKafkaNoAuth(t *testing.T) {
393+
session := testSession()
394+
info := hostInfo(testHost, 19092)
395+
plugin := services.NewKafkaPlugin()
396+
397+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
398+
defer cancel()
399+
400+
result := plugin.Scan(ctx, info, session)
401+
if result == nil {
402+
t.Fatal("result is nil")
403+
}
404+
if !result.Success {
405+
t.Fatalf("expected kafka to succeed, got error: %v", result.Error)
406+
}
407+
t.Logf("kafka: type=%s banner=%s", result.Type, result.Banner)
408+
}
409+
410+
// ── SMTP ───────────────────────────────────────────────────────
411+
412+
func TestSMTPServiceDetect(t *testing.T) {
413+
session := testSession()
414+
info := hostInfo(testHost, 11025)
415+
plugin := services.NewSMTPPlugin()
416+
417+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
418+
defer cancel()
419+
420+
result := plugin.Scan(ctx, info, session)
421+
if result == nil {
422+
t.Fatal("result is nil")
423+
}
424+
if !result.Success {
425+
t.Fatalf("expected smtp to succeed, got error: %v", result.Error)
426+
}
427+
t.Logf("smtp: type=%s banner=%s", result.Type, result.Banner)
428+
}
429+
207430
// ── 连接失败场景 ──────────────────────────────────────────────
208431

209432
func TestRedisConnectionRefused(t *testing.T) {

tests/integration/mosquitto.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
listener 1883
2+
allow_anonymous true

0 commit comments

Comments
 (0)