diff options
Diffstat (limited to 'doc/userguide/lua')
-rw-r--r-- | doc/userguide/lua/index.rst | 7 | ||||
-rw-r--r-- | doc/userguide/lua/lua-functions.rst | 1114 | ||||
-rw-r--r-- | doc/userguide/lua/lua-usage.rst | 20 |
3 files changed, 1141 insertions, 0 deletions
diff --git a/doc/userguide/lua/index.rst b/doc/userguide/lua/index.rst new file mode 100644 index 0000000..d6bbf70 --- /dev/null +++ b/doc/userguide/lua/index.rst @@ -0,0 +1,7 @@ +Lua support +=========== + +.. toctree:: + + lua-usage + lua-functions diff --git a/doc/userguide/lua/lua-functions.rst b/doc/userguide/lua/lua-functions.rst new file mode 100644 index 0000000..92473d5 --- /dev/null +++ b/doc/userguide/lua/lua-functions.rst @@ -0,0 +1,1114 @@ +.. _lua-functions: + +Lua functions +============= + +Differences between `output` and `detect`: +------------------------------------------ + +Currently, the ``needs`` key initialization varies, depending on what is the goal of the script: output or detection. + +If the script is for detection, the ``needs`` initialization should be as seen in the example below (see :ref:`lua-detection` for a complete example of a detection script): + +:: + + function init (args) + local needs = {} + needs["packet"] = tostring(true) + return needs + end + +For output logs, follow the pattern below. (The complete script structure can be seen at :ref:`lua-output`:) + +:: + + function init (args) + local needs = {} + needs["protocol"] = "http" + return needs + end + + +Do notice that the functions and protocols available for ``log`` and ``match`` may also vary. DNP3, for instance, is not +available for logging. + +packet +------ + +Initialize with: + +:: + + function init (args) + local needs = {} + needs["type"] = "packet" + return needs + end + +SCPacketTimestamp +~~~~~~~~~~~~~~~~~ + +Get packets timestamp as 2 numbers: seconds & microseconds elapsed since +1970-01-01 00:00:00 UTC. + +:: + + function log(args) + local sec, usec = SCPacketTimestamp() + end + +SCPacketTimeString +~~~~~~~~~~~~~~~~~~ + +Use ``SCPacketTimeString`` to get the packet's time string in the format: +11/24/2009-18:57:25.179869 + +:: + + function log(args) + ts = SCPacketTimeString() + +SCPacketTuple +~~~~~~~~~~~~~ + +:: + + ipver, srcip, dstip, proto, sp, dp = SCPacketTuple() + +SCPacketPayload +~~~~~~~~~~~~~~~ + +:: + + p = SCPacketPayload() + +flow +---- + +:: + + function init (args) + local needs = {} + needs["type"] = "flow" + return needs + end + +SCFlowTimestamps +~~~~~~~~~~~~~~~~ + +Get timestamps (seconds and microseconds) of the first and the last packet from +the flow. + +:: + + startts, lastts = SCFlowTimestamps() + startts_s, lastts_s, startts_us, lastts_us = SCFlowTimestamps() + +SCFlowTimeString +~~~~~~~~~~~~~~~~ + +:: + + startts = SCFlowTimeString() + +SCFlowTuple +~~~~~~~~~~~ + +:: + + ipver, srcip, dstip, proto, sp, dp = SCFlowTuple() + +SCFlowAppLayerProto +~~~~~~~~~~~~~~~~~~~ + +Get alproto as a string from the flow. If a alproto is not (yet) known, it +returns "unknown". + +Example: + +:: + + function log(args) + alproto = SCFlowAppLayerProto() + if alproto ~= nil then + print (alproto) + end + end + +Returns 5 values: <alproto> <alproto_ts> <alproto_tc> <alproto_orig> <alproto_expect> + +Orig and expect are used when changing and upgrading protocols. In a SMTP STARTTLS +case, orig would normally be set to "smtp" and expect to "tls". + + +SCFlowHasAlerts +~~~~~~~~~~~~~~~ + +Returns true if flow has alerts. + +Example: + +:: + + function log(args) + has_alerts = SCFlowHasAlerts() + if has_alerts then + -- do something + end + end + +SCFlowStats +~~~~~~~~~~~ + +Gets the packet and byte counts per flow. + +:: + + tscnt, tsbytes, tccnt, tcbytes = SCFlowStats() + +SCFlowId +~~~~~~~~ + +Gets the flow id. + +:: + + id = SCFlowId() + +Note that simply printing 'id' will likely result in printing a scientific +notation. To avoid that, simply do: + +:: + + id = SCFlowId() + idstr = string.format("%.0f",id) + print ("Flow ID: " .. idstr .. "\n") + + +http +---- + +For output, init with: + +:: + + function init (args) + local needs = {} + needs["protocol"] = "http" + return needs + end + +For detection, use the specific buffer (cf :ref:`lua-detection` for a complete list), as with: + +:: + + function init (args) + local needs = {} + needs["http.uri"] = tostring(true) + return needs + end + +HttpGetRequestBody and HttpGetResponseBody. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Make normalized body data available to the script through +HttpGetRequestBody and HttpGetResponseBody. + +There no guarantees that all of the body will be available. + +Example: + +:: + + function log(args) + a, o, e = HttpGetResponseBody(); + --print("offset " .. o .. " end " .. e) + for n, v in ipairs(a) do + print(v) + end + end + +HttpGetRequestHost +~~~~~~~~~~~~~~~~~~ + +Get the host from libhtp's tx->request_hostname, which can either be +the host portion of the url or the host portion of the Host header. + +Example: + +:: + + http_host = HttpGetRequestHost() + if http_host == nil then + http_host = "<hostname unknown>" + end + +HttpGetRequestHeader +~~~~~~~~~~~~~~~~~~~~ + +:: + + http_ua = HttpGetRequestHeader("User-Agent") + if http_ua == nil then + http_ua = "<useragent unknown>" + end + +HttpGetResponseHeader +~~~~~~~~~~~~~~~~~~~~~ + +:: + + server = HttpGetResponseHeader("Server"); + print ("Server: " .. server); + +HttpGetRequestLine +~~~~~~~~~~~~~~~~~~ + +:: + + rl = HttpGetRequestLine(); + print ("Request Line: " .. rl); + +HttpGetResponseLine +~~~~~~~~~~~~~~~~~~~ + +:: + + rsl = HttpGetResponseLine(); + print ("Response Line: " .. rsl); + +HttpGetRawRequestHeaders +~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + rh = HttpGetRawRequestHeaders(); + print ("Raw Request Headers: " .. rh); + +HttpGetRawResponseHeaders +~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + rh = HttpGetRawResponseHeaders(); + print ("Raw Response Headers: " .. rh); + +HttpGetRequestUriRaw +~~~~~~~~~~~~~~~~~~~~ + +:: + + http_uri = HttpGetRequestUriRaw() + if http_uri == nil then + http_uri = "<unknown>" + end + +HttpGetRequestUriNormalized +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + http_uri = HttpGetRequestUriNormalized() + if http_uri == nil then + http_uri = "<unknown>" + end + +HttpGetRequestHeaders +~~~~~~~~~~~~~~~~~~~~~ + +:: + + a = HttpGetRequestHeaders(); + for n, v in pairs(a) do + print(n,v) + end + +HttpGetResponseHeaders +~~~~~~~~~~~~~~~~~~~~~~ + +:: + + a = HttpGetResponseHeaders(); + for n, v in pairs(a) do + print(n,v) + end + +DNS +--- + +If your purpose is to create a logging script, initialize the buffer as: + +:: + + function init (args) + local needs = {} + needs["protocol"] = "dns" + return needs + end + +If you are going to use the script for rule matching, choose one of the available DNS buffers listed in +:ref:`lua-detection` and follow the pattern: + +:: + + function init (args) + local needs = {} + needs["dns.rrname"] = tostring(true) + return needs + end + +DnsGetQueries +~~~~~~~~~~~~~ + +:: + + dns_query = DnsGetQueries(); + if dns_query ~= nil then + for n, t in pairs(dns_query) do + rrname = t["rrname"] + rrtype = t["type"] + + print ("QUERY: " .. ts .. " " .. rrname .. " [**] " .. rrtype .. " [**] " .. + "TODO" .. " [**] " .. srcip .. ":" .. sp .. " -> " .. + dstip .. ":" .. dp) + end + end + +returns a table of tables + +DnsGetAnswers +~~~~~~~~~~~~~ + +:: + + dns_answers = DnsGetAnswers(); + if dns_answers ~= nil then + for n, t in pairs(dns_answers) do + rrname = t["rrname"] + rrtype = t["type"] + ttl = t["ttl"] + + print ("ANSWER: " .. ts .. " " .. rrname .. " [**] " .. rrtype .. " [**] " .. + ttl .. " [**] " .. srcip .. ":" .. sp .. " -> " .. + dstip .. ":" .. dp) + end + end + +returns a table of tables + +DnsGetAuthorities +~~~~~~~~~~~~~~~~~ + +:: + + dns_auth = DnsGetAuthorities(); + if dns_auth ~= nil then + for n, t in pairs(dns_auth) do + rrname = t["rrname"] + rrtype = t["type"] + ttl = t["ttl"] + + print ("AUTHORITY: " .. ts .. " " .. rrname .. " [**] " .. rrtype .. " [**] " .. + ttl .. " [**] " .. srcip .. ":" .. sp .. " -> " .. + dstip .. ":" .. dp) + end + end + +returns a table of tables + +DnsGetRcode +~~~~~~~~~~~ + +:: + + rcode = DnsGetRcode(); + if rcode == nil then + return 0 + end + print (rcode) + +returns a lua string with the error message, or nil + +DnsGetRecursionDesired +~~~~~~~~~~~~~~~~~~~~~~ + +:: + + if DnsGetRecursionDesired() == true then + print ("RECURSION DESIRED") + end + +returns a bool + +TLS +--- + +For log output, initialize with: + +:: + + function init (args) + local needs = {} + needs["protocol"] = "tls" + return needs + end + +For detection, initialization is as follows: + +:: + + function init (args) + local needs = {} + needs["tls"] = tostring(true) + return needs + end + +TlsGetVersion +~~~~~~~~~~~~~ + +Get the negotiated version in a TLS session as a string through TlsGetVersion. + +Example: + +:: + + function log (args) + version = TlsGetVersion() + if version then + -- do something + end + end + +TlsGetCertInfo +~~~~~~~~~~~~~~ + +Make certificate information available to the script through TlsGetCertInfo. + +Example: + +:: + + function log (args) + version, subject, issuer, fingerprint = TlsGetCertInfo() + if version == nil then + return 0 + end + end + +TlsGetCertChain +~~~~~~~~~~~~~~~ + +Make certificate chain available to the script through TlsGetCertChain. + +The output is an array of certificate with each certificate being an hash +with `data` and `length` keys. + +Example: + +:: + + -- Use debian lua-luaossl coming from https://github.com/wahern/luaossl + local x509 = require"openssl.x509" + + chain = TlsGetCertChain() + for k, v in pairs(chain) do + -- v.length is length of data + -- v.data is raw binary data of certificate + cert = x509.new(v["data"], "DER") + print(cert:text() .. "\n") + end + + +TlsGetCertNotAfter +~~~~~~~~~~~~~~~~~~ + +Get the Unix timestamp of end of validity of certificate. + +Example: + +:: + + function log (args) + notafter = TlsGetCertNotAfter() + if notafter < os.time() then + -- expired certificate + end + end + +TlsGetCertNotBefore +~~~~~~~~~~~~~~~~~~~ + +Get the Unix timestamp of beginning of validity of certificate. + +Example: + +:: + + function log (args) + notbefore = TlsGetCertNotBefore() + if notbefore > os.time() then + -- not yet valid certificate + end + end + +TlsGetCertSerial +~~~~~~~~~~~~~~~~ + +Get TLS certificate serial number through TlsGetCertSerial. + +Example: + +:: + + function log (args) + serial = TlsGetCertSerial() + if serial then + -- do something + end + end + +TlsGetSNI +~~~~~~~~~ + +Get the Server name Indication from a TLS connection. + +Example: + +:: + + function log (args) + asked_domain = TlsGetSNI() + if string.find(asked_domain, "badguys") then + -- ok connection to bad guys let's do something + end + end + + +JA3 +--- + +JA3 must be enabled in the Suricata config file (set 'app-layer.protocols.tls.ja3-fingerprints' to 'yes'). + +For log output, initialize with: + +:: + + function init (args) + local needs = {} + needs["protocol"] = "tls" + return needs + end + +For detection, initialization is as follows: + +:: + + function init (args) + local needs = {} + needs["tls"] = tostring(true) + return needs + end + +Ja3GetHash +~~~~~~~~~~ + +Get the JA3 hash (md5sum of JA3 string) through Ja3GetHash. + +Example: + +:: + + function log (args) + hash = Ja3GetHash() + if hash == nil then + return + end + end + +Ja3GetString +~~~~~~~~~~~~ + +Get the JA3 string through Ja3GetString. + +Example: + +:: + + function log (args) + str = Ja3GetString() + if str == nil then + return + end + end + +Ja3SGetHash +~~~~~~~~~~~ + +Get the JA3S hash (md5sum of JA3S string) through JA3SGetHash. + +Examples: + +:: + + function log (args) + hash = Ja3SGetHash() + if hash == nil then + return + end + end + +Or, for detection: + +:: + + function match (args) + hash = Ja3SGetHash() + if hash == nil then + return 0 + end + + // matching code + + return 0 + end + +JA3SGetString +~~~~~~~~~~~~~ + +Get the JA3S string through Ja3SGetString. + +Examples: + +:: + + function log (args) + str = Ja3SGetString() + if str == nil then + return + end + end + +Or, for detection: + +:: + + function match (args) + str = Ja3SGetString() + if str == nil then + return 0 + end + + // matching code + + return 0 + end + +SSH +--- + +Initialize with: + +:: + + function init (args) + local needs = {} + needs["protocol"] = "ssh" + return needs + end + +SshGetServerProtoVersion +~~~~~~~~~~~~~~~~~~~~~~~~ + +Get SSH protocol version used by the server through SshGetServerProtoVersion. + +Example: + +:: + + function log (args) + version = SshGetServerProtoVersion() + if version == nil then + return 0 + end + end + +SshGetServerSoftwareVersion +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Get SSH software used by the server through SshGetServerSoftwareVersion. + +Example: + +:: + + function log (args) + software = SshGetServerSoftwareVersion() + if software == nil then + return 0 + end + end + +SshGetClientProtoVersion +~~~~~~~~~~~~~~~~~~~~~~~~ + +Get SSH protocol version used by the client through SshGetClientProtoVersion. + +Example: + +:: + + function log (args) + version = SshGetClientProtoVersion() + if version == nil then + return 0 + end + end + +SshGetClientSoftwareVersion +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Get SSH software used by the client through SshGetClientSoftwareVersion. + +Example: + +:: + + function log (args) + software = SshGetClientSoftwareVersion() + if software == nil then + return 0 + end + end + + +HasshGet +~~~~~~~~ + +Get MD5 of hassh algorithms used by the client through HasshGet. + +Example: + +:: + + function log (args) + hassh = HasshGet() + if hassh == nil then + return 0 + end + end + +HasshGetString +~~~~~~~~~~~~~~ + +Get hassh algorithms used by the client through HasshGetString. + +Example: + +:: + + function log (args) + hassh_string = HasshGetString() + if hassh == nil then + return 0 + end + end + +HasshServerGet +~~~~~~~~~~~~~~ + +Get MD5 of hassh algorithms used by the server through HasshServerGet. + +Example: + +:: + + function log (args) + hassh_string = HasshServerGet() + if hassh == nil then + return 0 + end + end + +HasshServerGetString +~~~~~~~~~~~~~~~~~~~~ + +Get hassh algorithms used by the server through HasshServerGetString. + +Example: + +:: + + function log (args) + hassh_string = HasshServerGetString() + if hassh == nil then + return 0 + end + end + + +Files +----- + +To use the file logging API, the script's init() function needs to look like: + +:: + + function init (args) + local needs = {} + needs['type'] = 'file' + return needs + end + +SCFileInfo +~~~~~~~~~~ + +:: + + + fileid, txid, name, size, magic, md5, sha1, sha256 = SCFileInfo() + +returns fileid (number), txid (number), name (string), size (number), +magic (string), md5 in hex (string), sha1 (string), sha256 (string) + +SCFileState +~~~~~~~~~~~ + +:: + + state, stored = SCFileState() + +returns state (string), stored (bool) + +Alerts +------ + +Alerts are a subset of the 'packet' logger: + +:: + + function init (args) + local needs = {} + needs["type"] = "packet" + needs["filter"] = "alerts" + return needs + end + +SCRuleIds +~~~~~~~~~ + +:: + + sid, rev, gid = SCRuleIds() + +SCRuleAction +~~~~~~~~~~~~ + +:: + + action = SCRuleAction() + +returns one of 'pass', 'reject', 'drop' or 'alert' + +SCRuleMsg +~~~~~~~~~ + +:: + + msg = SCRuleMsg() + +SCRuleClass +~~~~~~~~~~~ + +:: + + + class, prio = SCRuleClass() + +Streaming Data +-------------- + +Streaming data can currently log out reassembled TCP data and +normalized HTTP data. The script will be invoked for each consecutive +data chunk. + +In case of TCP reassembled data, all possible overlaps are removed +according to the host OS settings. + +:: + + function init (args) + local needs = {} + needs["type"] = "streaming" + needs["filter"] = "tcp" + return needs + end + +In case of HTTP body data, the bodies are unzipped and dechunked if applicable. + +:: + + function init (args) + local needs = {} + needs["type"] = "streaming" + needs["protocol"] = "http" + return needs + end + +SCStreamingBuffer +~~~~~~~~~~~~~~~~~ + +:: + + function log(args) + -- sb_ts and sb_tc are bools indicating the direction of the data + data, sb_open, sb_close, sb_ts, sb_tc = SCStreamingBuffer() + if sb_ts then + print("->") + else + print("<-") + end + hex_dump(data) + end + +Flow variables +-------------- + +It is possible to access, define and modify Flow variables from Lua. To do so, +you must use the functions described in this section and declare the counter in +init function: + +:: + + function init(args) + local needs = {} + needs["tls"] tostring(true) + needs["flowint"] = {"tls-cnt"} + return needs + end + +Here we define a `tls-cnt` Flowint that can now be used in output or in a +signature via dedicated functions. The access to the Flow variable is done by +index so in our case we need to use 0. + +:: + + function match(args) + a = SCFlowintGet(0); + if a then + SCFlowintSet(0, a + 1) + else + SCFlowintSet(0, 1) + end + +SCFlowintGet +~~~~~~~~~~~~ + +Get the Flowint at index given by the parameter. + +SCFlowintSet +~~~~~~~~~~~~ + +Set the Flowint at index given by the first parameter. The second parameter is the value. + +SCFlowintIncr +~~~~~~~~~~~~~ + +Increment Flowint at index given by the first parameter. + +SCFlowintDecr +~~~~~~~~~~~~~ + +Decrement Flowint at index given by the first parameter. + +SCFlowvarGet +~~~~~~~~~~~~ + +Get the Flowvar at index given by the parameter. + +SCFlowvarSet +~~~~~~~~~~~~ + +Set a Flowvar. First parameter is the index, second is the data +and third is the length of data. + +You can use it to set string + +:: + + function init (args) + local needs = {} + needs["http.request_headers"] = tostring(true) + needs["flowvar"] = {"cnt"} + return needs + end + + function match(args) + a = SCFlowvarGet(0); + if a then + a = tostring(tonumber(a)+1) + SCFlowvarSet(0, a, #a) + else + a = tostring(1) + SCFlowvarSet(0, a, #a) + end + +Misc +---- + +SCThreadInfo +~~~~~~~~~~~~ + +:: + + tid, tname, tgroup = SCThreadInfo() + +It gives: tid (integer), tname (string), tgroup (string) + +SCLogError, SCLogWarning, SCLogNotice, SCLogInfo, SCLogDebug +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Print a message. It will go into the outputs defined in the +yaml. Whether it will be printed depends on the log level. + +Example: + +:: + + SCLogError("some error message") + +SCLogPath +~~~~~~~~~ + +Expose the log path. + +:: + + + name = "fast_lua.log" + function setup (args) + filename = SCLogPath() .. "/" .. name + file = assert(io.open(filename, "a")) + end + +SCByteVarGet +~~~~~~~~~~~~ + +Get the ByteVar at index given by the parameter. These variables are defined by +`byte_extract` or `byte_math` in Suricata rules. Only callable from match scripts. + +:: + + function init(args) + local needs = {} + needs["bytevar"] = {"var1", "var2"} + return needs + end + +Here we define a register that we will be using variables `var1` and `var2`. +The access to the Byte variables is done by index. + +:: + + function match(args) + var1 = SCByteVarGet(0) + var2 = SCByteVarGet(1) diff --git a/doc/userguide/lua/lua-usage.rst b/doc/userguide/lua/lua-usage.rst new file mode 100644 index 0000000..19946db --- /dev/null +++ b/doc/userguide/lua/lua-usage.rst @@ -0,0 +1,20 @@ +Lua usage in Suricata +===================== + +Lua scripting can be used in two components of Suricata. The first is in +output and the second one in rules in the detection engine. + +Both features are using a list of functions to access the data extracted by +Suricata. You can get the list of functions in the :ref:`lua-functions` page. + +.. note:: Currently, there is a difference in the ``needs`` key in the ``init`` function, depending on what is the usage: ``output`` or ``detection``. The list of available functions may also differ. + +Lua output +---------- + +Lua can be used to write arbitrary output. See :ref:`lua-output` for more information. + +Lua detection +------------- + +Lua script can be used as a filter condition in signatures. See :ref:`lua-detection` for more information. |