Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added some logging of X224 negotiation failures #102

Merged
merged 8 commits into from
Aug 19, 2019
26 changes: 26 additions & 0 deletions pyrdp/enum/rdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,32 @@ class NegotiationProtocols(IntFlag):
EARLY_USER_AUTHORIZATION_RESULT = 0b00001000


class NegotiationFailureCode(IntEnum):
obilodeau marked this conversation as resolved.
Show resolved Hide resolved
"""
RDP Negotiation Failure error messages
See: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/1b3920e7-0116-4345-bc45-f2c4ad012761
"""

SSL_REQUIRED_BY_SERVER = 0x00000001
SSL_NOT_ALLOWED_BY_SERVER = 0x00000002
SSL_CERT_NOT_ON_SERVER = 0x00000003
INCONSISTENT_FLAGS = 0x00000004
HYBRID_REQUIRED_BY_SERVER = 0x00000005
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006

@staticmethod
def getMessage(code: "NegotiationFailureCode"):
MESSAGES = {
NegotiationFailureCode.SSL_REQUIRED_BY_SERVER: "The server requires that the client support Enhanced RDP Security (section 5.4) with either TLS 1.0, 1.1 or 1.2 (section 5.4.5.1) or CredSSP (section 5.4.5.2). If only CredSSP was requested then the server only supports TLS.",
NegotiationFailureCode.SSL_NOT_ALLOWED_BY_SERVER: "The server is configured to only use Standard RDP Security mechanisms (section 5.3) and does not support any External Security Protocols (section 5.4.5).",
NegotiationFailureCode.SSL_CERT_NOT_ON_SERVER: "The server does not possess a valid authentication certificate and cannot initialize the External Security Protocol Provider (section 5.4.5).",
NegotiationFailureCode.INCONSISTENT_FLAGS: "The list of requested security protocols is not consistent with the current security protocol in effect. This error is only possible when the Direct Approach (sections 5.4.2.2 and 1.3.1.2) is used and an External Security Protocol (section 5.4.5) is already being used.",
NegotiationFailureCode.HYBRID_REQUIRED_BY_SERVER: "The server requires that the client support Enhanced RDP Security (section 5.4) with CredSSP (section 5.4.5.2).",
NegotiationFailureCode.SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER: "The server requires that the client support Enhanced RDP Security (section 5.4) with TLS 1.0, 1.1 or 1.2 (section 5.4.5.1) and certificate-based client authentication."
}
return MESSAGES.get(code, "Unknown Error")


class CapabilityType(IntEnum):
CAPSTYPE_GENERAL = 0x0001
CAPSTYPE_BITMAP = 0x0002
Expand Down
13 changes: 9 additions & 4 deletions pyrdp/mitm/X224MITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from logging import LoggerAdapter

from pyrdp.core import defer
from pyrdp.enum import NegotiationProtocols, NegotiationType
from pyrdp.enum import NegotiationFailureCode, NegotiationProtocols, NegotiationType
from pyrdp.layer import X224Layer
from pyrdp.mitm.state import RDPMITMState
from pyrdp.parser import NegotiationRequestParser, NegotiationResponseParser
from pyrdp.pdu import NegotiationRequestPDU, NegotiationResponsePDU, X224ConnectionConfirmPDU, X224ConnectionRequestPDU, \
X224DisconnectRequestPDU, X224ErrorPDU
X224DisconnectRequestPDU, X224ErrorPDU, NegotiationFailurePDU


class X224MITM:
Expand Down Expand Up @@ -89,7 +89,7 @@ async def connectToServer(self, payload: bytes):
await self.connector
self.server.sendConnectionRequest(payload = payload)

def onConnectionConfirm(self, _: X224ConnectionConfirmPDU):
def onConnectionConfirm(self, pdu: X224ConnectionConfirmPDU):
"""
Execute a startTLS if the SSL protocol was selected.
:param _: the connection confirm PDU
Expand All @@ -99,7 +99,12 @@ def onConnectionConfirm(self, _: X224ConnectionConfirmPDU):
protocols = NegotiationProtocols.SSL if self.originalRequest.tlsSupported else NegotiationProtocols.NONE

parser = NegotiationResponseParser()
payload = parser.write(NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, 0x00, protocols))
response = parser.parse(pdu.payload)
if isinstance(response, NegotiationFailurePDU):
self.log.info("The server failed the negotiation. Error: %(error)s", {"error": NegotiationFailureCode.getMessage(response.failureCode)})
payload = pdu.payload
else:
payload = parser.write(NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, 0x00, protocols))
self.client.sendConnectionConfirm(payload, source=0x1234)

if self.originalRequest.tlsSupported:
Expand Down
14 changes: 10 additions & 4 deletions pyrdp/parser/rdp/negotiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
#

from io import BytesIO
from typing import Union

from pyrdp.core import Uint16LE, Uint32LE, Uint8
from pyrdp.enum import NegotiationRequestFlags, NegotiationType
from pyrdp.parser.parser import Parser
from pyrdp.pdu import NegotiationRequestPDU, NegotiationResponsePDU
from pyrdp.pdu import NegotiationFailurePDU, NegotiationRequestPDU, NegotiationResponsePDU


class NegotiationRequestParser(Parser):
Expand Down Expand Up @@ -82,7 +83,7 @@ class NegotiationResponseParser(Parser):
"""
Parser for RDP negotiation responses (Connection Confirm payloads).
"""
def parse(self, data: bytes) -> NegotiationResponsePDU:
def parse(self, data: bytes) -> Union[NegotiationResponsePDU, NegotiationFailurePDU]:
"""
Parse a negotiation response.
:param data: the response data.
Expand All @@ -93,8 +94,13 @@ def parse(self, data: bytes) -> NegotiationResponsePDU:
type = Uint8.unpack(stream)
flags = Uint8.unpack(stream)
length = Uint16LE.unpack(stream)
selectedProtocols = Uint32LE.unpack(stream)
return NegotiationResponsePDU(type, flags, selectedProtocols)

if type == NegotiationType.TYPE_RDP_NEG_FAILURE:
failureCode = Uint32LE.unpack(stream)
return NegotiationFailurePDU(type, flags, failureCode)
else:
selectedProtocols = Uint32LE.unpack(stream)
return NegotiationResponsePDU(type, flags, selectedProtocols)
Res260 marked this conversation as resolved.
Show resolved Hide resolved
else:
return NegotiationResponsePDU(None, None, None)

Expand Down
2 changes: 1 addition & 1 deletion pyrdp/pdu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from pyrdp.pdu.rdp.input import ExtendedMouseEvent, KeyboardEvent, MouseEvent, SlowPathInput, SynchronizeEvent, \
UnicodeKeyboardEvent, UnusedEvent
from pyrdp.pdu.rdp.licensing import LicenseBinaryBlob, LicenseErrorAlertPDU, LicensingPDU
from pyrdp.pdu.rdp.negotiation import NegotiationRequestPDU, NegotiationResponsePDU
from pyrdp.pdu.rdp.negotiation import NegotiationFailurePDU, NegotiationRequestPDU, NegotiationResponsePDU
from pyrdp.pdu.rdp.pointer import Point, PointerCacheEvent, PointerColorEvent, PointerEvent, PointerNewEvent, \
PointerPositionEvent, PointerSystemEvent
from pyrdp.pdu.rdp.security import SecurityExchangePDU, SecurityPDU
Expand Down
20 changes: 18 additions & 2 deletions pyrdp/pdu/rdp/negotiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from typing import Optional

from pyrdp.enum import NegotiationProtocols, NegotiationRequestFlags, NegotiationType
from pyrdp.enum import NegotiationFailureCode, NegotiationProtocols, NegotiationRequestFlags, NegotiationType
from pyrdp.pdu.pdu import PDU


Expand Down Expand Up @@ -52,4 +52,20 @@ def __init__(self, type: Optional[int], flags: Optional[int], selectedProtocols:
self.selectedProtocols = selectedProtocols
self.tlsSelected = selectedProtocols is not None and selectedProtocols & NegotiationProtocols.SSL != 0
self.credSspSelected = selectedProtocols is not None and selectedProtocols & NegotiationProtocols.CRED_SSP != 0
self.earlyUserAuthSelected = selectedProtocols is not None and selectedProtocols & NegotiationProtocols.EARLY_USER_AUTHORIZATION_RESULT != 0
self.earlyUserAuthSelected = selectedProtocols is not None and selectedProtocols & NegotiationProtocols.EARLY_USER_AUTHORIZATION_RESULT != 0

class NegotiationFailurePDU(PDU):
"""
Special PDU indicating failure. Sent by the server.
"""
def __init__(self, type: Optional[int], flags: Optional[int], failureCode: NegotiationFailureCode):
"""
:param flags: response flags.
:param failureCode: error from the server
"""
PDU.__init__(self)
self.type = type
self.packetType = NegotiationType.TYPE_RDP_NEG_RSP
self.length = 8
self.flags = flags
self.failureCode = failureCode