def step(self, input_token=None):
        if self._negotiate_message is None:
            self._negotiate_message = NegotiateMessage(self.negotiate_flags,
                                                       self.domain,
                                                       self.workstation)
            return self._negotiate_message.get_data()
        else:
            self._challenge_message = ChallengeMessage(input_token)
            self._authenticate_message = AuthenticateMessage(
                self.username, self.password, self.domain, self.workstation,
                self._challenge_message, self.ntlm_compatibility,
                server_certificate_hash=self._server_certificate_hash,
                cbt_data=self.cbt_data
            )
            self._authenticate_message.add_mic(self._negotiate_message,
                                               self._challenge_message)

            flag_bytes = self._authenticate_message.negotiate_flags
            flags = struct.unpack("<I", flag_bytes)[0]
            if flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL or \
                    flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
                self._session_security = SessionSecurity(
                    flags, self.session_key
                )

            self.complete = True
            return self._authenticate_message.get_data()
Beispiel #2
0
    def create_negotiate_message(self, domain_name=None, workstation=None):
        """
        Create an NTLM NEGOTIATE_MESSAGE

        :param domain_name: The domain name of the user account we are authenticating with, default is None
        :param worksation: The workstation we are using to authenticate with, default is None
        :return: A base64 encoded string of the NEGOTIATE_MESSAGE
        """
        self.negotiate_message = NegotiateMessage(self.negotiate_flags,
                                                  domain_name, workstation)

        return base64.b64encode(self.negotiate_message.get_data())
Beispiel #3
0
 def test_negotiate_without_domain_workstation(self):
     expected = b"\x4e\x54\x4c\x4d\x53\x53\x50\x00" \
                b"\x01\x00\x00\x00\x31\x82\x02\xe2" \
                b"\x00\x00\x00\x00\x28\x00\x00\x00" \
                b"\x00\x00\x00\x00\x28\x00\x00\x00" \
                b"\x06\x01\xb1\x1d\x00\x00\x00\x0f"
     actual = NegotiateMessage(3791815219, None, None).get_data()
     assert actual == expected
Beispiel #4
0
    def test_negotiate_without_domain_workstation(self):
        test_flags = ntlmv1_negotiate_flags

        expected = HexToByte('4e 54 4c 4d 53 53 50 00 01 00 00 00 32 82 02 e2'
                             '00 00 00 00 28 00 00 00 00 00 00 00 28 00 00 00'
                             '06 01 b1 1d 00 00 00 0f')

        actual = NegotiateMessage(test_flags, None, None).get_data()

        assert actual == expected
Beispiel #5
0
 def test_negotiate_with_all(self):
     expected = b"\x4e\x54\x4c\x4d\x53\x53\x50\x00" \
                b"\x01\x00\x00\x00\x31\xb2\x02\xe2" \
                b"\x06\x00\x06\x00\x28\x00\x00\x00" \
                b"\x08\x00\x08\x00\x2e\x00\x00\x00" \
                b"\x06\x01\xb1\x1d\x00\x00\x00\x0f" \
                b"\x44\x6f\x6d\x61\x69\x6e\x43\x4f" \
                b"\x4d\x50\x55\x54\x45\x52"
     actual = NegotiateMessage(3791815219, "Domain", "COMPUTER").get_data()
     assert actual == expected
Beispiel #6
0
 def test_negotiate_without_version(self):
     test_flags = 3791815219 - NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION
     expected = b"\x4e\x54\x4c\x4d\x53\x53\x50\x00" \
                b"\x01\x00\x00\x00\x31\xb2\x02\xe0" \
                b"\x06\x00\x06\x00\x28\x00\x00\x00" \
                b"\x08\x00\x08\x00\x2e\x00\x00\x00" \
                b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                b"\x44\x6f\x6d\x61\x69\x6e\x43\x4f" \
                b"\x4d\x50\x55\x54\x45\x52"
     actual = NegotiateMessage(test_flags, "Domain", "COMPUTER").get_data()
     assert actual == expected
Beispiel #7
0
    def create_negotiate_message(self, domain_name=None, workstation=None):
        """
        Create an NTLM NEGOTIATE_MESSAGE

        :param domain_name: The domain name of the user account we are authenticating with, default is None
        :param worksation: The workstation we are using to authenticate with, default is None
        :return: A base64 encoded string of the NEGOTIATE_MESSAGE
        """
        self.negotiate_message = NegotiateMessage(self.negotiate_flags, domain_name, workstation)

        return base64.b64encode(self.negotiate_message.get_data())
Beispiel #8
0
    def test_negotiate_with_all(self):
        test_flags = ntlmv1_negotiate_flags

        expected = HexToByte('4e 54 4c 4d 53 53 50 00 01 00 00 00 32 b2 02 e2'
                             '06 00 06 00 28 00 00 00 10 00 10 00 2e 00 00 00'
                             '06 01 b1 1d 00 00 00 0f 44 6f 6d 61 69 6e 43 00'
                             '4f 00 4d 00 50 00 55 00 54 00 45 00 52 00')

        actual = NegotiateMessage(test_flags, domain_name,
                                  workstation_name).get_data()

        assert actual == expected
Beispiel #9
0
    def test_negotiate_without_version(self):
        test_flags = ntlmv1_negotiate_flags
        test_flags -= NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION

        expected = HexToByte('4e 54 4c 4d 53 53 50 00 01 00 00 00 32 b2 02 e0'
                             '06 00 06 00 28 00 00 00 10 00 10 00 2e 00 00 00'
                             '00 00 00 00 00 00 00 00 44 6f 6d 61 69 6e 43 00'
                             '4f 00 4d 00 50 00 55 00 54 00 45 00 52 00')

        actual = NegotiateMessage(test_flags, domain_name,
                                  workstation_name).get_data()

        assert actual == expected
Beispiel #10
0
    def test_authenticate_message_with_mic(self, random_function,
                                           session_key_function):
        test_challenge_message = ChallengeMessage(ntlmv2_challenge_message)
        test_challenge_message.target_info[
            TargetInfo.MSV_AV_TIMESTAMP] = mock_timestamp()
        test_server_cert_hash = 'E3CA49271E5089CC48CE82109F1324F41DBEDDC29A777410C738F7868C4FF405'
        test_negotiate_message = NegotiateMessage(ntlmv2_negotiate_flags,
                                                  domain_name,
                                                  workstation_name)

        # Not a Microsoft example, using pre-computed value
        expected = HexToByte('4e 54 4c 4d 53 53 50 00 03 00 00 00 18 00 18 00'
                             '8c 00 00 00 7c 00 7c 00 a4 00 00 00 0c 00 0c 00'
                             '58 00 00 00 08 00 08 00 64 00 00 00 20 00 20 00'
                             '6c 00 00 00 10 00 10 00 20 01 00 00 31 82 8a e2'
                             '06 01 b1 1d 00 00 00 0f 77 ff c5 e6 db 55 87 0e'
                             '65 8d 7c ff 33 cd 90 2e 44 00 6f 00 6d 00 61 00'
                             '69 00 6e 00 55 00 73 00 65 00 72 00 43 00 00 00'
                             '4f 00 00 00 4d 00 00 00 50 00 00 00 55 00 00 00'
                             '54 00 00 00 45 00 00 00 52 00 00 00 00 00 00 00'
                             '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00'
                             '00 00 00 00 a1 3d 03 8a d0 ca 02 64 33 89 7c 33'
                             '5e 0f 56 df 01 01 00 00 00 00 00 00 00 00 00 00'
                             '00 00 00 00 aa aa aa aa aa aa aa aa 00 00 00 00'
                             '02 00 0c 00 44 00 6f 00 6d 00 61 00 69 00 6e 00'
                             '01 00 0c 00 53 00 65 00 72 00 76 00 65 00 72 00'
                             '07 00 08 00 00 00 00 00 00 00 00 00 06 00 04 00'
                             '02 00 00 00 0a 00 10 00 6e a1 9d f0 66 da 46 22'
                             '05 1f 9c 4f 92 c6 df 74 00 00 00 00 00 00 00 00'
                             '1d 08 89 d1 a5 ee ed 21 91 9e 1a b8 27 c3 0b 17')

        actual = AuthenticateMessage(user_name, password, domain_name,
                                     workstation_name, test_challenge_message,
                                     3, test_server_cert_hash)
        actual.add_mic(test_negotiate_message, test_challenge_message)
        actual = actual.get_data()

        assert actual == expected
Beispiel #11
0
class Ntlm(object):
    def __init__(self, ntlm_compatibility=3):
        """
        Initialises the NTLM context to use when sending and receiving messages
        to and from the server. You should be using this object as it supports
        NTLMv2 authenticate and it easier to use than before. It also brings in
        the ability to use signing and sealing with session_security and
        generate a MIC structure.

        :param ntlm_compatibility: (Default 3)
            The Lan Manager Compatibility Level to use with the auth message
            This is set by an Administrator in the registry key
            'HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel'
            The values correspond to the following;
                0 : LM and NTLMv1
                1 : LM, NTLMv1 and NTLMv1 with Extended Session Security
                2 : NTLMv1 and NTLMv1 with Extended Session Security
                3-5 : NTLMv2 Only
            Note: Values 3 to 5 are no different from a client perspective

        Attributes:
            negotiate_flags: A NEGOTIATE structure that contains a set of bit
                flags. These flags are the options the client supports and are
                sent in the negotiate_message
            ntlm_compatibility: The Lan Manager Compatibility Level, same as
                the input if supplied
            negotiate_message: A NegotiateMessage object that is sent to the
                server
            challenge_message: A ChallengeMessage object that has been created
                from the server response
            authenticate_message: An AuthenticateMessage object that is sent to
                the server based on the ChallengeMessage
            session_security: A SessionSecurity structure that can be used to
                sign and seal messages sent after the authentication challenge
        """
        self.ntlm_compatibility = ntlm_compatibility

        # Setting up our flags so the challenge message returns the target info
        # block if supported
        self.negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_56 | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL

        # Setting the message types based on the ntlm_compatibility level
        self._set_ntlm_compatibility_flags(self.ntlm_compatibility)

        self.negotiate_message = None
        self.challenge_message = None
        self.authenticate_message = None
        self.session_security = None

    def create_negotiate_message(self, domain_name=None, workstation=None):
        """
        Create an NTLM NEGOTIATE_MESSAGE

        :param domain_name: The domain name of the user account we are
            authenticating with, default is None
        :param worksation: The workstation we are using to authenticate with,
            default is None
        :return: A base64 encoded string of the NEGOTIATE_MESSAGE
        """
        self.negotiate_message = NegotiateMessage(self.negotiate_flags,
                                                  domain_name, workstation)

        return base64.b64encode(self.negotiate_message.get_data())

    def parse_challenge_message(self, msg2):
        """
        Parse the NTLM CHALLENGE_MESSAGE from the server and add it to the Ntlm
        context fields

        :param msg2: A base64 encoded string of the CHALLENGE_MESSAGE
        """
        msg2 = base64.b64decode(msg2)
        self.challenge_message = ChallengeMessage(msg2)

    def create_authenticate_message(self,
                                    user_name,
                                    password,
                                    domain_name=None,
                                    workstation=None,
                                    server_certificate_hash=None):
        """
        Create an NTLM AUTHENTICATE_MESSAGE based on the Ntlm context and the
        previous messages sent and received

        :param user_name: The user name of the user we are trying to
            authenticate with
        :param password: The password of the user we are trying to authenticate
            with
        :param domain_name: The domain name of the user account we are
            authenticated with, default is None
        :param workstation: The workstation we are using to authenticate with,
            default is None
        :param server_certificate_hash: The SHA256 hash string of the server
            certificate (DER encoded) NTLM is authenticating to. Used for
            Channel Binding Tokens. If nothing is supplied then the CBT hash
            will not be sent. See messages.py AuthenticateMessage for more
            details
        :return: A base64 encoded string of the AUTHENTICATE_MESSAGE
        """
        self.authenticate_message = \
            AuthenticateMessage(user_name, password, domain_name, workstation,
                                self.challenge_message,
                                self.ntlm_compatibility,
                                server_certificate_hash)
        self.authenticate_message.add_mic(self.negotiate_message,
                                          self.challenge_message)

        # Setups up the session_security context used to sign and seal messages
        # if wanted
        if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL or \
                self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
            flags = self.authenticate_message.negotiate_flags
            flag_bytes = struct.unpack("<I", flags)[0]
            self.session_security = \
                SessionSecurity(flag_bytes,
                                self.authenticate_message.exported_session_key)

        return base64.b64encode(self.authenticate_message.get_data())

    def _set_ntlm_compatibility_flags(self, ntlm_compatibility):
        if (ntlm_compatibility >= 0) and (ntlm_compatibility <= 5):
            if ntlm_compatibility == 0:
                self.negotiate_flags |= \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY
            elif ntlm_compatibility == 1:
                self.negotiate_flags |= \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
            else:
                self.negotiate_flags |= \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
        else:
            raise Exception("Unknown ntlm_compatibility level - "
                            "expecting value between 0 and 5")
class NtlmContext(object):

    def __init__(self, username, password, domain=None, workstation=None,
                 cbt_data=None, ntlm_compatibility=3):
        r"""
        Initialises a NTLM context to use when authenticating using the NTLM
        protocol.
        Initialises the NTLM context to use when sending and receiving messages
        to and from the server. You should be using this object as it supports
        NTLMv2 authenticate and it easier to use than before. It also brings in
        the ability to use signing and sealing with session_security and
        generate a MIC structure.

        :param username: The username to authenticate with
        :param password: The password for the username
        :param domain: The domain part of the username (None if n/a)
        :param workstation: The localworkstation (None if n/a)
        :param cbt_data: A GssChannelBindingsStruct or None to bind channel
            data with the auth process
        :param ntlm_compatibility: (Default 3)
            The Lan Manager Compatibility Level to use with the auth message
            This is set by an Administrator in the registry key
            'HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel'
            The values correspond to the following;
                0 : LM and NTLMv1
                1 : LM, NTLMv1 and NTLMv1 with Extended Session Security
                2 : NTLMv1 and NTLMv1 with Extended Session Security
                3-5 : NTLMv2 Only
            Note: Values 3 to 5 are no different from a client perspective
        """
        self.username = username
        self.password = password
        self.domain = domain
        self.workstation = workstation
        self.cbt_data = cbt_data
        self._server_certificate_hash = None  # deprecated for backwards compat
        self.ntlm_compatibility = ntlm_compatibility
        self.complete = False

        # Setting up our flags so the challenge message returns the target info
        # block if supported
        self.negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_56 | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN | \
            NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL

        # Setting the message types based on the ntlm_compatibility level
        self._set_ntlm_compatibility_flags(self.ntlm_compatibility)

        self._negotiate_message = None
        self._challenge_message = None
        self._authenticate_message = None
        self._session_security = None

    @property
    def mic_present(self):
        if self._authenticate_message:
            return bool(self._authenticate_message.mic)

        return False

    @property
    def session_key(self):
        if self._authenticate_message:
            return self._authenticate_message.exported_session_key

    def reset_rc4_state(self, outgoing=True):
        """ Resets the signing cipher for the incoming or outgoing cipher. For SPNEGO for calculating mechListMIC. """
        if self._session_security:
            self._session_security.reset_rc4_state(outgoing=outgoing)

    def step(self, input_token=None):
        if self._negotiate_message is None:
            self._negotiate_message = NegotiateMessage(self.negotiate_flags,
                                                       self.domain,
                                                       self.workstation)
            return self._negotiate_message.get_data()
        else:
            self._challenge_message = ChallengeMessage(input_token)
            self._authenticate_message = AuthenticateMessage(
                self.username, self.password, self.domain, self.workstation,
                self._challenge_message, self.ntlm_compatibility,
                server_certificate_hash=self._server_certificate_hash,
                cbt_data=self.cbt_data
            )
            self._authenticate_message.add_mic(self._negotiate_message,
                                               self._challenge_message)

            flag_bytes = self._authenticate_message.negotiate_flags
            flags = struct.unpack("<I", flag_bytes)[0]
            if flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL or \
                    flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
                self._session_security = SessionSecurity(
                    flags, self.session_key
                )

            self.complete = True
            return self._authenticate_message.get_data()

    def sign(self, data):
        return self._session_security.get_signature(data)

    def verify(self, data, signature):
        self._session_security.verify_signature(data, signature)

    def wrap(self, data):
        if self._session_security is None:
            raise NoAuthContextError("Cannot wrap data as no security context "
                                     "has been established")

        data, header = self._session_security.wrap(data)
        return header + data

    def unwrap(self, data):
        if self._session_security is None:
            raise NoAuthContextError("Cannot unwrap data as no security "
                                     "context has been established")
        header = data[0:16]
        data = data[16:]
        message = self._session_security.unwrap(data, header)
        return message

    def _set_ntlm_compatibility_flags(self, ntlm_compatibility):
        if (ntlm_compatibility >= 0) and (ntlm_compatibility <= 5):
            if ntlm_compatibility == 0:
                self.negotiate_flags |= \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY
            elif ntlm_compatibility == 1:
                self.negotiate_flags |= \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
            else:
                self.negotiate_flags |= \
                    NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
        else:
            raise Exception("Unknown ntlm_compatibility level - "
                            "expecting value between 0 and 5")
Beispiel #13
0
    def test_authenticate_message_with_mic(self, monkeypatch):
        monkeypatch.setattr('os.urandom', lambda s: b"\xaa" * 8)
        monkeypatch.setattr('ntlm_auth.messages.get_random_export_session_key',
                            lambda: b"\x55" * 16)

        test_challenge_message = ChallengeMessage(
            b"\x4e\x54\x4c\x4d\x53\x53\x50\x00"
            b"\x02\x00\x00\x00\x03\x00\x0c\x00"
            b"\x38\x00\x00\x00\x33\x82\x8a\xe2"
            b"\x01\x23\x45\x67\x89\xab\xcd\xef"
            b"\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x24\x00\x24\x00\x44\x00\x00\x00"
            b"\x06\x00\x70\x17\x00\x00\x00\x0f"
            b"\x53\x00\x65\x00\x72\x00\x76\x00"
            b"\x65\x00\x72\x00\x02\x00\x0c\x00"
            b"\x44\x00\x6f\x00\x6d\x00\x61\x00"
            b"\x69\x00\x6e\x00\x01\x00\x0c\x00"
            b"\x53\x00\x65\x00\x72\x00\x76\x00"
            b"\x65\x00\x72\x00\x00\x00\x00\x00")
        test_challenge_message.target_info[AvId.MSV_AV_TIMESTAMP] = b"\x00" * 8
        test_server_cert_hash = \
            'E3CA49271E5089CC48CE82109F1324F41DBEDDC29A777410C738F7868C4FF405'
        test_negotiate_message = NegotiateMessage(3800728115, "Domain",
                                                  "COMPUTER")

        # Not a Microsoft example, using pre-computed value
        expected = b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
                   b"\x03\x00\x00\x00\x18\x00\x18\x00" \
                   b"\x7C\x00\x00\x00\x7C\x00\x7C\x00" \
                   b"\x94\x00\x00\x00\x0C\x00\x0C\x00" \
                   b"\x58\x00\x00\x00\x08\x00\x08\x00" \
                   b"\x64\x00\x00\x00\x10\x00\x10\x00" \
                   b"\x6C\x00\x00\x00\x10\x00\x10\x00" \
                   b"\x10\x01\x00\x00\x31\x82\x8A\xE2" \
                   b"\x06\x01\xB1\x1D\x00\x00\x00\x0F" \
                   b"\xD2\xA1\x45\xDE\xA4\x25\x3E\x19" \
                   b"\x10\xFE\x0F\x5B\x7A\x0D\x2A\x90" \
                   b"\x44\x00\x6F\x00\x6D\x00\x61\x00" \
                   b"\x69\x00\x6E\x00\x55\x00\x73\x00" \
                   b"\x65\x00\x72\x00\x43\x00\x4F\x00" \
                   b"\x4D\x00\x50\x00\x55\x00\x54\x00" \
                   b"\x45\x00\x52\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\xA1\x3D\x03\x8A" \
                   b"\xD0\xCA\x02\x64\x33\x89\x7C\x33" \
                   b"\x5E\x0F\x56\xDF\x01\x01\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\xAA\xAA\xAA\xAA" \
                   b"\xAA\xAA\xAA\xAA\x00\x00\x00\x00" \
                   b"\x02\x00\x0C\x00\x44\x00\x6F\x00" \
                   b"\x6D\x00\x61\x00\x69\x00\x6E\x00" \
                   b"\x01\x00\x0C\x00\x53\x00\x65\x00" \
                   b"\x72\x00\x76\x00\x65\x00\x72\x00" \
                   b"\x07\x00\x08\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x06\x00\x04\x00" \
                   b"\x02\x00\x00\x00\x0A\x00\x10\x00" \
                   b"\x6E\xA1\x9D\xF0\x66\xDA\x46\x22" \
                   b"\x05\x1F\x9C\x4F\x92\xC6\xDF\x74" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x1D\x08\x89\xD1\xA5\xEE\xED\x21" \
                   b"\x91\x9E\x1A\xB8\x27\xC3\x0B\x17"

        actual = AuthenticateMessage("User", "Password", "Domain", "COMPUTER",
                                     test_challenge_message, 3,
                                     test_server_cert_hash)
        actual.add_mic(test_negotiate_message, test_challenge_message)
        actual = actual.get_data()
        assert actual == expected
Beispiel #14
0
class Ntlm(object):
    """
    Initialises the NTLM context to use when sending and receiving messages to and from the server. You should be
    using this object as it supports NTLMv2 authenticate and it easier to use than before. It also brings in the
    ability to use signing and sealing with session_security and generate a MIC structure.

    :param ntlm_compatibility: The Lan Manager Compatibility Level to use withe the auth message - Default 3
                                This is set by an Administrator in the registry key
                                'HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel'
                                The values correspond to the following;
                                    0 : LM and NTLMv1
                                    1 : LM, NTLMv1 and NTLMv1 with Extended Session Security
                                    2 : NTLMv1 and NTLMv1 with Extended Session Security
                                    3-5 : NTLMv2 Only
                                Note: Values 3 to 5 are no different as the client supports the same types

    Attributes:
        negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports and are sent in the negotiate_message
        ntlm_compatibility: The Lan Manager Compatibility Level, same as the input if supplied
        negotiate_message: A NegotiateMessage object that is sent to the server
        challenge_message: A ChallengeMessage object that has been created from the server response
        authenticate_message: An AuthenticateMessage object that is sent to the server based on the ChallengeMessage
        session_security: A SessionSecurity structure that can be used to sign and seal messages sent after the authentication challenge
    """
    def __init__(self, ntlm_compatibility=3):
        self.ntlm_compatibility = ntlm_compatibility

        # Setting up our flags so the challenge message returns the target info block if supported
        self.negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_56 | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN | \
                               NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL

        # Setting the message types based on the ntlm_compatibility level
        self._set_ntlm_compatibility_flags(self.ntlm_compatibility)

        self.negotiate_message = None
        self.challenge_message = None
        self.authenticate_message = None
        self.session_security = None


    def create_negotiate_message(self, domain_name=None, workstation=None):
        """
        Create an NTLM NEGOTIATE_MESSAGE

        :param domain_name: The domain name of the user account we are authenticating with, default is None
        :param worksation: The workstation we are using to authenticate with, default is None
        :return: A base64 encoded string of the NEGOTIATE_MESSAGE
        """
        self.negotiate_message = NegotiateMessage(self.negotiate_flags, domain_name, workstation)

        return base64.b64encode(self.negotiate_message.get_data())

    def parse_challenge_message(self, msg2):
        """
        Parse the NTLM CHALLENGE_MESSAGE from the server and add it to the Ntlm context fields

        :param msg2: A base64 encoded string of the CHALLENGE_MESSAGE
        """
        msg2 = base64.b64decode(msg2)
        self.challenge_message = ChallengeMessage(msg2)

    def create_authenticate_message(self, user_name, password, domain_name=None, workstation=None, server_certificate_hash=None):
        """
        Create an NTLM AUTHENTICATE_MESSAGE based on the Ntlm context and the previous messages sent and received

        :param user_name: The user name of the user we are trying to authenticate with
        :param password: The password of the user we are trying to authenticate with
        :param domain_name: The domain name of the user account we are authenticated with, default is None
        :param workstation: The workstation we are using to authenticate with, default is None
        :param server_certificate_hash: The SHA256 hash string of the server certificate (DER encoded) NTLM is authenticating to. Used for Channel
                                        Binding Tokens. If nothing is supplied then the CBT hash will not be sent. See messages.py AuthenticateMessage
                                        for more details
        :return: A base64 encoded string of the AUTHENTICATE_MESSAGE
        """
        self.authenticate_message = AuthenticateMessage(user_name, password, domain_name, workstation,
                                                        self.challenge_message, self.ntlm_compatibility,
                                                        server_certificate_hash)
        self.authenticate_message.add_mic(self.negotiate_message, self.challenge_message)

        # Setups up the session_security context used to sign and seal messages if wanted
        if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL or self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
            self.session_security = SessionSecurity(struct.unpack("<I", self.authenticate_message.negotiate_flags)[0],
                                                    self.authenticate_message.exported_session_key)

        return base64.b64encode(self.authenticate_message.get_data())

    def _set_ntlm_compatibility_flags(self, ntlm_compatibility):
        if (ntlm_compatibility >= 0) and (ntlm_compatibility <= 5):
            if ntlm_compatibility == 0:
                self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
                                        NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY
            elif ntlm_compatibility == 1:
                self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
                                        NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
            else:
                self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
        else:
            raise Exception("Unknown ntlm_compatibility level - expecting value between 0 and 5")
Beispiel #15
0
    def test_authenticate_message_with_mic(self, monkeypatch):
        monkeypatch.setattr('os.urandom', lambda s: b"\xaa" * 8)
        monkeypatch.setattr('ntlm_auth.messages.get_random_export_session_key',
                            lambda: b"\x55" * 16)

        test_challenge_message = ChallengeMessage(
            b"\x4e\x54\x4c\x4d\x53\x53\x50\x00"
            b"\x02\x00\x00\x00\x03\x00\x0c\x00"
            b"\x38\x00\x00\x00\x33\x82\x8a\xe2"
            b"\x01\x23\x45\x67\x89\xab\xcd\xef"
            b"\x00\x00\x00\x00\x00\x00\x00\x00"
            b"\x24\x00\x24\x00\x44\x00\x00\x00"
            b"\x06\x00\x70\x17\x00\x00\x00\x0f"
            b"\x53\x00\x65\x00\x72\x00\x76\x00"
            b"\x65\x00\x72\x00\x02\x00\x0c\x00"
            b"\x44\x00\x6f\x00\x6d\x00\x61\x00"
            b"\x69\x00\x6e\x00\x01\x00\x0c\x00"
            b"\x53\x00\x65\x00\x72\x00\x76\x00"
            b"\x65\x00\x72\x00\x00\x00\x00\x00")
        test_challenge_message.target_info[AvId.MSV_AV_TIMESTAMP] = b"\x00" * 8
        test_server_cert_hash = \
            'E3CA49271E5089CC48CE82109F1324F41DBEDDC29A777410C738F7868C4FF405'
        test_negotiate_message = NegotiateMessage(3800728115, "Domain",
                                                  "COMPUTER")

        # Not a Microsoft example, using pre-computed value
        expected = b"\x4e\x54\x4c\x4d\x53\x53\x50\x00" \
                   b"\x03\x00\x00\x00\x18\x00\x18\x00" \
                   b"\x7c\x00\x00\x00\x7c\x00\x7c\x00" \
                   b"\x94\x00\x00\x00\x0c\x00\x0c\x00" \
                   b"\x58\x00\x00\x00\x08\x00\x08\x00" \
                   b"\x64\x00\x00\x00\x10\x00\x10\x00" \
                   b"\x6c\x00\x00\x00\x10\x00\x10\x00" \
                   b"\x10\x01\x00\x00\x31\x82\x8a\xe2" \
                   b"\x06\x01\xb1\x1d\x00\x00\x00\x0f" \
                   b"\x8b\x69\xf5\x92\xb2\xd7\x8f\xd7" \
                   b"\x3a\x3a\x49\xdb\xfe\x19\x61\xbc" \
                   b"\x44\x00\x6f\x00\x6d\x00\x61\x00" \
                   b"\x69\x00\x6e\x00\x55\x00\x73\x00" \
                   b"\x65\x00\x72\x00\x43\x00\x4f\x00" \
                   b"\x4d\x00\x50\x00\x55\x00\x54\x00" \
                   b"\x45\x00\x52\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\xa1\x3d\x03\x8a" \
                   b"\xd0\xca\x02\x64\x33\x89\x7c\x33" \
                   b"\x5e\x0f\x56\xdf\x01\x01\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\xaa\xaa\xaa\xaa" \
                   b"\xaa\xaa\xaa\xaa\x00\x00\x00\x00" \
                   b"\x02\x00\x0c\x00\x44\x00\x6f\x00" \
                   b"\x6d\x00\x61\x00\x69\x00\x6e\x00" \
                   b"\x01\x00\x0c\x00\x53\x00\x65\x00" \
                   b"\x72\x00\x76\x00\x65\x00\x72\x00" \
                   b"\x07\x00\x08\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x06\x00\x04\x00" \
                   b"\x02\x00\x00\x00\x0a\x00\x10\x00" \
                   b"\x6e\xa1\x9d\xf0\x66\xda\x46\x22" \
                   b"\x05\x1f\x9c\x4f\x92\xc6\xdf\x74" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x1d\x08\x89\xd1\xa5\xee\xed\x21" \
                   b"\x91\x9e\x1a\xb8\x27\xc3\x0b\x17"

        actual = AuthenticateMessage("User", "Password", "Domain", "COMPUTER",
                                     test_challenge_message, 3,
                                     test_server_cert_hash)
        actual.add_mic(test_negotiate_message, test_challenge_message)
        actual = actual.get_data()
        assert actual == expected