Skip to content

[Bug]: RFC 8446 violation : WolfSSL accept HelloRetryRequests/ServerHello with changing ciphers #10016

@nataelbaffou

Description

@nataelbaffou

Contact Details

No response

Version

5.8.4

Description

Similar issue that #9331 but server side.

A WolfSSL TLS 1.3 server receiving à first ClientHello with a specific ciphersuite and no keyshare or unsupported keyshare will send an HRR. When receiving a second ClientHello with another ciphersuite, he will continue the handshake and send a ServerHello with the new ciphersuite.

Ex: TLS_AES_256_GCM_SHA384 in CH1/HRR then TLS_AES_128_GCM_SHA256 in CH2/ServerHello

According to the RFC 8446 section 4.1.6 :
_The client will also send a ClientHello when the server has responded to its ClientHello with a HelloRetryRequest. In that case, the client MUST send the same ClientHello without modification, except as follows:

  • If a "key_share" extension was supplied in the HelloRetryRequest, replacing the list of shares with a list containing a single KeyShareEntry from the indicated group.
  • Removing the "early_data" extension (Section 4.2.10) if one was present. Early data is not permitted after a HelloRetryRequest.
  • Including a "cookie" extension if one was provided in the HelloRetryRequest.
  • Updating the "pre_shared_key" extension if present by recomputing the "obfuscated_ticket_age" and binder values and (optionally) removing any PSKs which are incompatible with the server's indicated cipher suite.
  • Optionally adding, removing, or changing the length of the "padding" extension [RFC7685].
  • Other modifications that may be allowed by an extension defined in the future and present in the HelloRetryRequest._

The behaviour from the client is illegal and the RFC does not explicitly asks for the server to reject the second ClientHello.

According to the RFC 8446 section 4.1.4 :
A client which receives a cipher suite that was not offered MUST abort the handshake. Servers MUST ensure that they negotiate the same cipher suite when receiving a conformant updated ClientHello (if the server selects the cipher suite as the first step in the negotiation, then this will happen automatically). Upon receiving the ServerHello, clients MUST check that the cipher suite supplied in the ServerHello is the same as that in the HelloRetryRequest and otherwise abort the handshake with an “illegal_parameter” alert.

The updated ClientHello is not "conformant". The RFC does not explicitly asks for a particular action from the server for a non-conformant ClientHello but if it selects the second cipher then the client theoretically abort with a "illegal_parameter" alert which means this is not the correct behaviour. The RFC doesn't spell it out explicitly but the server should abort the connection.

In RFC section 6 :
Peers which receive a message which is syntactically correct but semantically invalid (e.g., a DHE share of p - 1, or an invalid enum) MUST terminate the connection with an "illegal_parameter" alert.

In regard to this section, the server should abort with an "illegal_parameter" alert.

Impact

The transcript hash computed by the server is as followed:
With H1 the hash function of the ciphersuite provided by the client in ClientHello1 (CH1)
With H2 the hash function of the ciphersuite provided by the client in ClientHello2 (CH2)
transcript_hash = H2[message_hash | HRR | CH2 | SH]
message_hash = header | H1[CH1]

When computing the message hash it keeps H1 from HRR then all hashes are computed in parallel and H2 is kept after the ServerHello.
No vulnerability has been found using this hash transcript composition.
The same hash computation was done in the client side with the bug found in #9331

Expected behavior

WolfSSL server should abort the connection after the second ClientHello.

Reproduction steps

Here is an example of a TLS 1.3 handshake that triggers the described behaviour :

  • Send the following ClientHello
    • Record Layer
      • ContentType: Handshake
      • Version: TLS 1.2 (legacy marker)
      • Length: 110
    • Handshake:
      • Type: ClientHello
      • Length: 106
      • Version: 0x0303
      • Random: 0101010101010101010101010101010101010101010101010101010101010101
      • SessionID: 0303030303030303030303030303030303030303030303030303030303030303 (32 bytes)
      • CipherSuites: TLS_AES_256_GCM_SHA384 (0x1302)
      • Compression: null
      • Extensions:
        • supported_groups = secp384r1 (0x0018)
        • signature_algorithms = rsa_pkcs1_sha256 (0x0401), rsa_pss_rsae_sha256 (0x0804)
        • key_share = empty list ← triggers HRR
        • supported_versions = TLS 1.3 (0x0304)
    • in raw hex : 160303006e0100006a03030101010101010101010101010101010101010101010101010101010101010101200303030303030303030303030303030303030303030303030303030303030303000213020100001f000a000400020018000d0006000404010804003300020000002b0003020304
  • Wait for HRR
  • Send the following ClientHello
    • Record Layer
      • ContentType: Handshake
      • Version: TLS 1.2 (legacy marker)
      • Length: 211
    • Handshake:
      • Type: ClientHello
      • Length: 207
      • Version: 0x0303
      • Random: 0101010101010101010101010101010101010101010101010101010101010101
      • SessionID: 0303030303030303030303030303030303030303030303030303030303030303 (32 bytes)
      • CipherSuites: TLS_AES_128_GCM_SHA256 (0x1301) ← mismatches HRR-selected 0x1302
      • Compression: null
      • Extensions:
        • supported_groups = secp384r1 (0x0018)
        • signature_algorithms = rsa_pkcs1_sha256 (0x0401), rsa_pss_rsae_sha256 (0x0804)
        • key_share = secp384r1, key_exchange (97) : 04533ee5bf40ec2d67988b77f317489bb6df952925c709fc0381111a5956f2d758110e59d3d7c1729e2c0d70eaf773e6120116426de2436a2f5fdd7fe54faf952b04fd13f516ce627f89d2019d4c8796959e4333c7065b496ca634d5dc63bde91f
        • supported_versions = TLS 1.3 (0x0304)
    • in raw hex : 16030300d3010000cf030301010101010101010101010101010101010101010101010101010101010101012003030303030303030303030303030303030303030303030303030303030303030002130101000084000a000400020018000d00060004040108040033006700650018006104533ee5bf40ec2d67988b77f317489bb6df952925c709fc0381111a5956f2d758110e59d3d7c1729e2c0d70eaf773e6120116426de2436a2f5fdd7fe54faf952b04fd13f516ce627f89d2019d4c8796959e4333c7065b496ca634d5dc63bde91f002b0003020304
  • Wait for ServerHello
    • Receives it with CipherSuites 0x1301 selected -> mismatch with HRR 0x1302

To reproduce :
Start a WolfSSL TLS1.3 server:
./build/examples/server/server -p 3000 -v 4

Then start the following python client:

import socket

HOST = "0.0.0.0"
PORT = 3000


# First ClientHello: cipher TLS_AES_256_GCM_SHA384, empty key_share
client_hello_1 = bytes.fromhex("160303006e0100006a03030101010101010101010101010101010101010101010101010101010101010101200303030303030303030303030303030303030303030303030303030303030303000213020100001f000a000400020018000d0006000404010804003300020000002b0003020304")

# Second ClientHello: cipher TLS_AES_128_GCM_SHA256, with secp384r1 key share
client_hello_2 = bytes.fromhex("16030300d3010000cf030301010101010101010101010101010101010101010101010101010101010101012003030303030303030303030303030303030303030303030303030303030303030002130101000084000a000400020018000d00060004040108040033006700650018006104533ee5bf40ec2d67988b77f317489bb6df952925c709fc0381111a5956f2d758110e59d3d7c1729e2c0d70eaf773e6120116426de2436a2f5fdd7fe54faf952b04fd13f516ce627f89d2019d4c8796959e4333c7065b496ca634d5dc63bde91f002b0003020304")


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))
    print(f"[+] Connected to {HOST}:{PORT}")

    # --- Round 1: trigger HelloRetryRequest ---

    sock.sendall(client_hello_1)
    print(f"[>] Sent first ClientHello ({len(client_hello_1)} bytes)")

    hrr = sock.recv(1024)
    print(f"[<] Received HRR ({len(hrr)} bytes): {hrr.hex()}")

    # --- Round 2: full handshake ---

    sock.sendall(client_hello_2)
    print(f"[>] Sent second ClientHello ({len(client_hello_2)} bytes)")

    server_hello = sock.recv(1024)
    print(f"[<] Received ServerHello ({len(server_hello)} bytes): {server_hello.hex()}")

You should see the server sending a ServerHello instead of aborting the connection.

Acknowledgments

This bug was found thanks to the tlspuffin fuzzer designed and developed by the tlspuffin

  • Max Ammann
  • Olivier Demengeon - Loria, Inria
  • Tom Gouville - Loria, Inria
  • Lucca Hirschi - Loria, Inria
  • Steve Kremer - Loria, Inria
  • Michael Mera - Loria, Inria
  • Nataël Baffou - Loria, Inria

Relevant log output

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions