Stamus-Networks-Blog

Finding self signed TLS certificates - Suricata and Luajit scripting

Written by Peter Manev | Jul 24, 2015 3:01:48 PM

Introduction

This is a short tutorial of how you can find and store to disk a self signed TLS certificates with Suricata IDPS and Lua (luajit scripting).

What does self signed TLS certificate mean - the quick version  from Wikipedia here. In other words - "certificate that is signed by the same entity whose identity it certifies" or anyone can create and deploy such a certificate. This kind of events are signed of some poorly setup TLS servers and that's why it is good to keep an eye on such events in your network for the purpose of contingency monitoring - the very least.

TLS support in Suricata allows you to do match on TLS subject, TLS issuer DN and things like TLS fingerprint. For instance, one can do

alert tls any any -> any any (msg:"forged ssl google user";
tls.subject:"CN=*.googleusercontent.com";
tls.issuerdn:!"CN=Google-Internet-Authority"; sid:8; rev:1;)

to detect that the TLS issuer DN is not the one we were waiting for a given TLS subject. But there is no way to compare the two different fields. So how do we catch all those self signed certificates without knowing any details about them and/or any network/domain/port specifics either. And we want them all caught and stored to disk!

This case is one example where the Lua support in Suricata IDPS  shines - more than shines actually because it empowers you to do much more than chasing packet bytes with rule keywords and using PCREs inside a rule - and all those still deliver limited functionality in this particular scenario.

Lua and Suricata

Since version 2.0, Suricata has support for Lua scripting. The idea is to be able to decide if an alert is matching based on the return of a lua script. This script is taking some fields extracted by Suricata (the magic of it all) as parameters and return 1 in case of match and 0 if not. This lua scripting allows rules to implement complex logic that would be impossible with the standard rule language. For instance, Victor Julien was able to write a performant Heartbleed detection with lua scripting in the afternoon (the very same day) the problem/exploit was announced.

The syntax is the following, you prefilter with standard signature language, and you add a lua keyword with parameter being the script to run in case of partial match:

alert tls any any -> any any ( \
msg:"TLS HEARTBLEED malformed heartbeat record"; \
content:"|18 03|"; depth:2; lua:tls-heartbleed.lua; \
classtype:misc-attack; sid:3000001; rev:1;)

For more information on Suricata Lua scripting, please read how to write luascripts for Suricata.

For self signed certificate detection, you need to write a script - shall we say "self-signed-cert.lua" and save it in your /etc/suricata/rules directory. Then you can use it in a rule like so -

alert tls any any -> any any (msg:"SURICATA TLS Self Signed Certificate"; \
flow:established; luajit:self-signed-cert.lua; \
tls.store; classtype:protocol-command-decode; sid:999666111; rev:1;)

Now let us explain that in a bit more detail below.

At the time of this writing we are using this branch in particular - TLS Lua rebased . This is going to be merged to the Suricata git (latest dev) soon and later into beta, stable editions.

You need to make sure Suricata is compiled with Lua enabled:

# suricata --build-info
This is Suricata version 2.1dev (rev b5e1df2)
...
Prelude support:                         no
PCRE jit:                                yes
LUA support:                             yes
libluajit:                               yes
libgeoip:                                yes
...

In suricata.yaml make sure the tls section is enabled:

# a line based log of TLS handshake parameters (no alerts)
- tls-store:
enabled: yes  # Active TLS certificate store.
certs-log-dir: certs # directory to store the certificates files

We have the following rule file (self-sign.rules) content located in /etc/suricata/rules/ :

alert tls any any -> any any (msg:"SURICATA TLS Self Signed Certificate"; \
flow:established; luajit:self-signed-cert.lua; \
tls.store; classtype:protocol-command-decode; sid:999666111; rev:1;)

Make sure you add the rule to your rules files loaded in suricata.yaml and that you copy the associated lua script in the same directory. Here you can find the self-signed-cert.lua script.

Then you can start Suricata IDPS the usual way you always do.

The active part of the lua script is the following:

function match(args)
version, subject, issuer, fingerprint = TlsGetCertInfo();

if subject == issuer then
return 1
else
return 0
end
end

When suricata will see a TLS handshake (regardless of IP/port), it will run the Lua script. This one uses the fact that an equality between subject and issuer DN constitutes most of the self-signed certificates. When it finds such an equality it will return 1 and an alert will be generated.

This script is showing a really basic but useful code. However you can use all the power of Lua with the given info to do whatever you want/need.

The result

 

Extracted self signed SSL certificate

 

Some meta data about the certificate (in /var/log/suricata/certs/ you will find the .meta and pem  files):

TIME:              07/14/2015-14:45:16.757001
SRC IP:            10.0.2.15
DST IP:            192.168.1.180
PROTO:             6
SRC PORT:          49966
DST PORT:          443
TLS SUBJECT:       C=FR, ST=IDF, L=Paris, O=Stamus, CN=SELKS
TLS ISSUERDN:      C=FR, ST=IDF, L=Paris, O=Stamus, CN=SELKS
TLS FINGERPRINT:   80:e7:af:49:c3:fe:9a:73:78:29:6b:dd:fd:28:9e:d9:c9:15:3e:18

 

Further more from the alert info in JSON format (/var/log/suricata/eve.json log) we also have extra TLS info for the generated alert :

{"timestamp":"2015-07-14T14:45:18.076794+0200","flow_id":137451536,"in_iface":"eth0",
"event_type":"alert","src_ip":"192.168.1.180","src_port":443,"dest_ip":"10.0.2.15","dest_port":49966,"proto":"TCP","alert":
{"action":"allowed","gid":1,"signature_id":999666111,"rev":1,"signature":"SURICATA TLS Self Signed Certificate","category":"Generic Protocol Command
Decode","severity":3},"tls":{"subject":"C=FR, ST=IDF, L=Paris, O=Stamus, CN=SELKS","issuerdn":"C=FR, ST=IDF, L=Paris, O=Stamus,
CN=SELKS","fingerprint":"80:e7:af:49:c3:fe:9a:73:78:29:6b:dd:fd:28:9e:d9:c9:15:3e:18","version":"TLS 1.2"}}

To get the extra TLS info in the JSON alert you need to enable it in the suricata.yaml like so:

  # Extensible Event Format (nicknamed EVE) event log in JSON format
- eve-log:
enabled: yes
filetype: regular #regular|syslog|unix_dgram|unix_stream
filename: eve.json
types:
- alert:
#payload: yes # enable dumping payload in Base64
#payload-printable: yes # enable dumping payload in printable (lossy) format
#packet: yes # enable dumping of packet (without stream segments)
http: yes # enable dumping of http fields
tls: yes # enable dumping of tls fields <<---
ssh: yes # enable dumping of ssh fields

so you can get all the JSON data in Kibana/Elasticsearch as well.

Conclusion

If you would like to learn more about what can you do with Lua and Suricata IDPS, those links below will put you off to a good start: