shadow1ng/fscan · error

invalid kafka response length: %d

Error message

invalid kafka response length: %d

What it means

kafkaRecv reads a 4-byte big-endian length prefix and rejects any declared message length below 4. A Kafka response frame must contain at least its own 4-byte correlation-id; anything smaller is a protocol violation or not a Kafka response at all. This protects the parser from garbage data.

Source

Thrown at plugins/services/kafka.go:191

	binary.BigEndian.PutUint16(buf[6:8], uint16(apiVersion))
	binary.BigEndian.PutUint32(buf[8:12], uint32(corrID))
	binary.BigEndian.PutUint16(buf[12:14], uint16(len(clientID)))
	copy(buf[14:], clientID)
	copy(buf[14+len(clientID):], body)

	_, err := conn.Write(buf)
	return err
}

func kafkaRecv(conn io.Reader) ([]byte, error) {
	// 读取 4 字节长度
	lenBuf := make([]byte, 4)
	if _, err := io.ReadFull(conn, lenBuf); err != nil {
		return nil, err
	}
	msgLen := int(binary.BigEndian.Uint32(lenBuf))
	if msgLen < 4 {
		return nil, fmt.Errorf("invalid kafka response length: %d", msgLen)
	}
	if msgLen > maxKafkaResponseSize {
		return nil, fmt.Errorf("kafka response too large: %d", msgLen)
	}
	// 读取消息体
	msg := make([]byte, msgLen)
	if _, err := io.ReadFull(conn, msg); err != nil {
		return nil, err
	}
	// 跳过 correlation_id (4B),返回 body
	return msg[4:], nil
}

func kafkaString(s string) []byte {
	b := []byte(s)
	buf := make([]byte, 2+len(b))
	binary.BigEndian.PutUint16(buf, uint16(len(b)))
	copy(buf[2:], b)

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Verify the target port is a Kafka broker (run identifyService or a version probe first)
  2. Re-sync the stream: if framing desync is suspected, reconnect instead of continuing to read
  3. Log the raw 4 length bytes when this fires to diagnose non-Kafka responses
  4. Keep this as a hard validation; do not loosen the <4 check

Example fix

// before
msgLen := int(binary.BigEndian.Uint32(lenBuf))
if msgLen < 4 {
    return nil, fmt.Errorf("invalid kafka response length: %d", msgLen)
}
// after
msgLen := int(binary.BigEndian.Uint32(lenBuf))
if msgLen < 4 {
    return nil, fmt.Errorf("invalid kafka response length: %d (not a kafka response; raw=% x)", msgLen, lenBuf)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Identify the service before parsing frames
if !identifyService(conn) {
    return fmt.Errorf("target does not speak the Kafka protocol")
}

Type guard

func isValidKafkaFrameLen(lenBuf []byte) bool {
    l := binary.BigEndian.Uint32(lenBuf)
    return l >= 4 && l <= maxKafkaResponseSize
}

Try / catch

resp, err := kafkaRecv(conn)
if err != nil {
    if strings.Contains(err.Error(), "invalid kafka response length") {
        log.Println("not a kafka response or desynced stream; reconnect and re-identify service")
        return
    }
    return err
}

Prevention

When it happens

Trigger: kafkaRecv (called by doKafkaAuth, identifyService, and unit tests): the connection returns a length prefix whose uint32 value is 0..3 — garbage banner from a non-Kafka service, desynced stream, or corrupt frame.

Common situations: Scanning a port that is not actually Kafka (HTTP server echoing short responses); reading a response mid-stream so the 4 bytes are misaligned with the frame boundary; a proxy mangling the framing.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/a564ff22b98a614c. Report an issue: GitHub.