lua: convert ja3 function into suricata.ja3 lib

Ticket: 7605
pull/13179/head
Philippe Antoine 2 months ago committed by Victor Julien
parent c578015edf
commit 7e78ad944c

@ -18,3 +18,4 @@ environment without access to additional modules.
packetlib
rule
ssh
ja3

@ -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)

@ -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
-----

@ -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;

@ -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;

@ -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);

@ -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 },

@ -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;
}

@ -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 */

Loading…
Cancel
Save