projectdiscovery/nuclei · error
ntlm: missing NTLMSSP signature
Error message
ntlm: missing NTLMSSP signature
What it means
The base64 decoded fine but the first 8 bytes are not the 'NTLMSSP\x00' signature. DecodeNTLM only understands NTLMSSP blobs; the bytes are almost certainly a Kerberos AP-REQ / SPNEGO token (which commonly starts with 0x60 or 0x6A DER tags) or arbitrary data.
Source
Thrown at pkg/js/libs/http/ntlm.go:93
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
}
if len(data) < 48 {
return nil, fmt.Errorf("ntlm: challenge too short")
}
targetNameLen := binary.LittleEndian.Uint16(data[12:14])
targetNameOffset := binary.LittleEndian.Uint32(data[16:20])
if targetNameLen > 0 && int(targetNameOffset)+int(targetNameLen) <= len(data) {
info.TargetName = decodeUTF16LE(data[targetNameOffset : targetNameOffset+uint32(targetNameLen)])
}
negotiateFlags := binary.LittleEndian.Uint32(data[20:24])View on GitHub (pinned to 265b3a3dec)
Solutions
- Force NTLM: send Authorization: NTLM <http.NegotiateNTLM()> and decode the WWW-Authenticate of the second response, which is then guaranteed NTLMSSP
- If the scheme is Negotiate and the decoded token starts with Kerberos DER tags, use a Kerberos parser instead of DecodeNTLM
- Confirm the decoded bytes begin with 4E 54 4C 4D 53 53 50 00 ('NTLMSSP\x00') before asserting NTLM fields
Example fix
// before
const info = http.DecodeNTLM(resp.GetHeader('WWW-Authenticate'));
// after - force an NTLM challenge first
client.SetHeader('Authorization', 'NTLM ' + http.NegotiateNTLM());
const resp2 = client.Get(url);
const h = resp2.GetHeader('WWW-Authenticate') || '';
if (/^ntlm\s+/i.test(h)) {
const info = http.DecodeNTLM(h);
} Defensive patterns
Strategy: validation
Validate before calling
// Force NTLM so the challenge is NTLMSSP, not a Kerberos SPNEGO token
client.SetHeader('Authorization', 'NTLM ' + http.NegotiateNTLM());
const resp2 = client.Get(url);
const h = resp2.GetHeader('WWW-Authenticate') || '';
if (/^ntlm\s+/i.test(h)) {
const info = http.DecodeNTLM(h);
} Try / catch
try {
const info = http.DecodeNTLM(blob);
} catch (e) {
// Valid base64 but not NTLMSSP (likely Kerberos); skip NTLM parsing
} Prevention
- Do not assume the Negotiate scheme implies NTLM; servers may choose Kerberos
- Drive the handshake yourself: Type-1 in, decode the Type-2 challenge out
When it happens
Trigger: Decoding a 'Negotiate <token>' header where the server chose Kerberos; passing base64 of a random string; passing the base64 of an already-decoded NTLM message; decoding the client's own Authorization echo instead of the server challenge.
Common situations: IIS/Exchange servers configured Kerberos-only or Kerberos-preferred under the Negotiate scheme; hybrid setups where the same endpoint answers NTLM on one path and Kerberos on another; templates assuming Negotiate always means NTLM.
Related errors
- dcerpc bind: %w
- domain controller denied by network policy
- invalid signature type:
- tsch bind: %w
- ntlm: empty blob
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/92d61f2998268c3d.
Report an issue: GitHub.