diff --git a/doc/userguide/lua/libs/index.rst b/doc/userguide/lua/libs/index.rst index ba7e672360..b6555bdfa7 100644 --- a/doc/userguide/lua/libs/index.rst +++ b/doc/userguide/lua/libs/index.rst @@ -18,3 +18,4 @@ environment without access to additional modules. packetlib rule ssh + ja3 diff --git a/doc/userguide/lua/libs/ja3.rst b/doc/userguide/lua/libs/ja3.rst new file mode 100644 index 0000000000..e025cbddc6 --- /dev/null +++ b/doc/userguide/lua/libs/ja3.rst @@ -0,0 +1,90 @@ +JA3 +--- + +JA3 details are exposes to Lua scripts with the +``suricata.ja3`` library, For example:: + + local ja3 = require("suricata.ja3") + +If you want to use ja3, you can either set suricata.yaml option +``app-layer.protocols.tls.ja3-fingerprints`` to true, +or specify it in the ``init`` function of your lua script +by calling ``ja3.enable_ja3()``:: + + function init (args) + ja3.enable_ja3() + return {} + end + +``ja3.enable_ja3()`` will not enable ja3 if they are explicitly +disabled, so you should add ``requires: feature ja3;`` to your rule. + +For use in rule matching, the rule may **hook** into a TLS or QUIC +transaction state if you want to match on only one of these protocols. +Or you should use need ``ja3`` or ``ja3s`` in your init script:: + + function init (args) + ja3.enable_ja3() + local needs = {} + needs["ja3s"] = true + return needs + end + +Transaction +~~~~~~~~~~~ + +JA3 is transaction based, and the current transaction must be obtained before use:: + + local tx, err = ja3.get_tx() + if tx == err then + print(err) + end + +All other functions are methods on the transaction (either a QUIC or a TLS one). + +Transaction Methods +~~~~~~~~~~~~~~~~~~~ + +``ja3_get_hash()`` +^^^^^^^^^^^^^^^^^^ + +Get the ja3 value as a hash. + +Example:: + + local tx = ja3.get_tx() + local h = tx:ja3_get_hash(); + print (h) + +``ja3_get_string()`` +^^^^^^^^^^^^^^^^^^^^ + +Get the ja3 value as a string. + +Example:: + + local tx = ja3.get_tx() + local s = tx:ja3_get_string(); + print (s) + +``ja3s_get_hash()`` +^^^^^^^^^^^^^^^^^^^ + +Get the ja3s value as a hash. + +Example:: + + local tx = ja3.get_tx() + local h = tx:ja3s_get_hash(); + print (h) + +``ja3s_get_string()`` +^^^^^^^^^^^^^^^^^^^^^ + +Get the ja3s value as a string. + +Example:: + + local tx = ja3.get_tx() + local s = tx:ja3s_get_string(); + print (s) \ No newline at end of file diff --git a/doc/userguide/lua/lua-functions.rst b/doc/userguide/lua/lua-functions.rst index 9cecddf57c..f8c521937d 100644 --- a/doc/userguide/lua/lua-functions.rst +++ b/doc/userguide/lua/lua-functions.rst @@ -348,126 +348,6 @@ Example: 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 - Files ----- diff --git a/src/detect-lua-extensions.c b/src/detect-lua-extensions.c index c793828de8..c55511c52b 100644 --- a/src/detect-lua-extensions.c +++ b/src/detect-lua-extensions.c @@ -38,7 +38,6 @@ #include "util-lua.h" #include "util-lua-common.h" -#include "util-lua-ja3.h" #include "util-lua-tls.h" #include "util-lua-smtp.h" #include "util-lua-dnp3.h" @@ -121,7 +120,6 @@ int LuaRegisterExtensions(lua_State *lua_state) lua_setglobal(lua_state, "SCByteVarGet"); LuaRegisterFunctions(lua_state); - LuaRegisterJa3Functions(lua_state); LuaRegisterTlsFunctions(lua_state); LuaRegisterSmtpFunctions(lua_state); return 0; diff --git a/src/detect-lua.c b/src/detect-lua.c index 11c9fad7cf..b86a84de95 100644 --- a/src/detect-lua.c +++ b/src/detect-lua.c @@ -49,6 +49,7 @@ #include "app-layer.h" #include "app-layer-parser.h" #include "app-layer-htp.h" +#include "app-layer-ssl.h" #include "stream-tcp.h" @@ -72,6 +73,8 @@ static int DetectLuaSetup (DetectEngineCtx *, Signature *, const char *); static void DetectLuaRegisterTests(void); #endif static void DetectLuaFree(DetectEngineCtx *, void *); +static int g_lua_ja3_list_id = 0; +static int g_lua_ja3s_list_id = 0; /** * \brief Registration function for keyword: lua @@ -89,6 +92,18 @@ void DetectLuaRegister(void) sigmatch_table[DETECT_LUA].RegisterTests = DetectLuaRegisterTests; #endif + g_lua_ja3_list_id = DetectBufferTypeRegister("ja3.lua"); + DetectAppLayerInspectEngineRegister("ja3.lua", ALPROTO_TLS, SIG_FLAG_TOSERVER, + TLS_STATE_CLIENT_HELLO_DONE, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister( + "ja3.lua", ALPROTO_QUIC, SIG_FLAG_TOSERVER, 1, DetectEngineInspectGenericList, NULL); + + g_lua_ja3s_list_id = DetectBufferTypeRegister("ja3s.lua"); + DetectAppLayerInspectEngineRegister("ja3s.lua", ALPROTO_TLS, SIG_FLAG_TOCLIENT, + TLS_STATE_SERVER_HELLO_DONE, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister( + "ja3s.lua", ALPROTO_QUIC, SIG_FLAG_TOCLIENT, 1, DetectEngineInspectGenericList, NULL); + SCLogDebug("registering lua rule option"); } @@ -96,6 +111,8 @@ void DetectLuaRegister(void) #define FLAG_DATATYPE_PACKET BIT_U32(0) #define FLAG_DATATYPE_PAYLOAD BIT_U32(1) #define FLAG_DATATYPE_STREAM BIT_U32(2) +#define FLAG_LIST_JA3 BIT_U32(3) +#define FLAG_LIST_JA3S BIT_U32(4) #define FLAG_DATATYPE_BUFFER BIT_U32(22) #define FLAG_ERROR_LOGGED BIT_U32(23) #define FLAG_BLOCKED_FUNCTION_LOGGED BIT_U32(24) @@ -651,7 +668,11 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld, const continue; } - if (strcmp(k, "packet") == 0) { + if (strcmp(k, "ja3") == 0) { + ld->flags |= FLAG_LIST_JA3; + } else if (strcmp(k, "ja3s") == 0) { + ld->flags |= FLAG_LIST_JA3S; + } else if (strcmp(k, "packet") == 0) { ld->flags |= FLAG_DATATYPE_PACKET; } else if (strcmp(k, "payload") == 0) { ld->flags |= FLAG_DATATYPE_PAYLOAD; @@ -758,6 +779,13 @@ static int DetectLuaSetup (DetectEngineCtx *de_ctx, Signature *s, const char *st SCLogError("lua failed to set up"); goto error; } + if (list == 0) { + if (lua->flags & FLAG_LIST_JA3) { + list = g_lua_ja3_list_id; + } else if (lua->flags & FLAG_LIST_JA3S) { + list = g_lua_ja3s_list_id; + } + } if (SigMatchAppendSMToList(de_ctx, s, DETECT_LUA, (SigMatchCtx *)lua, list) == NULL) { goto error; diff --git a/src/output-lua.c b/src/output-lua.c index 199c8fcc15..5c087044e1 100644 --- a/src/output-lua.c +++ b/src/output-lua.c @@ -36,7 +36,6 @@ #include "util-lua.h" #include "util-lua-common.h" #include "util-lua-http.h" -#include "util-lua-ja3.h" #include "util-lua-tls.h" #include "util-lua-smtp.h" @@ -588,7 +587,6 @@ static lua_State *LuaScriptSetup(const char *filename, LogLuaMasterCtx *ctx) /* register functions common to all */ LuaRegisterFunctions(luastate); - LuaRegisterJa3Functions(luastate); LuaRegisterTlsFunctions(luastate); LuaRegisterSmtpFunctions(luastate); diff --git a/src/util-lua-builtins.c b/src/util-lua-builtins.c index c0898e28ea..1534b64939 100644 --- a/src/util-lua-builtins.c +++ b/src/util-lua-builtins.c @@ -29,6 +29,7 @@ #include "util-lua-hashlib.h" #include "util-lua-packetlib.h" #include "util-lua-rule.h" +#include "util-lua-ja3.h" #include "lauxlib.h" @@ -42,6 +43,7 @@ static const luaL_Reg builtins[] = { { "suricata.flowvar", LuaLoadFlowvarLib }, { "suricata.hashlib", SCLuaLoadHashlib }, { "suricata.http", SCLuaLoadHttpLib }, + { "suricata.ja3", SCLuaLoadJa3Lib }, { "suricata.packet", LuaLoadPacketLib }, { "suricata.rule", SCLuaLoadRuleLib }, { "suricata.ssh", SCLuaLoadSshLib }, diff --git a/src/util-lua-ja3.c b/src/util-lua-ja3.c index b7074ac1c6..33b0c5bc0b 100644 --- a/src/util-lua-ja3.c +++ b/src/util-lua-ja3.c @@ -24,145 +24,183 @@ */ #include "suricata-common.h" -#include "detect.h" -#include "pkt-var.h" -#include "conf.h" - -#include "threads.h" -#include "threadvars.h" -#include "tm-threads.h" - -#include "util-print.h" -#include "util-unittest.h" - -#include "util-debug.h" - -#include "output.h" -#include "app-layer.h" -#include "app-layer-parser.h" -#include "app-layer-ssl.h" -#include "util-privs.h" -#include "util-buffer.h" -#include "util-proto-name.h" -#include "util-logopenfile.h" -#include "util-time.h" - -#include "lua.h" -#include "lualib.h" -#include "lauxlib.h" - +#include "util-lua-ja3.h" #include "util-lua.h" #include "util-lua-common.h" -#include "util-lua-ja3.h" - -static int Ja3GetHash(lua_State *luastate) -{ - if (!(LuaStateNeedProto(luastate, ALPROTO_TLS))) - return LuaCallbackError(luastate, "error: protocol is not tls"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); - - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); +#include "app-layer-ssl.h" +#include "rust.h" - SSLState *ssl_state = (SSLState *)state; +static const char ja3_tx[] = "suricata:ja3:tx"; - if (ssl_state->client_connp.ja3_hash == NULL) - return LuaCallbackError(luastate, "error: no JA3 hash"); +struct LuaTx { + void *tx; // Quic or TLS Transaction + AppProto alproto; +}; - return LuaPushStringBuffer(luastate, - (uint8_t *)ssl_state->client_connp.ja3_hash, - strlen(ssl_state->client_connp.ja3_hash)); +static int LuaJa3GetTx(lua_State *L) +{ + AppProto alproto = ALPROTO_QUIC; + if (LuaStateNeedProto(L, ALPROTO_TLS)) { + alproto = ALPROTO_TLS; + } else if (!(LuaStateNeedProto(L, ALPROTO_QUIC))) { + return LuaCallbackError(L, "error: protocol nor tls neither quic"); + } + void *tx = LuaStateGetTX(L); + if (tx == NULL) { + return LuaCallbackError(L, "error: no tx available"); + } + struct LuaTx *ltx = (struct LuaTx *)lua_newuserdata(L, sizeof(*ltx)); + if (ltx == NULL) { + return LuaCallbackError(L, "error: fail to allocate user data"); + } + ltx->tx = tx; + ltx->alproto = alproto; + + luaL_getmetatable(L, ja3_tx); + lua_setmetatable(L, -2); + + return 1; } -static int Ja3GetString(lua_State *luastate) +static int LuaJa3TxGetHash(lua_State *L) { - if (!(LuaStateNeedProto(luastate, ALPROTO_TLS))) - return LuaCallbackError(luastate, "error: protocol is not tls"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); - - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); - - SSLState *ssl_state = (SSLState *)state; - - if (ssl_state->client_connp.ja3_str == NULL || - ssl_state->client_connp.ja3_str->data == NULL) - return LuaCallbackError(luastate, "error: no JA3 str"); - - return LuaPushStringBuffer(luastate, - (uint8_t *)ssl_state->client_connp.ja3_str->data, - ssl_state->client_connp.ja3_str->used); + struct LuaTx *ltx = luaL_testudata(L, 1, ja3_tx); + if (ltx == NULL) { + lua_pushnil(L); + return 1; + } + if (ltx->alproto == ALPROTO_TLS) { + SSLState *ssl_state = (SSLState *)ltx->tx; + if (ssl_state->client_connp.ja3_hash == NULL) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, (uint8_t *)ssl_state->client_connp.ja3_hash, + strlen(ssl_state->client_connp.ja3_hash)); + } // else QUIC { + const uint8_t *buf = NULL; + uint32_t b_len = 0; + if (!SCQuicTxGetJa3(ltx->tx, STREAM_TOSERVER, &buf, &b_len)) { + lua_pushnil(L); + return 1; + } + uint8_t ja3_hash[SC_MD5_HEX_LEN + 1]; + // this adds a final zero + SCMd5HashBufferToHex(buf, b_len, (char *)ja3_hash, SC_MD5_HEX_LEN + 1); + return LuaPushStringBuffer(L, ja3_hash, SC_MD5_HEX_LEN); } -static int Ja3SGetHash(lua_State *luastate) +static int LuaJa3TxGetString(lua_State *L) { - if (!(LuaStateNeedProto(luastate, ALPROTO_TLS))) - return LuaCallbackError(luastate, "error: protocol is not tls"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); - - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); - - SSLState *ssl_state = (SSLState *)state; - - if (ssl_state->server_connp.ja3_hash == NULL) - return LuaCallbackError(luastate, "error: no JA3S hash"); - - return LuaPushStringBuffer(luastate, - (uint8_t *)ssl_state->server_connp.ja3_hash, - strlen(ssl_state->server_connp.ja3_hash)); + struct LuaTx *ltx = luaL_testudata(L, 1, ja3_tx); + if (ltx == NULL) { + lua_pushnil(L); + return 1; + } + if (ltx->alproto == ALPROTO_TLS) { + SSLState *ssl_state = (SSLState *)ltx->tx; + if (ssl_state->client_connp.ja3_str == NULL || + ssl_state->client_connp.ja3_str->data == NULL) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, (uint8_t *)ssl_state->client_connp.ja3_str->data, + ssl_state->client_connp.ja3_str->used); + } // else QUIC { + const uint8_t *buf = NULL; + uint32_t b_len = 0; + if (!SCQuicTxGetJa3(ltx->tx, STREAM_TOSERVER, &buf, &b_len)) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, buf, b_len); } -static int Ja3SGetString(lua_State *luastate) +static int LuaJa3TxGetServerHash(lua_State *L) { - if (!(LuaStateNeedProto(luastate, ALPROTO_TLS))) - return LuaCallbackError(luastate, "error: protocol is not tls"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); - - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); - - SSLState *ssl_state = (SSLState *)state; - - if (ssl_state->server_connp.ja3_str == NULL || - ssl_state->server_connp.ja3_str->data == NULL) - return LuaCallbackError(luastate, "error: no JA3S str"); - - return LuaPushStringBuffer(luastate, - (uint8_t *)ssl_state->server_connp.ja3_str->data, - ssl_state->server_connp.ja3_str->used); + struct LuaTx *ltx = luaL_testudata(L, 1, ja3_tx); + if (ltx == NULL) { + lua_pushnil(L); + return 1; + } + if (ltx->alproto == ALPROTO_TLS) { + SSLState *ssl_state = (SSLState *)ltx->tx; + if (ssl_state->server_connp.ja3_hash == NULL) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, (uint8_t *)ssl_state->server_connp.ja3_hash, + strlen(ssl_state->server_connp.ja3_hash)); + } // else QUIC { + const uint8_t *buf = NULL; + uint32_t b_len = 0; + if (!SCQuicTxGetJa3(ltx->tx, STREAM_TOCLIENT, &buf, &b_len)) { + lua_pushnil(L); + return 1; + } + uint8_t ja3_hash[SC_MD5_HEX_LEN + 1]; + // this adds a final zero + SCMd5HashBufferToHex(buf, b_len, (char *)ja3_hash, SC_MD5_HEX_LEN + 1); + return LuaPushStringBuffer(L, ja3_hash, SC_MD5_HEX_LEN); } -/** *\brief Register JA3 Lua extensions */ -int LuaRegisterJa3Functions(lua_State *luastate) +static int LuaJa3TxGetServerString(lua_State *L) { - lua_pushcfunction(luastate, Ja3GetHash); - lua_setglobal(luastate, "Ja3GetHash"); + struct LuaTx *ltx = luaL_testudata(L, 1, ja3_tx); + if (ltx == NULL) { + lua_pushnil(L); + return 1; + } + if (ltx->alproto == ALPROTO_TLS) { + SSLState *ssl_state = (SSLState *)ltx->tx; + if (ssl_state->server_connp.ja3_str == NULL || + ssl_state->server_connp.ja3_str->data == NULL) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, (uint8_t *)ssl_state->server_connp.ja3_str->data, + ssl_state->server_connp.ja3_str->used); + } // else QUIC { + const uint8_t *buf = NULL; + uint32_t b_len = 0; + if (!SCQuicTxGetJa3(ltx->tx, STREAM_TOCLIENT, &buf, &b_len)) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, buf, b_len); +} - lua_pushcfunction(luastate, Ja3GetString); - lua_setglobal(luastate, "Ja3GetString"); +static const struct luaL_Reg txlib[] = { + // clang-format off + { "ja3_get_hash", LuaJa3TxGetHash }, + { "ja3_get_string", LuaJa3TxGetString }, + { "ja3s_get_hash", LuaJa3TxGetServerHash }, + { "ja3s_get_string", LuaJa3TxGetServerString }, + { NULL, NULL, } + // clang-format on +}; + +static int LuaJa3Enable(lua_State *L) +{ + SSLEnableJA3(); + return 1; +} - lua_pushcfunction(luastate, Ja3SGetHash); - lua_setglobal(luastate, "Ja3SGetHash"); +static const struct luaL_Reg ja3lib[] = { + // clang-format off + { "get_tx", LuaJa3GetTx }, + { "enable_ja3", LuaJa3Enable }, + { NULL, NULL,}, + // clang-format on +}; - lua_pushcfunction(luastate, Ja3SGetString); - lua_setglobal(luastate, "Ja3SGetString"); +int SCLuaLoadJa3Lib(lua_State *L) +{ + luaL_newmetatable(L, ja3_tx); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, txlib, 0); - return 0; + luaL_newlib(L, ja3lib); + return 1; } diff --git a/src/util-lua-ja3.h b/src/util-lua-ja3.h index 0bbbb666b2..a5b6cbe8d6 100644 --- a/src/util-lua-ja3.h +++ b/src/util-lua-ja3.h @@ -24,6 +24,8 @@ #ifndef SURICATA_UTIL_LUA_JA3_H #define SURICATA_UTIL_LUA_JA3_H -int LuaRegisterJa3Functions(lua_State *luastate); +#include "lua.h" + +int SCLuaLoadJa3Lib(lua_State *L); #endif /* SURICATA_UTIL_LUA_JA3_H */