shadow1ng/fscan · error

SASL handshake error: %d

Error message

SASL handshake error: %d

What it means

doKafkaAuth parses the SaslHandshake response and reads the first 2 bytes as a big-endian int16 error_code. A non-zero code means the broker rejected the handshake (unknown mechanism, unsupported SASL version, auth disabled, security protocol mismatch). The numeric Kafka protocol error code is embedded in the message for diagnosis.

Source

Thrown at plugins/services/kafka.go:113

	// Step 2: SASL/PLAIN 认证 (如果需要)
	if cred.Username != "" || cred.Password != "" {
		// SaslHandshake: mechanism=PLAIN (api_key=17, api_version=0)
		body := kafkaString("PLAIN")
		if err := kafkaSend(conn, 17, 0, body); err != nil {
			state.IncrementTCPFailedPacketCount()
			return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
		}
		resp, err := kafkaRecv(conn)
		if err != nil {
			state.IncrementTCPFailedPacketCount()
			return &AuthResult{Success: false, ErrorType: classifyKafkaErrorType(err), Error: err}
		}
		// SaslHandshake 响应: [4B error_code] + [mechanisms array]
		if len(resp) >= 2 {
			code := int16(binary.BigEndian.Uint16(resp[:2]))
			if code != 0 {
				return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("SASL handshake error: %d", code)}
			}
		}

		// SaslAuthenticate: PLAIN token = \x00user\x00pass (api_key=36, api_version=0)
		token := []byte("\x00" + cred.Username + "\x00" + cred.Password)
		authBody := kafkaBytes(token)
		if err := kafkaSend(conn, 36, 0, authBody); err != nil {
			state.IncrementTCPFailedPacketCount()
			return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
		}
		resp, err = kafkaRecv(conn)
		if err != nil {
			state.IncrementTCPFailedPacketCount()
			return &AuthResult{Success: false, ErrorType: classifyKafkaErrorType(err), Error: err}
		}
		if len(resp) >= 2 {
			code := int16(binary.BigEndian.Uint16(resp[:2]))
			if code != 0 {

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Match the security protocol: use TLS for SASL_SSL listeners before attempting SASL
  2. Verify the broker listener supports the PLAIN mechanism (check sasl.enabled.mechanisms)
  3. Decode the specific code (e.g. 33=UnsupportedSaslMechanism, 34=IllegalSaslState) to target the fix
  4. Confirm the broker's listener port is the SASL-enabled one, not PLAINTEXT

Example fix

// before
if code != 0 {
    return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("SASL handshake error: %d", code)}
}
// after
if code != 0 {
    return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("SASL handshake error: %d (%s)", code, kafkaSaslHandshakeErrText(code))}
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Probe the listener's security protocol before SASL
// e.g. send ApiVersions unencrypted; if the broker requires TLS the frame fails early

Type guard

func isHandshakeRejected(code int16) bool {
    return code != 0 // 33 UnsupportedSaslMechanism, 34 IllegalSaslState
}

Try / catch

res := plugin.Scan(info, config, state)
if a := res.AuthResults; a != nil {
    for _, r := range a {
        if r.Error != nil && strings.Contains(r.Error.Error(), "SASL handshake error") {
            log.Printf("kafka: broker rejected handshake (%v); check listener protocol/mechanism", r.Error)
        }
    }
}

Prevention

When it happens

Trigger: doKafkaAuth: after sending the SaslHandshake request (api_key=17) over PLAIN, kafkaRecv returns a response whose first 2 bytes are a non-zero error code.

Common situations: Broker listener configured with SASL_SSL while plugin connects plaintext (or vice versa); broker does not support the PLAIN mechanism (only SCRAM); SASL not enabled on the listener at all; api_version mismatch with older brokers.

Understand the failure class

Related errors


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