# SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ SPNEGO Implements parts of: - GSSAPI SPNEGO: RFC4178 > RFC2478 - GSSAPI SPNEGO NEGOEX: [MS-NEGOEX] .. note:: You will find more complete documentation for this layer over at `GSSAPI `_ """ import struct from uuid import UUID from scapy.asn1.asn1 import ( ASN1_OID, ASN1_STRING, ASN1_Codecs, ) from scapy.asn1.mib import conf # loads conf.mib from scapy.asn1fields import ( ASN1F_CHOICE, ASN1F_ENUMERATED, ASN1F_FLAGS, ASN1F_GENERAL_STRING, ASN1F_OID, ASN1F_PACKET, ASN1F_SEQUENCE, ASN1F_SEQUENCE_OF, ASN1F_STRING, ASN1F_optional, ) from scapy.asn1packet import ASN1_Packet from scapy.fields import ( FieldListField, LEIntEnumField, LEIntField, LELongEnumField, LELongField, LEShortField, MultipleTypeField, PacketField, PacketListField, StrField, StrFixedLenField, UUIDEnumField, UUIDField, XStrFixedLenField, XStrLenField, ) from scapy.packet import Packet, bind_layers from scapy.layers.gssapi import ( GSSAPI_BLOB, GSSAPI_BLOB_SIGNATURE, GSS_C_FLAGS, GSS_S_BAD_MECH, GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED, SSP, _GSSAPI_OIDS, _GSSAPI_SIGNATURE_OIDS, ) # SSP Providers from scapy.layers.kerberos import ( Kerberos, ) from scapy.layers.ntlm import ( NEGOEX_EXCHANGE_NTLM, NTLM_Header, _NTLMPayloadField, _NTLMPayloadPacket, ) # Typing imports from typing import ( Dict, Optional, Tuple, ) # SPNEGO negTokenInit # https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.1 class SPNEGO_MechType(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_OID("oid", None) class SPNEGO_MechTypes(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType) class SPNEGO_MechListMIC(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_STRING("value", "") _mechDissector = { "1.3.6.1.4.1.311.2.2.10": NTLM_Header, # NTLM "1.2.840.48018.1.2.2": Kerberos, # MS KRB5 - Microsoft Kerberos 5 "1.2.840.113554.1.2.2": Kerberos, # Kerberos 5 } class _SPNEGO_Token_Field(ASN1F_STRING): def i2m(self, pkt, x): if x is None: x = b"" return super(_SPNEGO_Token_Field, self).i2m(pkt, bytes(x)) def m2i(self, pkt, s): dat, r = super(_SPNEGO_Token_Field, self).m2i(pkt, s) if isinstance(pkt.underlayer, SPNEGO_negTokenInit): types = pkt.underlayer.mechTypes elif isinstance(pkt.underlayer, SPNEGO_negTokenResp): types = [pkt.underlayer.supportedMech] if types and types[0] and types[0].oid.val in _mechDissector: return _mechDissector[types[0].oid.val](dat.val), r return dat, r class SPNEGO_Token(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = _SPNEGO_Token_Field("value", None) _ContextFlags = [ "delegFlag", "mutualFlag", "replayFlag", "sequenceFlag", "superseded", "anonFlag", "confFlag", "integFlag", ] class SPNEGO_negHints(ASN1_Packet): # [MS-SPNG] 2.2.1 ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_GENERAL_STRING( "hintName", "not_defined_in_RFC4178@please_ignore", explicit_tag=0xA0 ), ), ASN1F_optional( ASN1F_GENERAL_STRING("hintAddress", None, explicit_tag=0xA1), ), ) class SPNEGO_negTokenInit(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType, explicit_tag=0xA0) ), ASN1F_optional(ASN1F_FLAGS("reqFlags", None, _ContextFlags, implicit_tag=0x81)), ASN1F_optional( ASN1F_PACKET("mechToken", None, SPNEGO_Token, explicit_tag=0xA2) ), # [MS-SPNG] flavor ! ASN1F_optional( ASN1F_PACKET("negHints", None, SPNEGO_negHints, explicit_tag=0xA3) ), ASN1F_optional( ASN1F_PACKET("mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA4) ), # Compat with RFC 4178's SPNEGO_negTokenInit ASN1F_optional( ASN1F_PACKET("_mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA3) ), ) # SPNEGO negTokenTarg # https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.2 class SPNEGO_negTokenResp(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_optional( ASN1F_ENUMERATED( "negResult", 0, { 0: "accept-completed", 1: "accept-incomplete", 2: "reject", 3: "request-mic", }, explicit_tag=0xA0, ), ), ASN1F_optional( ASN1F_PACKET( "supportedMech", SPNEGO_MechType(), SPNEGO_MechType, explicit_tag=0xA1 ), ), ASN1F_optional( ASN1F_PACKET("responseToken", None, SPNEGO_Token, explicit_tag=0xA2) ), ASN1F_optional( ASN1F_PACKET("mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA3) ), ) class SPNEGO_negToken(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_CHOICE( "token", SPNEGO_negTokenInit(), ASN1F_PACKET( "negTokenInit", SPNEGO_negTokenInit(), SPNEGO_negTokenInit, explicit_tag=0xA0, ), ASN1F_PACKET( "negTokenResp", SPNEGO_negTokenResp(), SPNEGO_negTokenResp, explicit_tag=0xA1, ), ) # Register for the GSS API Blob _GSSAPI_OIDS["1.3.6.1.5.5.2"] = SPNEGO_negToken _GSSAPI_SIGNATURE_OIDS["1.3.6.1.5.5.2"] = SPNEGO_negToken def mechListMIC(oids): """ Implementation of RFC 4178 - Appendix D. mechListMIC Computation """ return bytes(SPNEGO_MechTypes(mechTypes=oids)) # NEGOEX # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-negoex/0ad7a003-ab56-4839-a204-b555ca6759a2 _NEGOEX_AUTH_SCHEMES = { # Reversed. Is there any doc related to this? # The NEGOEX doc is very ellusive UUID("5c33530d-eaf9-0d4d-b2ec-4ae3786ec308"): "UUID('[NTLM-UUID]')", } class NEGOEX_MESSAGE_HEADER(Packet): fields_desc = [ StrFixedLenField("Signature", "NEGOEXTS", length=8), LEIntEnumField( "MessageType", 0, { 0x0: "INITIATOR_NEGO", 0x01: "ACCEPTOR_NEGO", 0x02: "INITIATOR_META_DATA", 0x03: "ACCEPTOR_META_DATA", 0x04: "CHALLENGE", 0x05: "AP_REQUEST", 0x06: "VERIFY", 0x07: "ALERT", }, ), LEIntField("SequenceNum", 0), LEIntField("cbHeaderLength", None), LEIntField("cbMessageLength", None), UUIDField("ConversationId", None), ] def post_build(self, pkt, pay): if self.cbHeaderLength is None: pkt = pkt[16:] + struct.pack(" bytes """Util function to build the offset and populate the lengths""" for field_name, value in self.fields["Payload"]: length = self.get_field("Payload").fields_map[field_name].i2len(self, value) count = self.get_field("Payload").fields_map[field_name].i2count(self, value) offset = fields[field_name] # Offset if self.getfieldval(field_name + "BufferOffset") is None: p = p[:offset] + struct.pack(" bytes return ( _NEGOEX_post_build( self, pkt, self.OFFSET, { "AuthScheme": 96, "Extension": 102, }, ) + pay ) @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 12: MessageType = struct.unpack("