def test_parse_public_key(self): expected = server_pub_key_token actual = TSRequest() actual.parse_data(public_key_ts_request) assert expected == actual['pub_key_auth'].value
def test_create_credential_ts_request(self): expected = credential_ts_request actual = TSRequest() actual['auth_info'].value = credentials_encrypted_password_creds assert expected == actual.get_data()
def test_get_empty_field(self): with self.assertRaises(AsnStructureException) as context: actual = TSRequest() actual.parse_data(credential_ts_request) actual['fake_field'] assert context.exception.args[ 0] == 'Illegal field fake_field in ASN.1 structure'
def test_parse_ts_request_wrong_type(self): test_data = hex_to_byte('A0 00 00 00 00') with self.assertRaises(AsnStructureException) as context: test_ts_request = TSRequest() test_ts_request.parse_data(test_data) assert context.exception.args[ 0] == 'Expecting TSRequest type to be (30), was (a0)'
def test_parse_ts_request_invalid_sequence(self): test_data = hex_to_byte('30 13 A0 03 02 01 03 A6 03 02 01 01') with self.assertRaises(AsnStructureException) as context: test_ts_request = TSRequest() test_ts_request.parse_data(test_data) assert context.exception.args[ 0] == 'Unknown sequence byte (a6) in sequence'
def test_create_negotiate_ts_request(self): expected = negotiate_ts_request actual_nego_data = NegoData() actual_nego_data['nego_token'].value = negotiate_token actual = TSRequest() actual['nego_tokens'].value = actual_nego_data.get_data() assert expected == actual.get_data()
def test_parse_challenge_ts_request(self): expected = challenge_token actual_ts_request = TSRequest() actual_ts_request.parse_data(challenge_ts_request) actual = NegoData() actual.parse_data(actual_ts_request['nego_tokens'].value) assert expected == actual['nego_token'].value
def test_verify_public_key_good(self, mock_unwrap): test_credssp_context = HttpCredSSPAuth('', '') test_ntlm_context = ntlm.Ntlm() test_ntlm_context.session_security = session_security.SessionSecurity(1, 'key'.encode()) test_credssp_context.context = test_ntlm_context test_ts_request = TSRequest() test_ts_request.parse_data(public_key_ts_request) test_public_key = hex_to_byte('00') + server_pub_key_token[1:] test_credssp_context._verify_public_keys(test_public_key, test_ts_request)
def test_create_authenticate_ts_request(self): expected = auth_ts_request actual_nego_data = NegoData() actual_nego_data['nego_token'].value = auth_token actual = TSRequest() actual['nego_tokens'].value = actual_nego_data.get_data() actual['pub_key_auth'].value = pub_key_token assert expected == actual.get_data()
def test_verify_public_key_invalid(self, mock_unwrap): test_credssp_context = HttpCredSSPAuth('', '') test_ntlm_context = ntlm.Ntlm() test_ntlm_context.session_security = session_security.SessionSecurity(1, 'key'.encode()) test_credssp_context.context = test_ntlm_context test_ts_request = TSRequest() test_ts_request.parse_data(public_key_ts_request) # Use the wrong first byte to ensure the keys don't match test_public_key = hex_to_byte('01') + server_pub_key_token[1:] with self.assertRaises(AssertionError) as context: test_credssp_context._verify_public_keys(test_public_key, test_ts_request) assert context.exception.args[0] == 'Could not verify key sent from the server, possibly man in the middle attack'
def _get_encrypted_credentials(self, context): """ [MS-CSSP] 3.1.5 Processing Events and Sequencing Rules - Step 5 https://msdn.microsoft.com/en-us/library/cc226791.aspx After the client has verified the server's authenticity, it encrypts the user's credentials with the authentication protocol's encryption services. The resulting value is encapsulated in the authInfo field of the TSRequest structure and sent over the encrypted TLS channel to the server :param context: The authenticated security context :return: The encrypted TSRequest that contains the user's credentials """ domain = u"" if "\\" in context.username: domain, username = context.username.split('\\', 1) else: username = context.username ts_password = TSPasswordCreds() ts_password['domainName'] = domain.encode('utf-16-le') ts_password['userName'] = username.encode('utf-16-le') ts_password['password'] = context.password.encode('utf-16-le') ts_credentials = TSCredentials() ts_credentials['credType'] = ts_password.CRED_TYPE ts_credentials['credentials'] = encoder.encode(ts_password) ts_request = TSRequest() enc_credentials = context.wrap(encoder.encode(ts_credentials)).data ts_request['authInfo'] = enc_credentials return encoder.encode(ts_request)
def test_check_error_code_version_3_none(self): test_data = utils.hex_to_byte('30 09 A0 03 02 01 03') test_ts_request = TSRequest() test_ts_request.parse_data(test_data) test_ts_request.check_error_code() assert test_ts_request['error_code'].value == None
def test_check_error_code_logon_failure(self): expected_byte = b'c000006d' test_data = hex_to_byte('30 13 A0 03 02 01 03 A4 06 02 04 C0 00 00 6D') with self.assertRaises(NTStatusException) as context: test_ts_request = TSRequest() test_ts_request.parse_data(test_data) test_ts_request.check_error_code() assert context.exception.args[ 0] == 'STATUS_LOGON_FAILURE - %s' % expected_byte
def test_check_error_code_undefinied(self): expected_byte = b'c000006e' test_data = hex_to_byte('30 13 A0 03 02 01 03 A4 06 02 04 C0 00 00 6E') with self.assertRaises(NTStatusException) as context: test_ts_request = TSRequest() test_ts_request.parse_data(test_data) test_ts_request.check_error_code() assert context.exception.args[ 0] == 'NTSTATUS error: Not Defined %s' % expected_byte
def _build_pub_key_auth(self, context, nonce, auth_token, public_key): """ [MS-CSSP] 3.1.5 Processing Events and Sequencing Rules - Step 3 https://msdn.microsoft.com/en-us/library/cc226791.aspx This step sends the final SPNEGO token to the server if required and computes the value for the pubKeyAuth field for the protocol version negotiated. The format of the pubKeyAuth field depends on the version that the server supports. For version 2 to 4: The pubKeyAuth field is just wrapped using the authenticated context For versions 5 to 6: The pubKeyAuth is a sha256 hash of the server's public key plus a nonce and a magic string value. This hash is wrapped using the authenticated context and the nonce is added to the TSRequest alongside the nonce used in the hash calcs. :param context: The authenticated context :param nonce: If versions 5+, the nonce to use in the hash :param auth_token: If NTLM, this is the last msg (authenticate msg) to send in the same request :param public_key: The server's public key :return: The TSRequest as a byte string to send to the server """ ts_request = TSRequest() if auth_token is not None: nego_token = NegoToken() nego_token['negoToken'] = auth_token ts_request['negoTokens'].append(nego_token) if nonce is not None: ts_request['clientNonce'] = nonce hash_input = b"CredSSP Client-To-Server Binding Hash\x00" + nonce + public_key pub_value = hashlib.sha256(hash_input).digest() else: pub_value = public_key enc_public_key = context.wrap(pub_value).data ts_request['pubKeyAuth'] = enc_public_key return encoder.encode(ts_request)
def credssp_generator(self): """ [MS-CSSP] 3.1.5 Processing Events and Sequencing Rules https://msdn.microsoft.com/en-us/library/cc226791.aspx Generator function that yields each CredSSP token to sent to the server. CredSSP has multiple steps that must be run for the client to successfully authenticate with the server and delegate the credentials. """ log.debug("Starting TLS handshake process") self.tls_connection = SSL.Connection(self.tls_context) self.tls_connection.set_connect_state() while True: try: self.tls_connection.do_handshake() except SSL.WantReadError: out_token = self.tls_connection.bio_read(self.BIO_BUFFER_SIZE) log.debug("Step 1. TLS Handshake, returning token: %s" % binascii.hexlify(out_token)) in_token = yield out_token, "Step 1. TLS Handshake" log.debug("Step 1. TLS Handshake, received token: %s" % binascii.hexlify(in_token)) self.tls_connection.bio_write(in_token) else: break log.debug("TLS Handshake complete. Protocol: %s, Cipher: %s" % (self.tls_connection.get_protocol_version_name(), self.tls_connection.get_cipher_name())) server_certificate = self.tls_connection.get_peer_certificate() server_public_key = self._get_subject_public_key(server_certificate) log.debug("Starting Authentication process") context = spnego.client(self.username, self.password, hostname=self.hostname, service='HTTP', protocol=self.auth_mechanism) out_token = context.step() while True: nego_token = NegoToken() nego_token['negoToken'] = out_token ts_request = TSRequest() ts_request['negoTokens'].append(nego_token) ts_request_token = encoder.encode(ts_request) log.debug("Step 2. Authenticate, returning token: %s" % binascii.hexlify(ts_request_token)) in_token = yield self.wrap(ts_request_token), "Step 2. Authenticate" in_token = self.unwrap(in_token) log.debug("Step 3. Authenticate, received token: %s" % binascii.hexlify(in_token)) ts_request = decoder.decode(in_token, asn1Spec=TSRequest())[0] ts_request.check_error_code() version = int(ts_request['version']) out_token = context.step(bytes(ts_request['negoTokens'][0]['negoToken'])) # Special edge case, we need to include the final NTLM token in the pubKeyAuth step but the context won't # be seen as complete at that stage so check if the known header is present. if context.complete or b"NTLMSSP\x00\x03\x00\x00\x00" in out_token: break version = min(version, TSRequest.CLIENT_VERSION) log.debug("Starting public key verification process at version %d" % version) if version < self.minimum_version: raise AuthenticationException("The reported server version was %d and did not meet the minimum " "requirements of %d" % (version, self.minimum_version)) if version > 4: nonce = os.urandom(32) else: log.warning("Reported server version was %d, susceptible to MitM attacks and should be patched - " "CVE 2018-0886" % version) nonce = None pub_key_auth = self._build_pub_key_auth(context, nonce, out_token, server_public_key) log.debug("Step 3. Server Authentication, returning token: %s" % binascii.hexlify(pub_key_auth)) in_token = yield self.wrap(pub_key_auth), "Step 3. Server Authentication" in_token = self.unwrap(in_token) log.debug("Step 3. Server Authentication, received token: %s" % binascii.hexlify(in_token)) log.debug("Starting server public key response verification") ts_request = decoder.decode(in_token, asn1Spec=TSRequest())[0] ts_request.check_error_code() if not ts_request['pubKeyAuth'].isValue: raise AuthenticationException("The server did not response with pubKeyAuth info, authentication was " "rejected") if len(ts_request['negoTokens']) > 0: # SPNEGO auth returned the mechListMIC for us to verify context.step(bytes(ts_request['negoTokens'][0]['negoToken'])) response_key = context.unwrap(bytes(ts_request['pubKeyAuth'])).data self._verify_public_keys(nonce, response_key, server_public_key) log.debug("Sending encrypted credentials") enc_credentials = self._get_encrypted_credentials(context) yield self.wrap(enc_credentials), "Step 5. Delegate Credentials"
def test_check_error_code_version_2_none(self): test_ts_request = TSRequest() test_ts_request.parse_data(challenge_ts_request) test_ts_request.check_error_code() assert test_ts_request['error_code'].value == None