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
- Guard before decoding: only call DecodeNTLM when the header is non-empty
- Trigger the challenge first: client.SetHeader('Authorization', 'NTLM ' + http.NegotiateNTLM()) so the 401 response carries the Type-2 blob
- 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
- Always check the WWW-Authenticate header exists before decoding
- Send an NTLM negotiate first (SetHeader with NegotiateNTLM()) to force the 401 challenge
- Treat missing NTLM support as a scan outcome, not an exception
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
- http: invalid url: %w
- http: url must include scheme and host
- could not create request: %s
- invalid goexec method arguments: %w
- grpc target host cannot be empty
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/3821f17209da0016.
Report an issue: GitHub.