suricata
detect-tls-version.c
Go to the documentation of this file.
1 /* Copyright (C) 2007-2025 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 /**
19  * \file
20  *
21  * \author Victor Julien <victor@inliniac.net>
22  *
23  * Implements the tls.version keyword
24  */
25 
26 #include "suricata-common.h"
27 #include "threads.h"
28 #include "decode.h"
29 
30 #include "detect.h"
31 #include "detect-parse.h"
32 
33 #include "detect-engine.h"
34 #include "detect-engine-mpm.h"
35 #include "detect-engine-state.h"
36 
37 #include "flow.h"
38 #include "flow-var.h"
39 #include "flow-util.h"
40 
41 #include "util-debug.h"
42 #include "util-unittest-helper.h"
43 
44 #include "app-layer.h"
45 #include "app-layer-parser.h"
46 
47 #include "app-layer-ssl.h"
48 #include "detect-tls-version.h"
49 
50 #include "stream-tcp.h"
51 
52 /**
53  * \brief Regex for parsing "id" option, matching number or "number"
54  */
55 #define PARSE_REGEX "^\\s*([A-z0-9\\.]+|\"[A-z0-9\\.]+\")\\s*$"
56 
57 static DetectParseRegex parse_regex;
58 
59 static int DetectTlsVersionMatch (DetectEngineThreadCtx *,
60  Flow *, uint8_t, void *, void *,
61  const Signature *, const SigMatchCtx *);
62 static int DetectTlsVersionSetup (DetectEngineCtx *, Signature *, const char *);
63 #ifdef UNITTESTS
64 static void DetectTlsVersionRegisterTests(void);
65 #endif
66 static void DetectTlsVersionFree(DetectEngineCtx *, void *);
67 
68 /** buffer for the version keywords, registered at the hello states: the
69  * version is only final once the hello has been decoded (a hello can span
70  * several records, so the record layer version may be seen first). */
71 static int g_tls_version_list_id = 0;
72 
73 /**
74  * \brief Registration function for keyword: tls.version
75  */
77 {
78  sigmatch_table[DETECT_TLS_VERSION].name = "tls.version";
79  sigmatch_table[DETECT_TLS_VERSION].desc = "match on TLS/SSL version";
80  sigmatch_table[DETECT_TLS_VERSION].url = "/rules/tls-keywords.html#tls-version";
81  sigmatch_table[DETECT_TLS_VERSION].AppLayerTxMatch = DetectTlsVersionMatch;
82  sigmatch_table[DETECT_TLS_VERSION].Setup = DetectTlsVersionSetup;
83  sigmatch_table[DETECT_TLS_VERSION].Free = DetectTlsVersionFree;
84  /* the negotiated version is not final until the hello is decoded (a hello
85  * can span records, and TLS 1.3 reveals the version in supported_versions),
86  * so a no match must stay revisitable until then. */
88 #ifdef UNITTESTS
89  sigmatch_table[DETECT_TLS_VERSION].RegisterTests = DetectTlsVersionRegisterTests;
90 #endif
91 
92  DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
93 
94  g_tls_version_list_id = DetectBufferTypeRegister("tls_version");
95  DetectBufferTypeSetDescriptionByName("tls_version", "generic tls version inspection");
96  /* the negotiated version is only final once the hello decoded, so the
97  * keyword's engine must be revisited as the tx advances. */
98  DetectBufferTypeSetRunAlways("tls_version");
103 }
104 
105 /**
106  * \brief match the specified version on a tls session
107  *
108  * \param t pointer to thread vars
109  * \param det_ctx pointer to the pattern matcher thread
110  * \param p pointer to the current packet
111  * \param m pointer to the sigmatch that we will cast into DetectTlsVersionData
112  *
113  * \retval 0 no match, version not decoded yet (revisitable)
114  * \retval 1 match
115  * \retval 2 no match, version decoded and different (final)
116  */
117 static int DetectTlsVersionMatch (DetectEngineThreadCtx *det_ctx,
118  Flow *f, uint8_t flags, void *state, void *txv,
119  const Signature *s, const SigMatchCtx *m)
120 {
121  SCEnter();
122 
123  const DetectTlsVersionData *tls_data = (const DetectTlsVersionData *)m;
124  const SSLState *ssl_state = (SSLState *)state;
125  if (ssl_state == NULL) {
126  SCLogDebug("no tls state, no match");
127  SCReturnInt(0);
128  }
129 
130  uint16_t version = 0;
131  bool decoded = false;
132  SCLogDebug("looking for tls_data->ver 0x%02X (flags 0x%02X)", tls_data->ver, flags);
133 
134  if (flags & STREAM_TOCLIENT) {
135  version = ssl_state->server_connp.version;
136  /* the version is final only once the hello (including
137  * supported_versions) decoded: the phase can advance on a later
138  * app-data record or EOF even when the hello never decoded */
139  decoded = ssl_state->server_connp.hello_decoded;
140  SCLogDebug("server (toclient) version is 0x%02X decoded %s", version, BOOL2STR(decoded));
141  } else if (flags & STREAM_TOSERVER) {
142  version = ssl_state->client_connp.version;
143  decoded = ssl_state->client_connp.hello_decoded;
144  SCLogDebug("client (toserver) version is 0x%02X decoded %s", version, BOOL2STR(decoded));
145  }
146 
147  if (!decoded) {
148  /* the hello (or its supported_versions) is not decoded yet: the
149  * version seen so far (record layer or legacy hello field) is not
150  * final, so the miss must stay revisitable. */
151  SCReturnInt(0);
152  }
153 
154  /* The rule's phase: hook rules use the hook's progress, non-hook rules
155  * inspect the direction's hello state. A rule must not match before its
156  * phase. */
157  const uint8_t engine_progress =
158  (tls_data->hook_progress >= 0)
159  ? (uint8_t)tls_data->hook_progress
160  : ((flags & STREAM_TOCLIENT) ? (uint8_t)TLS_STATE_SERVER_HELLO
161  : (uint8_t)TLS_STATE_CLIENT_HELLO);
162  const int progress = AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags);
163  if (progress < 0)
164  SCReturnInt(0);
165  if (progress < engine_progress)
166  SCReturnInt(0);
167 
168  if ((tls_data->flags & DETECT_TLS_VERSION_FLAG_RAW) == 0) {
169  /* Match all TLSv1.3 drafts as TLSv1.3 */
170  if (((version >> 8) & 0xff) == 0x7f) {
171  version = TLS_VERSION_13;
172  }
173  }
174 
175  if (tls_data->ver == version)
176  SCReturnInt(1);
177 
178  /* Decoded but a different version: the mismatch is final only once the
179  * transaction moved past the rule's phase (or reached its end state), so
180  * a rule hooked at a state keeps the same decision point it had before. */
181  if (progress > engine_progress ||
182  progress == AppLayerParserGetTxEndState(f->proto, f->alproto, txv, flags)) {
183  SCReturnInt(2);
184  }
185  SCReturnInt(0);
186 }
187 
188 /**
189  * \brief This function is used to parse IPV4 ip_id passed via keyword: "id"
190  *
191  * \param de_ctx Pointer to the detection engine context
192  * \param idstr Pointer to the user provided id option
193  *
194  * \retval id_d pointer to DetectTlsVersionData on success
195  * \retval NULL on failure
196  */
197 static DetectTlsVersionData *DetectTlsVersionParse (DetectEngineCtx *de_ctx, const char *str)
198 {
199  uint16_t temp;
200  DetectTlsVersionData *tls = NULL;
201  int res = 0;
202  size_t pcre2len;
203 
204  pcre2_match_data *match = NULL;
205  int ret = DetectParsePcreExec(&parse_regex, &match, str, 0, 0);
206  if (ret < 1 || ret > 3) {
207  SCLogError("invalid tls.version option");
208  goto error;
209  }
210 
211  if (ret > 1) {
212  char ver_ptr[64];
213  char *tmp_str;
214  pcre2len = sizeof(ver_ptr);
215  res = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)ver_ptr, &pcre2len);
216  if (res < 0) {
217  SCLogError("pcre2_substring_copy_bynumber failed");
218  goto error;
219  }
220 
221  /* We have a correct id option */
222  tls = SCCalloc(1, sizeof(DetectTlsVersionData));
223  if (unlikely(tls == NULL))
224  goto error;
225 
226  tmp_str = ver_ptr;
227 
228  /* Let's see if we need to scape "'s */
229  if (tmp_str[0] == '"')
230  {
231  tmp_str[strlen(tmp_str) - 1] = '\0';
232  tmp_str += 1;
233  }
234 
235  if (strncmp("1.0", tmp_str, 3) == 0) {
236  temp = TLS_VERSION_10;
237  } else if (strncmp("1.1", tmp_str, 3) == 0) {
238  temp = TLS_VERSION_11;
239  } else if (strncmp("1.2", tmp_str, 3) == 0) {
240  temp = TLS_VERSION_12;
241  } else if (strncmp("1.3", tmp_str, 3) == 0) {
242  temp = TLS_VERSION_13;
243  } else if ((strncmp("0x", tmp_str, 2) == 0) && (strlen(str) == 6)) {
244  temp = (uint16_t)strtol(tmp_str, NULL, 0);
246  } else {
247  SCLogError("Invalid value");
248  goto error;
249  }
250 
251  tls->ver = temp;
252 
253  SCLogDebug("will look for tls %"PRIu16"", tls->ver);
254  }
255 
256  pcre2_match_data_free(match);
257  return tls;
258 
259 error:
260  if (match) {
261  pcre2_match_data_free(match);
262  }
263  if (tls != NULL)
264  DetectTlsVersionFree(de_ctx, tls);
265  return NULL;
266 
267 }
268 
269 /**
270  * \brief this function is used to add the parsed "id" option
271  * \brief into the current signature
272  *
273  * \param de_ctx pointer to the Detection Engine Context
274  * \param s pointer to the Current Signature
275  * \param idstr pointer to the user provided "id" option
276  *
277  * \retval 0 on Success
278  * \retval -1 on Failure
279  */
280 static int DetectTlsVersionSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
281 {
283  return -1;
284 
285  DetectTlsVersionData *tls = DetectTlsVersionParse(de_ctx, str);
286  if (tls == NULL)
287  return -1;
288 
289  /* keyword supports multiple hooks, so attach to the hook specified in the rule. */
290  int list = g_tls_version_list_id;
291  tls->hook_progress = -1;
292  /* Okay so far so good, lets get this into a SigMatch
293  * and put it in the Signature. */
295  list = s->init_data->hook.sm_list;
296  tls->hook_progress = (int8_t)s->init_data->hook.t.app.app_progress;
297  /* A hook at the first state (e.g. tls:client_started) is evaluated
298  * before the hello decoded and would not be revisited once the tx
299  * advances: run its engine on every update. Later hooks are revisited
300  * by the normal P+1 evaluation. */
301  if (tls->hook_progress == 0)
303  }
304 
305  if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_TLS_VERSION, (SigMatchCtx *)tls, list) == NULL) {
306  DetectTlsVersionFree(de_ctx, tls);
307  return -1;
308  }
309 
310  return 0;
311 }
312 
313 /**
314  * \brief this function will free memory associated with DetectTlsVersionData
315  *
316  * \param id_d pointer to DetectTlsVersionData
317  */
318 static void DetectTlsVersionFree(DetectEngineCtx *de_ctx, void *ptr)
319 {
321  SCFree(id_d);
322 }
323 
324 #ifdef UNITTESTS
326 #endif
DETECT_TLS_VERSION
@ DETECT_TLS_VERSION
Definition: detect-engine-register.h:149
SigTableElmt_::url
const char * url
Definition: detect.h:1545
SSLState_
SSLv[2.0|3.[0|1|2|3]] state structure.
Definition: app-layer-ssl.h:263
detect-engine.h
detect-tls-version.c
SigTableElmt_::desc
const char * desc
Definition: detect.h:1544
sigmatch_table
SigTableElmt * sigmatch_table
Definition: detect-parse.c:78
SigTableElmt_::Free
void(* Free)(DetectEngineCtx *, void *)
Definition: detect.h:1529
SignatureHook_::sm_list
int sm_list
Definition: detect.h:585
flow-util.h
DetectParseRegex
Definition: detect-parse.h:94
SigTableElmt_::name
const char * name
Definition: detect.h:1542
stream-tcp.h
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
SSLState_::client_connp
SSLStateConnp client_connp
Definition: app-layer-ssl.h:284
SSLStateConnp_::hello_decoded
bool hello_decoded
Definition: app-layer-ssl.h:224
SigTableElmt_::flags
uint32_t flags
Definition: detect.h:1533
ALPROTO_TLS
@ ALPROTO_TLS
Definition: app-layer-protos.h:39
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:282
SignatureHook_::app
struct SignatureHook_::@87::@88 app
SSLState_::server_connp
SSLStateConnp server_connp
Definition: app-layer-ssl.h:285
Flow_::proto
uint8_t proto
Definition: flow.h:381
SignatureHook_::t
union SignatureHook_::@87 t
threads.h
Flow_
Flow data structure.
Definition: flow.h:359
DetectEngineCtx_
main detection engine ctx
Definition: detect.h:995
DetectTlsVersionData_::hook_progress
int8_t hook_progress
Definition: detect-tls-version.h:34
SigTableElmt_::AppLayerTxMatch
int(* AppLayerTxMatch)(DetectEngineThreadCtx *, Flow *, uint8_t flags, void *alstate, void *txv, const Signature *, const SigMatchCtx *)
Definition: detect.h:1507
DetectParsePcreExec
int DetectParsePcreExec(DetectParseRegex *parse_regex, pcre2_match_data **match, const char *str, int start_offset, int options)
Definition: detect-parse.c:4019
m
SCMutex m
Definition: flow-hash.h:6
SCDetectSignatureSetAppProto
int SCDetectSignatureSetAppProto(Signature *s, AppProto alproto)
Definition: detect-parse.c:2613
SIG_FLAG_TOCLIENT
#define SIG_FLAG_TOCLIENT
Definition: detect.h:275
SigTableElmt_::Setup
int(* Setup)(DetectEngineCtx *, Signature *, const char *)
Definition: detect.h:1524
DetectAppLayerInspectEngineRegister
void DetectAppLayerInspectEngineRegister(const char *name, AppProto alproto, uint32_t dir, uint8_t progress, InspectEngineFuncPtr Callback, InspectionBufferGetDataPtr GetData)
Registers an app inspection engine.
Definition: detect-engine.c:275
DetectTlsVersionData_::flags
uint8_t flags
Definition: detect-tls-version.h:31
util-unittest-helper.h
DetectBufferTypeSetRunAlways
void DetectBufferTypeSetRunAlways(const char *name)
Definition: detect-engine.c:1560
SIGNATURE_HOOK_TYPE_APP
@ SIGNATURE_HOOK_TYPE_APP
Definition: detect.h:558
SIG_FLAG_TOSERVER
#define SIG_FLAG_TOSERVER
Definition: detect.h:274
DETECT_TLS_VERSION_FLAG_RAW
#define DETECT_TLS_VERSION_FLAG_RAW
Definition: detect-tls-version.h:27
decode.h
util-debug.h
de_ctx
DetectEngineCtx * de_ctx
Definition: fuzz_siginit.c:22
DetectTlsVersionRegister
void DetectTlsVersionRegister(void)
Registration function for keyword: tls.version.
Definition: detect-tls-version.c:76
DetectEngineThreadCtx_
Definition: detect.h:1316
BOOL2STR
#define BOOL2STR(b)
Definition: util-debug.h:542
DetectSetupParseRegexes
void DetectSetupParseRegexes(const char *parse_str, DetectParseRegex *detect_parse)
Definition: detect-parse.c:4145
SCEnter
#define SCEnter(...)
Definition: util-debug.h:284
detect-engine-mpm.h
SCSigMatchAppendSMToList
SigMatch * SCSigMatchAppendSMToList(DetectEngineCtx *de_ctx, Signature *s, uint16_t type, SigMatchCtx *ctx, const int list)
Append a SigMatch to the list type.
Definition: detect-parse.c:424
detect.h
DetectEngineBufferTypeSetRunAlways
void DetectEngineBufferTypeSetRunAlways(DetectEngineCtx *de_ctx, const int id)
Definition: detect-engine.c:1569
app-layer-parser.h
SignatureInitData_::hook
SignatureHook hook
Definition: detect.h:604
AppLayerParserGetStateProgress
int AppLayerParserGetStateProgress(uint8_t ipproto, AppProto alproto, void *tx, uint8_t flags)
get the progress value for a tx/protocol
Definition: app-layer-parser.c:1199
Signature_::init_data
SignatureInitData * init_data
Definition: detect.h:775
detect-engine-state.h
Data structures and function prototypes for keeping state for the detection engine.
TLS_STATE_CLIENT_HELLO
@ TLS_STATE_CLIENT_HELLO
Definition: app-layer-ssl.h:81
DetectTlsVersionData_::ver
uint16_t ver
Definition: detect-tls-version.h:30
SigMatchCtx_
Used to start a pointer to SigMatch context Should never be dereferenced without casting to something...
Definition: detect.h:355
DetectBufferTypeRegister
int DetectBufferTypeRegister(const char *name)
Definition: detect-engine.c:1388
PARSE_REGEX
#define PARSE_REGEX
Regex for parsing "id" option, matching number or "number".
Definition: detect-tls-version.c:55
flags
uint8_t flags
Definition: decode-gre.h:0
suricata-common.h
SignatureHook_::type
enum SignatureHookType type
Definition: detect.h:584
version
uint8_t version
Definition: decode-gre.h:1
detect-tls-version.h
DetectEngineInspectGenericList
uint8_t DetectEngineInspectGenericList(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
Definition: detect-engine.c:2155
str
#define str(s)
Definition: suricata-common.h:313
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:274
SCFree
#define SCFree(p)
Definition: util-mem.h:61
detect-parse.h
Signature_
Signature container.
Definition: detect.h:692
AppLayerParserGetTxEndState
uint8_t AppLayerParserGetTxEndState(uint8_t ipproto, AppProto alproto, void *tx, uint8_t flags)
get the end state (progress) for a transaction.
Definition: app-layer-parser.c:1177
SIGMATCH_SUPPORT_FIREWALL
#define SIGMATCH_SUPPORT_FIREWALL
Definition: detect-engine-register.h:336
TLS_STATE_SERVER_HELLO
@ TLS_STATE_SERVER_HELLO
Definition: app-layer-ssl.h:89
DetectBufferTypeSetDescriptionByName
void DetectBufferTypeSetDescriptionByName(const char *name, const char *desc)
Definition: detect-engine.c:1549
DetectTlsVersionData_
Definition: detect-tls-version.h:29
flow.h
SIGMATCH_STATEFUL
#define SIGMATCH_STATEFUL
Definition: detect-engine-register.h:356
Flow_::alproto
AppProto alproto
application level protocol
Definition: flow.h:455
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
SCReturnInt
#define SCReturnInt(x)
Definition: util-debug.h:288
flow-var.h
app-layer-ssl.h
SigTableElmt_::RegisterTests
void(* RegisterTests)(void)
Definition: detect.h:1531
app-layer.h
f
Flow f
Definition: fuzz_dataset.c:32
SSLStateConnp_::version
uint16_t version
Definition: app-layer-ssl.h:186