/src/suricata7/src/detect-ssl-version.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2020 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 detect-ssl-version.c |
20 | | * |
21 | | * \author Gurvinder Singh <gurvindersinghdahiya@gmail.com> |
22 | | * |
23 | | * Implements the ssl_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.h" |
43 | | #include "util-unittest-helper.h" |
44 | | |
45 | | #include "app-layer.h" |
46 | | #include "app-layer-parser.h" |
47 | | |
48 | | #include "detect-ssl-version.h" |
49 | | |
50 | | #include "stream-tcp.h" |
51 | | #include "app-layer-ssl.h" |
52 | | |
53 | | |
54 | | static int DetectSslVersionMatch(DetectEngineThreadCtx *, |
55 | | Flow *, uint8_t, void *, void *, |
56 | | const Signature *, const SigMatchCtx *); |
57 | | static int DetectSslVersionSetup(DetectEngineCtx *, Signature *, const char *); |
58 | | #ifdef UNITTESTS |
59 | | static void DetectSslVersionRegisterTests(void); |
60 | | #endif |
61 | | static void DetectSslVersionFree(DetectEngineCtx *, void *); |
62 | | static int g_tls_generic_list_id = 0; |
63 | | |
64 | | /** |
65 | | * \brief Registration function for keyword: ssl_version |
66 | | */ |
67 | | void DetectSslVersionRegister(void) |
68 | 73 | { |
69 | 73 | sigmatch_table[DETECT_AL_SSL_VERSION].name = "ssl_version"; |
70 | 73 | sigmatch_table[DETECT_AL_SSL_VERSION].desc = "match version of SSL/TLS record"; |
71 | 73 | sigmatch_table[DETECT_AL_SSL_VERSION].url = "/rules/tls-keywords.html#ssl-version"; |
72 | 73 | sigmatch_table[DETECT_AL_SSL_VERSION].AppLayerTxMatch = DetectSslVersionMatch; |
73 | 73 | sigmatch_table[DETECT_AL_SSL_VERSION].Setup = DetectSslVersionSetup; |
74 | 73 | sigmatch_table[DETECT_AL_SSL_VERSION].Free = DetectSslVersionFree; |
75 | | #ifdef UNITTESTS |
76 | | sigmatch_table[DETECT_AL_SSL_VERSION].RegisterTests = DetectSslVersionRegisterTests; |
77 | | #endif |
78 | | |
79 | 73 | g_tls_generic_list_id = DetectBufferTypeRegister("tls_generic"); |
80 | 73 | } |
81 | | |
82 | | /** |
83 | | * \brief match the specified version on a ssl session |
84 | | * |
85 | | * \param t pointer to thread vars |
86 | | * \param det_ctx pointer to the pattern matcher thread |
87 | | * \param p pointer to the current packet |
88 | | * \param m pointer to the sigmatch that we will cast into DetectSslVersionData |
89 | | * |
90 | | * \retval 0 no match |
91 | | * \retval 1 match |
92 | | */ |
93 | | static int DetectSslVersionMatch(DetectEngineThreadCtx *det_ctx, |
94 | | Flow *f, uint8_t flags, void *state, void *txv, |
95 | | const Signature *s, const SigMatchCtx *m) |
96 | 0 | { |
97 | 0 | SCEnter(); |
98 | |
|
99 | 0 | int ret = 0; |
100 | 0 | uint16_t ver = 0; |
101 | 0 | uint8_t sig_ver = TLS_UNKNOWN; |
102 | |
|
103 | 0 | const DetectSslVersionData *ssl = (const DetectSslVersionData *)m; |
104 | 0 | SSLState *app_state = (SSLState *)state; |
105 | 0 | if (app_state == NULL) { |
106 | 0 | SCLogDebug("no app state, no match"); |
107 | 0 | SCReturnInt(0); |
108 | 0 | } |
109 | | |
110 | 0 | if (flags & STREAM_TOCLIENT) { |
111 | 0 | SCLogDebug("server (toclient) version is 0x%02X", |
112 | 0 | app_state->server_connp.version); |
113 | 0 | ver = app_state->server_connp.version; |
114 | 0 | } else if (flags & STREAM_TOSERVER) { |
115 | 0 | SCLogDebug("client (toserver) version is 0x%02X", |
116 | 0 | app_state->client_connp.version); |
117 | 0 | ver = app_state->client_connp.version; |
118 | 0 | } |
119 | |
|
120 | 0 | switch (ver) { |
121 | 0 | case SSL_VERSION_2: |
122 | 0 | if (ver == ssl->data[SSLv2].ver) |
123 | 0 | ret = 1; |
124 | 0 | sig_ver = SSLv2; |
125 | 0 | break; |
126 | 0 | case SSL_VERSION_3: |
127 | 0 | if (ver == ssl->data[SSLv3].ver) |
128 | 0 | ret = 1; |
129 | 0 | sig_ver = SSLv3; |
130 | 0 | break; |
131 | 0 | case TLS_VERSION_10: |
132 | 0 | if (ver == ssl->data[TLS10].ver) |
133 | 0 | ret = 1; |
134 | 0 | sig_ver = TLS10; |
135 | 0 | break; |
136 | 0 | case TLS_VERSION_11: |
137 | 0 | if (ver == ssl->data[TLS11].ver) |
138 | 0 | ret = 1; |
139 | 0 | sig_ver = TLS11; |
140 | 0 | break; |
141 | 0 | case TLS_VERSION_12: |
142 | 0 | if (ver == ssl->data[TLS12].ver) |
143 | 0 | ret = 1; |
144 | 0 | sig_ver = TLS12; |
145 | 0 | break; |
146 | 0 | case TLS_VERSION_13_DRAFT28: |
147 | 0 | case TLS_VERSION_13_DRAFT27: |
148 | 0 | case TLS_VERSION_13_DRAFT26: |
149 | 0 | case TLS_VERSION_13_DRAFT25: |
150 | 0 | case TLS_VERSION_13_DRAFT24: |
151 | 0 | case TLS_VERSION_13_DRAFT23: |
152 | 0 | case TLS_VERSION_13_DRAFT22: |
153 | 0 | case TLS_VERSION_13_DRAFT21: |
154 | 0 | case TLS_VERSION_13_DRAFT20: |
155 | 0 | case TLS_VERSION_13_DRAFT19: |
156 | 0 | case TLS_VERSION_13_DRAFT18: |
157 | 0 | case TLS_VERSION_13_DRAFT17: |
158 | 0 | case TLS_VERSION_13_DRAFT16: |
159 | 0 | case TLS_VERSION_13_PRE_DRAFT16: |
160 | 0 | if (((ver >> 8) & 0xff) == 0x7f) |
161 | 0 | ver = TLS_VERSION_13; |
162 | | /* fall through */ |
163 | 0 | case TLS_VERSION_13: |
164 | 0 | if (ver == ssl->data[TLS13].ver) |
165 | 0 | ret = 1; |
166 | 0 | sig_ver = TLS13; |
167 | 0 | break; |
168 | 0 | } |
169 | | |
170 | 0 | if (sig_ver == TLS_UNKNOWN) |
171 | 0 | SCReturnInt(0); |
172 | | |
173 | 0 | SCReturnInt(ret ^ ((ssl->data[sig_ver].flags & DETECT_SSL_VERSION_NEGATED) ? 1 : 0)); |
174 | 0 | } |
175 | | |
176 | | struct SSLVersionKeywords { |
177 | | const char *word; |
178 | | int index; |
179 | | uint16_t value; |
180 | | }; |
181 | | |
182 | | struct SSLVersionKeywords ssl_version_keywords[TLS_SIZE] = { |
183 | | { "sslv2", SSLv2, SSL_VERSION_2 }, |
184 | | { "sslv3", SSLv3, SSL_VERSION_3 }, |
185 | | { "tls1.0", TLS10, TLS_VERSION_10 }, |
186 | | { "tls1.1", TLS11, TLS_VERSION_11 }, |
187 | | { "tls1.2", TLS12, TLS_VERSION_12 }, |
188 | | { "tls1.3", TLS13, TLS_VERSION_13 }, |
189 | | }; |
190 | | |
191 | | /** |
192 | | * \brief This function is used to parse ssl_version data passed via |
193 | | * keyword: "ssl_version" |
194 | | * |
195 | | * \param de_ctx Pointer to the detection engine context |
196 | | * \param str Pointer to the user provided options |
197 | | * |
198 | | * \retval ssl pointer to DetectSslVersionData on success |
199 | | * \retval NULL on failure |
200 | | */ |
201 | | static DetectSslVersionData *DetectSslVersionParse(DetectEngineCtx *de_ctx, const char *str) |
202 | 1.45k | { |
203 | 1.45k | DetectSslVersionData *ssl = NULL; |
204 | 1.45k | const char *tmp_str = str; |
205 | 1.45k | size_t tmp_len = 0; |
206 | 1.45k | uint8_t found = 0; |
207 | | |
208 | | /* We have a correct ssl_version options */ |
209 | 1.45k | ssl = SCCalloc(1, sizeof(DetectSslVersionData)); |
210 | 1.45k | if (unlikely(ssl == NULL)) |
211 | 0 | goto error; |
212 | | |
213 | | // skip leading space |
214 | 1.65k | while (tmp_str[0] != 0 && isspace(tmp_str[0])) { |
215 | 197 | tmp_str++; |
216 | 197 | } |
217 | 1.45k | if (tmp_str[0] == 0) { |
218 | 1 | SCLogError("Invalid empty value"); |
219 | 1 | goto error; |
220 | 1 | } |
221 | | // iterate every version separated by comma |
222 | 2.13k | while (tmp_str[0] != 0) { |
223 | 1.52k | uint8_t neg = 0; |
224 | 1.52k | if (tmp_str[0] == '!') { |
225 | 96 | neg = 1; |
226 | 96 | tmp_str++; |
227 | 96 | } |
228 | | // counts word length |
229 | 1.52k | tmp_len = 0; |
230 | 35.1k | while (tmp_str[tmp_len] != 0 && !isspace(tmp_str[tmp_len]) && tmp_str[tmp_len] != ',') { |
231 | 33.6k | tmp_len++; |
232 | 33.6k | } |
233 | | |
234 | 1.52k | bool is_keyword = false; |
235 | 9.70k | for (size_t i = 0; i < TLS_SIZE; i++) { |
236 | 8.85k | if (tmp_len == strlen(ssl_version_keywords[i].word) && |
237 | 2.56k | strncasecmp(ssl_version_keywords[i].word, tmp_str, tmp_len) == 0) { |
238 | 674 | if (ssl->data[ssl_version_keywords[i].index].ver != 0) { |
239 | 1 | SCLogError("Invalid duplicate value"); |
240 | 1 | goto error; |
241 | 1 | } |
242 | 673 | ssl->data[ssl_version_keywords[i].index].ver = ssl_version_keywords[i].value; |
243 | 673 | if (neg == 1) |
244 | 87 | ssl->data[ssl_version_keywords[i].index].flags |= DETECT_SSL_VERSION_NEGATED; |
245 | 673 | is_keyword = true; |
246 | 673 | break; |
247 | 674 | } |
248 | 8.85k | } |
249 | 1.52k | if (!is_keyword) { |
250 | 847 | SCLogError("Invalid unknown value"); |
251 | 847 | goto error; |
252 | 847 | } |
253 | | |
254 | | /* check consistency between negative and positive values : |
255 | | * if there is a negative value, it overrides positive values |
256 | | */ |
257 | 673 | if (found == 0) { |
258 | 629 | found |= 1 << neg; |
259 | 629 | } else if (found != 1 << neg) { |
260 | 1 | SCLogError("Invalid value mixing negative and positive forms"); |
261 | 1 | goto error; |
262 | 1 | } |
263 | | |
264 | 672 | tmp_str += tmp_len; |
265 | 3.31k | while (isspace(tmp_str[0]) || tmp_str[0] == ',') { |
266 | 2.64k | tmp_str++; |
267 | 2.64k | } |
268 | 672 | } |
269 | | |
270 | 609 | return ssl; |
271 | | |
272 | 850 | error: |
273 | 850 | if (ssl != NULL) |
274 | 850 | DetectSslVersionFree(de_ctx, ssl); |
275 | 850 | return NULL; |
276 | | |
277 | 1.45k | } |
278 | | |
279 | | /** |
280 | | * \brief this function is used to add the parsed "id" option |
281 | | * \brief into the current signature |
282 | | * |
283 | | * \param de_ctx pointer to the Detection Engine Context |
284 | | * \param s pointer to the Current Signature |
285 | | * \param idstr pointer to the user provided "id" option |
286 | | * |
287 | | * \retval 0 on Success |
288 | | * \retval -1 on Failure |
289 | | */ |
290 | | static int DetectSslVersionSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) |
291 | 282 | { |
292 | 282 | DetectSslVersionData *ssl = NULL; |
293 | 282 | SigMatch *sm = NULL; |
294 | | |
295 | 282 | if (DetectSignatureSetAppProto(s, ALPROTO_TLS) != 0) |
296 | 7 | return -1; |
297 | | |
298 | 275 | ssl = DetectSslVersionParse(de_ctx, str); |
299 | 275 | if (ssl == NULL) |
300 | 271 | goto error; |
301 | | |
302 | | /* Okay so far so good, lets get this into a SigMatch |
303 | | * and put it in the Signature. */ |
304 | 4 | sm = SigMatchAlloc(); |
305 | 4 | if (sm == NULL) |
306 | 0 | goto error; |
307 | | |
308 | 4 | sm->type = DETECT_AL_SSL_VERSION; |
309 | 4 | sm->ctx = (void *)ssl; |
310 | | |
311 | 4 | SigMatchAppendSMToList(s, sm, g_tls_generic_list_id); |
312 | 4 | return 0; |
313 | | |
314 | 271 | error: |
315 | 271 | if (ssl != NULL) |
316 | 0 | DetectSslVersionFree(de_ctx, ssl); |
317 | 271 | if (sm != NULL) |
318 | 0 | SCFree(sm); |
319 | 271 | return -1; |
320 | 4 | } |
321 | | |
322 | | /** |
323 | | * \brief this function will free memory associated with DetectSslVersionData |
324 | | * |
325 | | * \param id_d pointer to DetectSslVersionData |
326 | | */ |
327 | | void DetectSslVersionFree(DetectEngineCtx *de_ctx, void *ptr) |
328 | 1.45k | { |
329 | 1.45k | if (ptr != NULL) |
330 | 1.45k | SCFree(ptr); |
331 | 1.45k | } |
332 | | |
333 | | #ifdef UNITTESTS |
334 | | #include "tests/detect-ssl-version.c" |
335 | | #endif |