You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/doc/userguide/lua/lua-functions.rst

508 lines
9.1 KiB
ReStructuredText

.. _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.
The Lua script for the ``luaxform`` transform **does not use ``needs``**.
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
flow
----
::
function init (args)
local needs = {}
needs["type"] = "flow"
return needs
end
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
~~~~~~~~~~~~~~~~~~
http: Use libhtp-rs. Ticket: #2696 There are a lot of changes here, which are described below. In general these changes are renaming constants to conform to the libhtp-rs versions (which are generated by cbindgen); making all htp types opaque and changing struct->member references to htp_struct_member() function calls; and a handful of changes to offload functionality onto libhtp-rs from suricata, such as URI normalization and transaction cleanup. Functions introduced to handle opaque htp_tx_t: - tx->parsed_uri => htp_tx_parsed_uri(tx) - tx->parsed_uri->path => htp_uri_path(htp_tx_parsed_uri(tx) - tx->parsed_uri->hostname => htp_uri_hostname(htp_tx_parsed_uri(tx)) - htp_tx_get_user_data() => htp_tx_user_data(tx) - htp_tx_is_http_2_upgrade(tx) convenience function introduced to detect response status 101 and “Upgrade: h2c" header. Functions introduced to handle opaque htp_tx_data_t: - d->len => htp_tx_data_len() - d->data => htp_tx_data_data() - htp_tx_data_tx(data) function to get the htp_tx_t from the htp_tx_data_t - htp_tx_data_is_empty(data) convenience function introduced to test if the data is empty. Other changes: Build libhtp-rs as a crate inside rust. Update autoconf to no longer use libhtp as an external dependency. Remove HAVE_HTP feature defines since they are no longer needed. Make function arguments and return values const where possible htp_tx_destroy(tx) will now free an incomplete transaction htp_time_t replaced with standard struct timeval Callbacks from libhtp now provide the htp_connp_t and the htp_tx_data_t as separate arguments. This means the connection parser is no longer fetched from the transaction inside callbacks. SCHTPGenerateNormalizedUri() functionality moved inside libhtp-rs, which now provides normalized URI values. The normalized URI is available with accessor function: htp_tx_normalized_uri() Configuration settings added to control the behaviour of the URI normalization: - htp_config_set_normalized_uri_include_all() - htp_config_set_plusspace_decode() - htp_config_set_convert_lowercase() - htp_config_set_double_decode_normalized_query() - htp_config_set_double_decode_normalized_path() - htp_config_set_backslash_convert_slashes() - htp_config_set_bestfit_replacement_byte() - htp_config_set_convert_lowercase() - htp_config_set_nul_encoded_terminates() - htp_config_set_nul_raw_terminates() - htp_config_set_path_separators_compress() - htp_config_set_path_separators_decode() - htp_config_set_u_encoding_decode() - htp_config_set_url_encoding_invalid_handling() - htp_config_set_utf8_convert_bestfit() - htp_config_set_normalized_uri_include_all() - htp_config_set_plusspace_decode() Constants related to configuring uri normalization: - HTP_URL_DECODE_PRESERVE_PERCENT => HTP_URL_ENCODING_HANDLING_PRESERVE_PERCENT - HTP_URL_DECODE_REMOVE_PERCENT => HTP_URL_ENCODING_HANDLING_REMOVE_PERCENT - HTP_URL_DECODE_PROCESS_INVALID => HTP_URL_ENCODING_HANDLING_PROCESS_INVALID htp_config_set_field_limits(soft_limit, hard_limit) changed to htp_config_set_field_limit(limit) because libhtp didn't implement soft limits. libhtp logging API updated to provide HTP_LOG_CODE constants along with the message. This eliminates the need to perform string matching on message text to map log messages to HTTP_DECODER_EVENT values, and the HTP_LOG_CODE values can be used directly. In support of this, HTP_DECODER_EVENT values are mapped to their corresponding HTP_LOG_CODE values. New log events to describe additional anomalies: HTP_LOG_CODE_REQUEST_TOO_MANY_LZMA_LAYERS HTP_LOG_CODE_RESPONSE_TOO_MANY_LZMA_LAYERS HTP_LOG_CODE_PROTOCOL_CONTAINS_EXTRA_DATA HTP_LOG_CODE_CONTENT_LENGTH_EXTRA_DATA_START HTP_LOG_CODE_CONTENT_LENGTH_EXTRA_DATA_END HTP_LOG_CODE_SWITCHING_PROTO_WITH_CONTENT_LENGTH HTP_LOG_CODE_DEFORMED_EOL HTP_LOG_CODE_PARSER_STATE_ERROR HTP_LOG_CODE_MISSING_OUTBOUND_TRANSACTION_DATA HTP_LOG_CODE_MISSING_INBOUND_TRANSACTION_DATA HTP_LOG_CODE_ZERO_LENGTH_DATA_CHUNKS HTP_LOG_CODE_REQUEST_LINE_UNKNOWN_METHOD HTP_LOG_CODE_REQUEST_LINE_UNKNOWN_METHOD_NO_PROTOCOL HTP_LOG_CODE_REQUEST_LINE_UNKNOWN_METHOD_INVALID_PROTOCOL HTP_LOG_CODE_REQUEST_LINE_NO_PROTOCOL HTP_LOG_CODE_RESPONSE_LINE_INVALID_PROTOCOL HTP_LOG_CODE_RESPONSE_LINE_INVALID_RESPONSE_STATUS HTP_LOG_CODE_RESPONSE_BODY_INTERNAL_ERROR HTP_LOG_CODE_REQUEST_BODY_DATA_CALLBACK_ERROR HTP_LOG_CODE_RESPONSE_INVALID_EMPTY_NAME HTP_LOG_CODE_REQUEST_INVALID_EMPTY_NAME HTP_LOG_CODE_RESPONSE_INVALID_LWS_AFTER_NAME HTP_LOG_CODE_RESPONSE_HEADER_NAME_NOT_TOKEN HTP_LOG_CODE_REQUEST_INVALID_LWS_AFTER_NAME HTP_LOG_CODE_LZMA_DECOMPRESSION_DISABLED HTP_LOG_CODE_CONNECTION_ALREADY_OPEN HTP_LOG_CODE_COMPRESSION_BOMB_DOUBLE_LZMA HTP_LOG_CODE_INVALID_CONTENT_ENCODING HTP_LOG_CODE_INVALID_GAP HTP_LOG_CODE_ERROR The new htp_log API supports consuming log messages more easily than walking a list and tracking the current offset. Internally, libhtp-rs now provides log messages as a queue of htp_log_t, which means the application can simply call htp_conn_next_log() to fetch the next log message until the queue is empty. Once the application is done with a log message, they can call htp_log_free() to dispose of it. Functions supporting htp_log_t: htp_conn_next_log(conn) - Get the next log message htp_log_message(log) - To get the text of the message htp_log_code(log) - To get the HTP_LOG_CODE value htp_log_free(log) - To free the htp_log_t
2 years ago
Get the host from libhtp's htp_tx_request_hostname(tx), 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
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
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.
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)