projectdiscovery/nuclei · error
ntlm: base64 decode: %w
Error message
ntlm: base64 decode: %w
What it means
Thrown by http.DecodeNTLM in nuclei's JavaScript HTTP library when the input cannot be base64-decoded. The helper strips an optional 'NTLM ' or 'Negotiate ' prefix, then tries base64.StdEncoding followed by base64.RawStdEncoding; when both fail it wraps the underlying Go base64 error. It means the value passed in was not an NTLM SSP token at all.
Source
Thrown at pkg/js/libs/http/ntlm.go:82
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
}
func parseNTLMMessage(data []byte) (*NTLMInfo, error) {
if len(data) < 12 {
return nil, fmt.Errorf("ntlm: message too short")
}
if !bytes.HasPrefix(data, []byte("NTLMSSP\x00")) {
return nil, fmt.Errorf("ntlm: missing NTLMSSP signature")
}
msgType := binary.LittleEndian.Uint32(data[8:12])
info := &NTLMInfo{MessageType: int(msgType)}
if msgType != 2 {
// Type 1/3: return type only; TargetInfo is Type-2 specific.
return info, nil
}View on GitHub (pinned to 265b3a3dec)
Solutions
- Split multi-scheme headers on ',' and pass only the single token that follows the 'NTLM ' (or 'Negotiate ') prefix
- Verify the remainder after prefix removal matches /^[A-Za-z0-9+/]+={0,2}$/ before calling DecodeNTLM
- Strip whitespace and line-fold characters from long base64 tokens before decoding
- If the blob is a Kerberos SPNEGO token, do not use DecodeNTLM at all; it only parses NTLMSSP
Example fix
// before
const info = http.DecodeNTLM(resp.GetHeader('WWW-Authenticate'));
// after
const header = resp.GetHeader('WWW-Authenticate') || '';
const part = header.split(',').map(h => h.trim()).find(h => /^(ntlm|negotiate)\s+\S+/i.test(h));
if (part) {
const info = http.DecodeNTLM(part);
} Defensive patterns
Strategy: validation
Validate before calling
function extractNTLMToken(header) {
if (!header) return null;
const part = header.split(',').map(h => h.trim()).find(h => /^(ntlm|negotiate)\s+\S+/i.test(h));
if (!part) return null;
const token = part.replace(/^(ntlm|negotiate)\s+/i, '');
return /^[A-Za-z0-9+/]+={0,2}$/.test(token) ? token : null;
}
const token = extractNTLMToken(resp.GetHeader('WWW-Authenticate'));
if (token) {
const info = http.DecodeNTLM(token);
} Try / catch
try {
const info = http.DecodeNTLM(token);
} catch (e) {
// Header was not an NTLMSSP blob; treat endpoint as non-NTLM and skip NTLM assertions
} Prevention
- Never pass a raw multi-scheme WWW-Authenticate value; select the NTLM token first
- Send a Type-1 negotiate with http.NegotiateNTLM() and decode only the second response, which is guaranteed NTLMSSP
- Check the header exists and carries a payload after the scheme name before decoding
When it happens
Trigger: Calling http.DecodeNTLM() with: the bare scheme 'NTLM' (no token after the prefix), a 'Negotiate' header whose token is URL-safe base64 or a Kerberos SPNEGO token, a comma-joined multi-scheme header such as 'Negotiate, NTLM TlRMTVNT...', arbitrary non-base64 text, or base64 containing embedded whitespace or folded newlines.
Common situations: Templates that read WWW-Authenticate from servers preferring Kerberos over NTLM; passing the whole response object instead of the header string; servers that advertise several auth schemes in one header; tokens copied truncated out of Burp or docs.
Related errors
- ntlm: empty blob
- probe concurrency must be at least 1
- response read size must be non-negative
- empty filename
- cannot use unsafe with http fuzzing templates
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/8ed78cf8e0b6485e.
Report an issue: GitHub.