projectdiscovery/nuclei · warning

ntlm: empty blob

Error message

ntlm: empty blob

What it means

DecodeNTLM was called with an empty or whitespace-only blob. The function expects the WWW-Authenticate/Authorization header value (with optional 'NTLM '/'Negotiate ' prefix) or raw base64 NTLMSSP data; empty input usually means the response carried no WWW-Authenticate header, i.e. the target did not offer an NTLM challenge at all.

Source

Thrown at pkg/js/libs/http/ntlm.go:68

}

// NegotiateNTLM returns a base64 Type-1 NTLM negotiate message suitable for
// an Authorization header (without the "NTLM " prefix).
// @example
// ```javascript
// const http = require('nuclei/http');
// const client = new http.Client();
// client.SetHeader('Authorization', 'NTLM ' + http.NegotiateNTLM());
// const resp = client.Get('https://exchange.acme.local/ews/');
// ```
func NegotiateNTLM() string {
	return base64.StdEncoding.EncodeToString(createNegotiateMessage())
}

func decodeNTLMBlob(blob string) ([]byte, error) {
	s := strings.TrimSpace(blob)
	if s == "" {
		return nil, fmt.Errorf("ntlm: empty blob")
	}
	lower := strings.ToLower(s)
	switch {
	case strings.HasPrefix(lower, "ntlm "):
		s = strings.TrimSpace(s[5:])
	case strings.HasPrefix(lower, "negotiate "):
		s = strings.TrimSpace(s[10:])
	}
	// Some servers return "Negotiate <spnego>" - still try base64 of remainder.
	raw, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		raw, err = base64.RawStdEncoding.DecodeString(s)
		if err != nil {
			return nil, fmt.Errorf("ntlm: base64 decode: %w", err)
		}
	}
	return raw, nil
}

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Guard before decoding: only call DecodeNTLM when the header is non-empty
  2. Trigger the challenge first: client.SetHeader('Authorization', 'NTLM ' + http.NegotiateNTLM()) so the 401 response carries the Type-2 blob
  3. Treat an absent header as a negative result rather than an error

Example fix

// before
const info = http.DecodeNTLM(resp.GetHeader('WWW-Authenticate')); // -> ntlm: empty blob

// after: check the challenge exists, then decode
const challenge = resp.GetHeader('WWW-Authenticate');
if (challenge && challenge.trim()) {
  const info = http.DecodeNTLM(challenge);
  log(info.DNSComputerName);
}
Defensive patterns

Strategy: validation

Validate before calling

const challenge = resp.GetHeader('WWW-Authenticate');
if (challenge && challenge.trim()) {
  const info = http.DecodeNTLM(challenge);
  log(info.DNSComputerName);
} else {
  // no NTLM challenge offered: negative result, skip decoding
}

Type guard

const hasNtlmChallenge = (h) => /NTLM|Negotiate/i.test(String(h || '').trim());

Try / catch

try { const info = http.DecodeNTLM(header); }
catch (e) { if (/ntlm: empty blob/.test(e.message || '')) { /* target offered no NTLM: treat as negative, not an error */ } }

Prevention

When it happens

Trigger: const info = http.DecodeNTLM(resp.GetHeader('WWW-Authenticate')) when the response has no WWW-Authenticate header (GetHeader returns ''); passing a null/undefined/unset template variable; probing a non-NTLM endpoint that answers 200 without a challenge.

Common situations: NTLM fingerprint templates run against arbitrary HTTP services; checking the wrong response in a multi-step flow; servers that only emit the challenge on a 401 that the request never triggered (no NTLM negotiate sent first).

Related errors


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