Hello again,
I was searching for Suricata rules and scripts that could enable detection of TLS or HTTPS traffic that use tls certificates signed by CA Root certificates that are not publicly trusted.
Why I found this important?
Being able to inspect or prevent any tls traffic that is against publicly trusted CAs or a predefined policy using whitelist of CAs can have many benefits. Traffic going outside of local network using untrusted cert is less likely to be safe and valid.
Web browsers and other apps regulary checks certificate validity, but in contrast to that, RAT trojans, ransomware and other types of malware connecting to C&C servers will most likely use encrypted TLS connections. They will probably use their own certificates and less likely obtain regular ones. Traffic could be also matched with predefined valid CAs to detect irregularities against some policies.
Decryption of TLS traffic using PolarProxy or SSLstrip may be of help to detection but is not always a good solution due to certificate pinning and packet routing issues. Besides of JA3 hashes, I believe that it could be a good approach to consider implementing certificate check function. Also, all NGFWs have this function already built in.
What I tried?
Unfortunetly there was no luck for me, at least for now. There are scripts on GitHub that use TlsGetCertInfo() function to compare Subject field of certificate with known CA names but this is not reliable because common names can easily be forged. That is why I need a method to compare Root CA of tls connection certificate with predefined CA list. So, in short, we need to:
- Retrieve Root CA certificate of tls certificate and convert to PEM format
- Retrieve list of publicly known CAs (from file, functions or libraries)
- Compare the certificates
- Return “1” if there is a match or “0” if no match
I found TlsGetCertChain() function in documentation that may be of help, I didn’t find any examples except one in documentation. There is a conteptual lua script file that may be working that loads the txt file of whitelisted CAs in headerless Base64 format, loads TLS package in init function, loads last certificate in chain, converts it to PEM and compares the certificate against the CA store.
However, I wanted to check with you if you had any ideas or sugestions if there are some built-in functions, scripts or predefined rules that can achieve this goal. Any feedback is welcome!
Also, for Suricata team, please consider possibility of implementing this feature as native in a future as it will be good mechanism for preventing malicious traffic from going out.
Please have in mind that this is just conceptual script and that it is not tested. I don’t know what performance impacts this may have.
package.path = package.path .. ";/usr/share/lua/5.4/?.lua;/usr/share/lua/5.4/?/init.lua"
package.cpath = package.cpath .. ";/usr/lib64/lua/5.4/?.so"
local x509 = require "openssl.x509"
local function load_predefined_cas(file_path)
local cas = {}
local file = io.open(file_path, "r")
if not file then
error("Could not open file: " .. file_path)
end
for line in file:lines() do
local trimmed_line = line:gsub("%s+", "")
if #trimmed_line > 0 then
table.insert(cas, trimmed_line)
end
end
file:close()
return cas
end
local predefined_cas = load_predefined_cas("/etc/suricata/lua-output/trusted_certs.crt")
function init(args)
local needs = {}
needs["tls"] = tostring(true)
return needs
end
function match(args)
local chain = TlsGetCertChain()
if chain then
local root_ca = chain[#chain]
if root_ca and root_ca.data then
local cert = x509.new(root_ca.data, "DER")
local root_ca_base64 = cert:toPEM()
root_ca_base64 = root_ca_base64:gsub("\n", "")
for _, ca in ipairs(predefined_cas) do
if root_ca_base64 == ca then
return 1
end
end
end
end
return 0
end