Bug #7476
openSuricata tls.sni check fails when PQC KEMs like kyber
Description
As clients begin migrating to PQC, such as the latest versions of Chrome and Golang which now use the Kyber KEM, many of our SNI-based rules have stopped functioning.
Example rule:
pass tls any any -> any any (msg:"Allow connection to test.openquantumsafe.org/"; flow:to_server,established; tls.sni;content:"test.openquantumsafe.org"; nocase; isdataat:!1,relative; sid:1;)
rejectboth tls any any -> any any (msg:"Unauthorized TLS outbound connection in any"; flow:to_server,established; sid:2;)
Example request: curl --verbose --curves kyber768:X25519 https://test.openquantumsafe.org/
Note :Communication works if I don't overwrite the curve
Logs from even.json: {"timestamp":"2025-01-02T20:20:15.023011+0000","flow_id":1866019709982669,"event_type":"alert","src_ip":"10.128.0.6","src_port":40248,"dest_ip":"158.177.128.14","dest_port":443,"proto":"TCP","pkt_src":"wire/pcap","alert":{"action":"blocked","gid":1,"signature_id":1,"rev":0,"signature":"Unauthorized TLS outbound connection","category":"","severity":3},"tls":{"version":"UNDETERMINED"},"app_proto":"tls","direction":"to_server","flow":{"pkts_toserver":3,"pkts_toclient":1,"bytes_toserver":1572,"bytes_toclient":60,"start":"2025-01-02T20:20:14.893218+0000","src_ip":"10.128.0.6","dest_ip":"158.177.128.14","src_port":40248,"dest_port":443}}
Here is the suricata build info
This is Suricata version 7.0.4 RELEASE
Features: NFQ PCAP_SET_BUFF AF_PACKET HAVE_PACKET_FANOUT LIBCAP_NG LIBNET1.1 HAVE_HTP_URI_NORMALIZE_HOOK PCRE_JIT HAVE_NSS HTTP2_DECOMPRESSION HAVE_LUA HAVE_LIBJANSSON TLS TLS_C11 MAGIC RUST
SIMD support: SSE_4_2 SSE_4_1 SSE_3
Atomic intrinsics: 1 2 4 8 16 byte(s)
64-bits, Little-endian architecture
GCC version 11.4.1 20230605 (Red Hat 11.4.1-2), C version 201112
compiled with _FORTIFY_SOURCE=0
L1 cache line size (CLS)=64
thread local storage method: _Thread_local
compiled with LibHTP v0.5.47, linked against LibHTP v0.5.47
Suricata Configuration:
AF_PACKET support: yes
AF_XDP support: no
DPDK support: yes
eBPF support: yes
XDP support: yes
PF_RING support: no
NFQueue support: yes
NFLOG support: no
IPFW support: no
Netmap support: no
DAG enabled: no
Napatech enabled: no
WinDivert enabled: no
Unix socket enabled: yes
Detection enabled: yes
Libmagic support: yes
libjansson support: yes
hiredis support: yes
hiredis async with libevent: yes
PCRE jit: yes
LUA support: yes
libluajit: no
GeoIP2 support: yes
Non-bundled htp: no
Hyperscan support: yes
Libnet support: yes
liblz4 support: yes
Landlock support: yes
Rust support: yes
Rust strict mode: no
Rust compiler path: /usr/bin/rustc
Rust compiler version: rustc 1.71.1 (eb26296b5 2023-08-03) (Red Hat 1.71.1-1.el9)
Cargo path: /usr/bin/cargo
Cargo version: cargo 1.71.1
Python support: yes
Python path: /usr/bin/python3
Install suricatactl: yes
Install suricatasc: yes
Install suricata-update: yes
Profiling enabled: no
Profiling locks enabled: no
Profiling rules enabled: no
Plugin support (experimental): yes
DPDK Bond PMD: no
Development settings:
Coccinelle / spatch: no
Unit tests enabled: no
Debug output enabled: no
Debug validation enabled: no
Fuzz targets enabled: no
Generic build parameters:
Installation prefix: /usr
Configuration directory: /etc/suricata/
Log directory: /var/log/suricata/
--prefix /usr
--sysconfdir /etc
--localstatedir /var
--datarootdir /usr/share
Host: x86_64-pc-linux-gnu
Compiler: gcc (exec name) / g++ (real)
GCC Protect enabled: no
GCC march native enabled: no
GCC Profile enabled: no
Position Independent Executable enabled: no
CFLAGS -g -O2 -fPIC -std=c11 -I/usr/include/dpdk -include rte_config.h -march=corei7 -I${srcdir}/../rust/gen -I${srcdir}/../rust/dist
PCAP_CFLAGS
SECCFLAGS
Files
Updated by Victor Julien 10 months ago
- Status changed from New to Feedback
- Priority changed from Urgent to Normal
Can you attach a pcap or create a full Suricata-Verify test?
Updated by Victor Julien 10 months ago
Do you also have a pcap from a successful session (so w/o Suricata)? The current pcap appears to work correctly.
Updated by Shashank Gugalia 10 months ago
Attaching pcap where its working.
Also was not clear with your comment "The current pcap appears to work correctly"
Like curl actually failed with connection reset error, so when you say its working correctly I am not very clear what you meant by that
Updated by Victor Julien 10 months ago
Based on the pcaps, things work as expected. The sni rule matches and puts the flow into "pass" mode. I've tested with IPS mode.
Can you describe your setup? Runmode, capture settings, IPS/IDS, etc?
Updated by Shashank Gugalia 10 months ago
We using IPS mode with these config overwritten
runmode: workers
nfq:
mode: accept
batchcount: 5
fail-open: yes
Default suricata.yaml gets is taken from https://github.com/jasonish/docker-suricata/tree/main/7.0
Updated by Shashank Gugalia 10 months ago
Hi Victor,
Please let me know if you need info/data from my end
Thanks
Shashank
Updated by Shashank Gugalia 9 months ago
- File tcpump.pcap tcpump.pcap added
Another pcap for releases.hashicorp.com where we are getting connection reset
Updated by Shashank Gugalia 9 months ago
Pass rule is
pass tls $src_ip any -> any 443 (msg:"Allow terraform binaries from releases.hashicorp.com"; flow:to_server,established; tls_sni; content:"releases.hashicorp.com"; nocase; isdataat:!1,relative; sid:77002;)
Updated by Victor Julien 9 months ago
I believe this is not related to the specific curve, but instead is due to the client hello getting split over multiple packets due to it's size. This leads to a problem with the rules above.
Packet X:
[ client hello part 1]: TLS detection, toserver, establised, so sid:2 matches
Packet Y:
[ client hello part 2]: client hello parsed, SNI available
What happens here is that the reject rule triggers for packet X, since all conditions match. Packet Y completes the client hello, which means only then sid:1 is evaluated.
The problem is that this scenario is not well supported, as the rule language doesn't give you a way to pass packet X currently.
There is work ongoing to address this, see:
#7164, #7485
https://github.com/OISF/suricata/pull/12422
https://github.com/OISF/suricata-verify/pull/2239/files#diff-9c1a8fa7b32b878c51333b3e87a2bdc2e91afd1efed930faf15ffe7b8a0c0593
Updated by Brinn Joyce 4 months ago
We ran into the same issue and resolved it by adding `ssl_state:client_hello` to our reject/drop rule - alternatively it could be added to the pass rules.
Updated by Ramesh Reddy 4 months ago
we tried upgrading to suricata 8.0 with the same ruleset as below and we are still facing the same issue.
Example rule:
pass tls any any -> any any (msg:"Allow connection to test.openquantumsafe.org/"; flow:to_server,established; tls.sni;content:"test.openquantumsafe.org"; nocase; isdataat:!1,relative; sid:1;)
rejectboth tls any any -> any any (msg:"Unauthorized TLS outbound connection in any"; flow:to_server,established; sid:2;)
wondering, do we need to update the ruleset to different format or we need to make some changes to suricata.yaml ?
Updated by Ramesh Reddy 3 months ago
@
Brinn Joyce wrote in #note-12:
We ran into the same issue and resolved it by adding `ssl_state:client_hello` to our reject/drop rule - alternatively it could be added to the pass rules.
Thanks Brinn, it worked only when we added to the reject rule.
Updated by Victor Julien 3 months ago
In 8.0 you can use the new rule hook support to make sure the rules are evaluated at the same hook:
pass tls:client_hello_done any any -> any any (tls.sni; content:"www.google.com"; sid:21; alert;) drop tls:client_hello_done any any -> any any (sid:22;)
This avoids the issue that the traffic triggers the drop/reject rule before the client hello is complete.