projectdiscovery/nuclei · warning

NTLM response not properly terminated with Sub-option End

Error message

NTLM response not properly terminated with Sub-option End

What it means

ParseNTLMResponse found the NTLMSSP signature but no 0xFF 0xF0 (IAC SE, Sub-option End) terminator after it. Nmap's original regex (NTLMSSP.*\xff\xf0) requires the terminator because various non-Microsoft telnet implementations advertise NTLM but emit malformed or unterminated data; this guard rejects exactly those.

Source

Thrown at pkg/utils/telnetmini/ntlm.go:37

	Timestamp           uint64 // Raw timestamp for skew calculation
}

// ParseNTLMResponse parses the NTLM response to extract system information
// This implements the exact parsing logic from the Nmap telnet-ntlm-info.nse script
func ParseNTLMResponse(data []byte) (*NTLMInfoResponse, error) {
	// Continue only if NTLMSSP response is returned.
	// Verify that the response is terminated with Sub-option End values as various
	// non Microsoft telnet implementations support NTLM but do not return valid data.
	// This matches the script's: local data = string.match(response, "(NTLMSSP.*)\xff\xf0")
	ntlmStart := bytes.Index(data, []byte("NTLMSSP"))
	if ntlmStart == -1 {
		return nil, fmt.Errorf("NTLMSSP signature not found in response")
	}

	// Find the end of NTLM data (Sub-option End: 0xFF 0xF0)
	ntlmEnd := bytes.Index(data[ntlmStart:], []byte{0xFF, 0xF0})
	if ntlmEnd == -1 {
		return nil, fmt.Errorf("NTLM response not properly terminated with Sub-option End")
	}

	// Extract NTLM data (NTLMSSP.*\xff\xf0)
	ntlmData := data[ntlmStart : ntlmStart+ntlmEnd]

	// Check message type (should be 2 for Challenge).
	// The fixed header runs to offset 48 (target-info offset field ends at byte 48),
	// so reject anything shorter before touching any field offsets.
	if len(ntlmData) < 48 {
		return nil, fmt.Errorf("NTLM response too short: need at least 48 bytes, got %d", len(ntlmData))
	}

	messageType := binary.LittleEndian.Uint32(ntlmData[8:12])
	if messageType != 2 {
		return nil, fmt.Errorf("expected NTLM challenge message, got type %d", messageType)
	}

	// Parse target name fields

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Retry the probe — transient truncation over slow links often clears on a second attempt
  2. Increase the negotiation timeout (the Client/Probe accepts a timeout parameter; default is 7s total) when integrating the telnetmini package directly
  3. Treat as 'unsupported/malformed NTLM' and move on — do not fail the whole scan
  4. If persistent, capture the raw bytes (hexdump) to check whether a middlebox is rewriting IAC sequences
Defensive patterns

Strategy: validation

Validate before calling

// Verify terminator presence before invoking the parser:
start := bytes.Index(raw, []byte("NTLMSSP"))
if start == -1 { return nil, nil }
if bytes.Index(raw[start:], []byte{0xFF, 0xF0}) == -1 {
    return nil, fmt.Errorf("truncated NTLM (no IAC SE) — consider retry or larger timeout")
}
info, err := telnetmini.ParseNTLMResponse(raw)

Type guard

func ntlmTerminated(data []byte) bool {
    i := bytes.Index(data, []byte("NTLMSSP"))
    return i != -1 && bytes.Index(data[i:], []byte{0xFF, 0xF0}) != -1
}

Try / catch

info, err := telnetmini.ParseNTLMResponse(resp)
if err != nil && strings.Contains(err.Error(), "not properly terminated") {
    info, err = nil, nil // truncated: retry once with longer timeout, else skip
}

Prevention

When it happens

Trigger: A truncated telnet response — read deadline (1s per read in the negotiation loop) fired mid-NTLM payload; an IDS/IPS or firewall stripping sub-option bytes; a non-Windows implementation emitting NTLMSSP without proper SE framing; very slow servers whose NTLM blob arrives after the reader gave up.

Common situations: Scanning across WAN links with high latency (fixed 1-second read deadlines in telnet.go's negotiation loop are tight); middleboxes mangling telnet negotiation; embedded devices with half-baked NTLM implementations.

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/249f4d41ee620086. Report an issue: GitHub.