tempesta-tech / tempesta

All-in-one solution for high performance web content delivery and advanced protection against DDoS and web attacks
https://tempesta-tech.com/
GNU General Public License v2.0
616 stars 103 forks source link

tls.test_tls_tickets.TlsTicketTest fails #1482

Closed krizhanovsky closed 3 years ago

krizhanovsky commented 3 years ago

It seems there is some bug in scapy-ssl_tls checksumming:

======================================================================
ERROR: test_empty_ticket (tls.test_tls_tickets.TlsTicketTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/tempesta-test/tls/test_tls_tickets.py", line 98, in test_empty_ticket
    res = hs_abb.do_12_resume(master_secret, ticket)
  File "/root/tempesta-test/tls/handshake.py", line 437, in do_12_resume
    if not self._do_12_hs_resume(master_secret, ticket, fuzzer):
  File "/root/tempesta-test/tls/handshake.py", line 400, in _do_12_hs_resume
    self.send_recv(tls.TLS.from_records([msg]))
  File "/root/tempesta-test/tls/handshake.py", line 198, in send_recv
    pkt, resp)
TLSProtocolError: FATAL alert returned by server: DECODE_ERROR
vankoven commented 3 years ago

The problem is in test suit, in scapy_ssl_tls layer actually. Multiple issues happens there and all of them trigger different issues.

First is dissecting issue: of encrypted Finished messages. They are formed in the next way:

TLSRecord: [
    type: Handshake
    version: 0x0303
    length: 40
    payload: [
        iv[8]
        EncryptedFinished
        mac
    ]
]

How the scapy dissects it:

This simple check cause multiple issues. First, Finished message from Tempesta is not dissected correctly: iv has zero value, that is perfectly dissected into two TlsHandshake messages with zero type and zero length, the rest of the message is dissected as third TlsHandshake message. Thus Finished message is never recognized by scapy. This is a reason for two issues: wrong checksum is sent by scapy on abbreviated handshake, since cleartext, not cipher text of Finished message must be used in the checksum calculation; and checksum of servers Finished message on Full handshake is never validated.

In the same time, scapy is happy with openssl servers, because openssl sends randomly generated iv. But in very unlikely situations, a very small or zero length can be parsed out of iv and the same situation as above will happen with openssl-based server.

Also a linked issue was caught: validaton of openssls Finished message may fail. Replace port 443 in tls.test_tls_tickets.TlsTicketTest.test_empty_ticket test with a port of openssl server, e.g. nginx. and star the test. A UserWarning: Verification of GCM tag failed: MAC check failed message may appear. Here is the trace back:

test_empty_ticket (tls.test_tls_tickets.TlsTicketTest) ... /home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls_crypto.py:1094: UserWarning: Verification of GCM tag failed: MAC check failed
  warnings.warn("Verification of GCM tag failed: %s" % why)
  File "./run_tests.py", line 303, in <module>
    result = testRunner.run(testsuite)
  File "/usr/lib/python2.7/unittest/runner.py", line 151, in run
    test(result)
  File "/usr/lib/python2.7/unittest/suite.py", line 70, in __call__
    return self.run(*args, **kwds)
  File "/usr/lib/python2.7/unittest/suite.py", line 108, in run
    test(result)
  File "/usr/lib/python2.7/unittest/case.py", line 393, in __call__
    return self.run(*args, **kwds)
  File "/usr/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "/home/user/tempesta-test/tls/test_tls_tickets.py", line 85, in test_empty_ticket
    res = hs.do_12()
  File "/home/user/tempesta-test/tls/handshake.py", line 431, in do_12
    if not self._do_12_hs(fuzzer):
  File "/home/user/tempesta-test/tls/handshake.py", line 337, in _do_12_hs
    resp = self.send_recv(msg1)
  File "/home/user/tempesta-test/tls/handshake.py", line 189, in send_recv
    resp = self.sock.recvall(timeout=self.io_to)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1298, in recvall
    records = TLS("".join(resp), ctx=self.tls_ctx, _origin=self._get_pkt_origin('in'))
  File "/usr/local/lib/python2.7/dist-packages/scapy/base_classes.py", line 266, in __call__
    i.__init__(*args, **kargs)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1332, in __init__
    Packet.__init__(self, *args, **fields)
  File "/usr/local/lib/python2.7/dist-packages/scapy/packet.py", line 158, in __init__
    self.dissect(_pkt)
  File "/usr/local/lib/python2.7/dist-packages/scapy/packet.py", line 875, in dissect
    s = self.do_dissect(s)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1363, in do_dissect
    payload = self.do_decrypt_payload(payload)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1382, in do_decrypt_payload
    record.content_type)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls_crypto.py", line 1095, in decrypt
    traceback.print_stack()
  File "./run_tests.py", line 303, in <module>
    result = testRunner.run(testsuite)
  File "/usr/lib/python2.7/unittest/runner.py", line 151, in run
    test(result)
  File "/usr/lib/python2.7/unittest/suite.py", line 70, in __call__
    return self.run(*args, **kwds)
  File "/usr/lib/python2.7/unittest/suite.py", line 108, in run
    test(result)
  File "/usr/lib/python2.7/unittest/case.py", line 393, in __call__
    return self.run(*args, **kwds)
  File "/usr/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "/home/user/tempesta-test/tls/test_tls_tickets.py", line 85, in test_empty_ticket
    res = hs.do_12()
  File "/home/user/tempesta-test/tls/handshake.py", line 433, in do_12
    return self._do_12_req(fuzzer)
  File "/home/user/tempesta-test/tls/handshake.py", line 414, in _do_12_req
    resp = self.send_recv(tls.TLSPlaintext(data=req))
  File "/home/user/tempesta-test/tls/handshake.py", line 189, in send_recv
    resp = self.sock.recvall(timeout=self.io_to)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1298, in recvall
    records = TLS("".join(resp), ctx=self.tls_ctx, _origin=self._get_pkt_origin('in'))
  File "/usr/local/lib/python2.7/dist-packages/scapy/base_classes.py", line 266, in __call__
    i.__init__(*args, **kargs)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1332, in __init__
    Packet.__init__(self, *args, **fields)
  File "/usr/local/lib/python2.7/dist-packages/scapy/packet.py", line 158, in __init__
    self.dissect(_pkt)
  File "/usr/local/lib/python2.7/dist-packages/scapy/packet.py", line 875, in dissect
    s = self.do_dissect(s)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1363, in do_dissect
    payload = self.do_decrypt_payload(payload)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls.py", line 1382, in do_decrypt_payload
    record.content_type)
  File "/home/user/tempesta-test/tls/scapy_ssl_tls/ssl_tls_crypto.py", line 1095, in decrypt
    traceback.print_stack()

Reverting eb4700fe55b392297b9e00b77e839aad58bb9c3f fixes this issue. But the message appears for Tepesta responses then. Looks like the origin issue in reverted patch was not linked to incorrect nonce, but it intriduces access to memory out of dissected data and that is why it works for tempesta, where the diisection is buggy, and fails for nginx with correct dissection