Rule to detect TLS connection with no SNI extension in Client Hello message

I need a rule to detect when a TLS connection is made that has no SNI extension in the Client Hello message of the TLS handshake.

Does anyone know how to write a rule like that?

I looked at the TLS section of the Suricata documentation and browsed through many example rules but did not see a way to do this.

Thanks

If there is no SNI, the basic method is to detect the CN of the server’s certification. However, it is not easy to detect encryption or obfuscation of certification used in TLS 1.3.

  • Even if there is no encryption or obfuscation of the certificate, be careful as some applications may pretend to be the certificate of another server to evade detection.

If there is no string pattern, detection should be done using features such as TCP’s cumulative sequence (stream_size), dsize, or ja3 that can match application units.

Of course, it is usually detection for desktop applications that can use these conditions. For connections through web browsers, the stream_size match is difficult because the amount of data transferred fluctuates greatly, and ja3 values ​​are matched on a per-application basis. Therefore, in the case of a web browser, it is difficult to detect without decryption.

In the actual case, it is necessary to check whether there are additional elements through analysis on a cap basis.

Hi. There is no way to check that a buffer does not exists in Suricata AFAIK.
You would need to do raw byte matching.

I asked the same question abut TLS SNI on #suricata IRC channel a week or so ago, perhaps this is related? Anyways I found the solution to this issue and wrote the rule which seems to be working so far.
Here is the body of the rule:

flow: established,to_server;
content: "|16 03 01|";depth: 3;
content: "|01|";distance: 2;within: 1;
byte_jump: 1, 37, relative, big;
byte_jump: 2, 0, relative, big;
byte_jump: 1, 0, relative, big;
byte_extract: 2, 0, ext_len, relative, big;
content: !"|00 00|";distance: 0;within: 2;isdataat: 1, relative;
byte_jump: 2, 2, relative, big;
content: !"|00 00|";distance: 0;within: 2;isdataat: 1, relative;
byte_jump: 2, 2, relative, big;
content: !"|00 00|";distance: 0;within: 2;isdataat: 1, relative;
byte_jump: 2, 2, relative, big;
content: !"|00 00|";distance: 0;within: 2;isdataat: 1, relative;
byte_jump: 2, 2, relative, big;
content: !"|00 00|";distance: 0;within: 2;isdataat: 1, relative;
byte_jump: 2, 2, relative, big;
threshold: type limit, track by_src, seconds 10, count 1;

There are more details about this rule in blog post written about evading internet scanners.
Cheers!

That looks really interesting. Thanks for that. I’ll need some time to dig into it.

If a pcap for a specific case is attached, I think more analysis can be provided.