shadow1ng/fscan · error
NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER
NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER
Error message
NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER
What it means
recvAttachUserConfirm expects the first byte of the server reply to be an MCS PDU header whose opcode is ATTACH_USER_CONFIRM (11). readMCSPDUHeader checks (option>>2)==11; if not, this sentinel error is emitted. It means the server responded with a different MCS PDU than the Attach User Confirm the client was waiting for.
Source
Thrown at libs/grdp/protocol/t125/mcs.go:375
func (c *MCSClient) sendAttachUserRequest() {
buff := &bytes.Buffer{}
writeMCSPDUHeader(ATTACH_USER_REQUEST, 0, buff)
c.transport.Write(buff.Bytes())
}
func (c *MCSClient) recvAttachUserConfirm(s []byte) {
glog.Debug("mcs recvAttachUserConfirm", hex.EncodeToString(s))
r := bytes.NewReader(s)
option, err := core.ReadUInt8(r)
if err != nil {
c.Emit("error", err)
return
}
if !readMCSPDUHeader(option, ATTACH_USER_CONFIRM) {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER"))
return
}
e, err := per.ReadEnumerates(r)
if err != nil {
c.Emit("error", err)
return
}
if e != 0 {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_SERVER_REJECT_USER'"))
return
}
userId, _ := per.ReadInteger16(r)
userId += MCS_USERCHANNEL_BASE
c.userId = userId
c.channels = append(c.channels, MCSChannelInfo{userId, "user"})View on GitHub (pinned to 95cc12e753)
Solutions
- Hex-dump the received buffer and decode the actual MCS opcode from (option>>2) to see what the server sent instead.
- Check whether the opcode is DISCONNECT_PROVIDER_ULTIMATUM (8) — that usually indicates the server rejected the connection outright.
- Verify the preceding sendAttachUserRequest was actually written (check transport.Write error handling in sendAttachUserRequest).
- Retry the connection; transient server-side resets can put the session out of sequence.
Example fix
// before
if !readMCSPDUHeader(option, ATTACH_USER_CONFIRM) {
c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER"))
return
}
// after
if readMCSPDUHeader(option, DISCONNECT_PROVIDER_ULTIMATUM) {
c.Emit("error", errors.New("server sent DISCONNECT_PROVIDER_ULTIMATUM instead of ATTACH_USER_CONFIRM"))
c.transport.Close()
return
}
if !readMCSPDUHeader(option, ATTACH_USER_CONFIRM) {
c.Emit("error", fmt.Errorf("NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER: got opcode %d", option>>2))
return
} Defensive patterns
Strategy: try-catch
Validate before calling
// Decode the actual opcode before dispatching handlers: op := (buf[0] >> 2) // expect op == 11 (ATTACH_USER_CONFIRM); anything else will hit BAD_HEADER
Try / catch
mcs.On("error", func(err error) {
if strings.Contains(err.Error(), "MCS_BAD_HEADER") {
// server sent an unexpected PDU — close and retry or inspect dump
}
}) Prevention
- Keep request/response pairing strict: register each Once('data', ...) handler in the right order.
- Log the decoded opcode on mismatch to distinguish reject/disconnect PDUs.
- Retry connections that fail at this stage — often transient server resets.
When it happens
Trigger: The 'data' event handled by recvAttachUserConfirm delivers a buffer whose first byte's high 6 bits are not ATTACH_USER_CONFIRM — e.g. a Disconnect Provider Ultimatum, an error PDU, or out-of-order data arrives.
Common situations: Server rejected the earlier Connect Initial/ Erect Domain phase and sends a disconnect PDU instead; TCP framing mismatch so the header byte read is not the PDU start; server under load sends data out of the expected request/confirm sequence.
Related errors
- Invalid expected MCS opcode receive data
- bad BER tags
- invalid expected BER tag
- unsupported Capability type 0x%04x
- Unknown data pdu type2 0x%02x
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/1e6f8ffe69656539.
Report an issue: GitHub.