/src/suricata7/src/detect-http-host.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2019 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 | | * \ingroup httplayer |
20 | | * |
21 | | * @{ |
22 | | */ |
23 | | |
24 | | |
25 | | /** |
26 | | * \file |
27 | | * |
28 | | * \author Anoop Saldanha <anoopsaldanha@gmail.com> |
29 | | * |
30 | | * Implements support for the http_host keyword. |
31 | | */ |
32 | | |
33 | | #include "suricata-common.h" |
34 | | #include "threads.h" |
35 | | #include "decode.h" |
36 | | |
37 | | #include "detect.h" |
38 | | #include "detect-parse.h" |
39 | | #include "detect-engine.h" |
40 | | #include "detect-engine-mpm.h" |
41 | | #include "detect-engine-prefilter.h" |
42 | | #include "detect-content.h" |
43 | | #include "detect-pcre.h" |
44 | | |
45 | | #include "flow.h" |
46 | | #include "flow-var.h" |
47 | | #include "flow-util.h" |
48 | | |
49 | | #include "util-debug.h" |
50 | | #include "util-unittest.h" |
51 | | #include "util-unittest-helper.h" |
52 | | #include "util-spm.h" |
53 | | |
54 | | #include "app-layer.h" |
55 | | #include "app-layer-parser.h" |
56 | | |
57 | | #include "app-layer-htp.h" |
58 | | #include "stream-tcp.h" |
59 | | #include "detect-http-host.h" |
60 | | |
61 | | static int DetectHttpHHSetup(DetectEngineCtx *, Signature *, const char *); |
62 | | #ifdef UNITTESTS |
63 | | static void DetectHttpHHRegisterTests(void); |
64 | | #endif |
65 | | static bool DetectHttpHostValidateCallback(const Signature *s, const char **sigerror); |
66 | | static int DetectHttpHostSetup(DetectEngineCtx *, Signature *, const char *); |
67 | | static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, |
68 | | const DetectEngineTransforms *transforms, |
69 | | Flow *_f, const uint8_t _flow_flags, |
70 | | void *txv, const int list_id); |
71 | | static InspectionBuffer *GetData2(DetectEngineThreadCtx *det_ctx, |
72 | | const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, |
73 | | const int list_id); |
74 | | static int DetectHttpHRHSetup(DetectEngineCtx *, Signature *, const char *); |
75 | | static int g_http_raw_host_buffer_id = 0; |
76 | | static int DetectHttpHostRawSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str); |
77 | | static InspectionBuffer *GetRawData(DetectEngineThreadCtx *det_ctx, |
78 | | const DetectEngineTransforms *transforms, Flow *_f, |
79 | | const uint8_t _flow_flags, void *txv, const int list_id); |
80 | | static InspectionBuffer *GetRawData2(DetectEngineThreadCtx *det_ctx, |
81 | | const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, |
82 | | const int list_id); |
83 | | static int g_http_host_buffer_id = 0; |
84 | | static int g_http2_thread_id = 0; |
85 | | static int g_http2_raw_thread_id = 0; |
86 | | |
87 | | /** |
88 | | * \brief Registers the keyword handlers for the "http_host" keyword. |
89 | | */ |
90 | | void DetectHttpHHRegister(void) |
91 | 75 | { |
92 | | /* http_host content modifier */ |
93 | 75 | sigmatch_table[DETECT_AL_HTTP_HOST].name = "http_host"; |
94 | 75 | sigmatch_table[DETECT_AL_HTTP_HOST].desc = "content modifier to match on the HTTP hostname"; |
95 | 75 | sigmatch_table[DETECT_AL_HTTP_HOST].url = "/rules/http-keywords.html#http-host-and-http-raw-host"; |
96 | 75 | sigmatch_table[DETECT_AL_HTTP_HOST].Setup = DetectHttpHHSetup; |
97 | | #ifdef UNITTESTS |
98 | | sigmatch_table[DETECT_AL_HTTP_HOST].RegisterTests = DetectHttpHHRegisterTests; |
99 | | #endif |
100 | 75 | sigmatch_table[DETECT_AL_HTTP_HOST].flags |= SIGMATCH_NOOPT|SIGMATCH_INFO_CONTENT_MODIFIER; |
101 | 75 | sigmatch_table[DETECT_AL_HTTP_HOST].alternative = DETECT_HTTP_HOST; |
102 | | |
103 | | /* http.host sticky buffer */ |
104 | 75 | sigmatch_table[DETECT_HTTP_HOST].name = "http.host"; |
105 | 75 | sigmatch_table[DETECT_HTTP_HOST].desc = "sticky buffer to match on the HTTP Host buffer"; |
106 | 75 | sigmatch_table[DETECT_HTTP_HOST].url = "/rules/http-keywords.html#http-host-and-http-raw-host"; |
107 | 75 | sigmatch_table[DETECT_HTTP_HOST].Setup = DetectHttpHostSetup; |
108 | 75 | sigmatch_table[DETECT_HTTP_HOST].flags |= SIGMATCH_NOOPT|SIGMATCH_INFO_STICKY_BUFFER; |
109 | | |
110 | 75 | DetectAppLayerInspectEngineRegister2("http_host", ALPROTO_HTTP1, SIG_FLAG_TOSERVER, |
111 | 75 | HTP_REQUEST_HEADERS, DetectEngineInspectBufferGeneric, GetData); |
112 | | |
113 | 75 | DetectAppLayerMpmRegister2("http_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, |
114 | 75 | GetData, ALPROTO_HTTP1, HTP_REQUEST_HEADERS); |
115 | | |
116 | 75 | DetectAppLayerInspectEngineRegister2("http_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, |
117 | 75 | HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); |
118 | | |
119 | 75 | DetectAppLayerMpmRegister2("http_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, |
120 | 75 | GetData2, ALPROTO_HTTP2, HTTP2StateOpen); |
121 | | |
122 | 75 | DetectBufferTypeRegisterValidateCallback("http_host", |
123 | 75 | DetectHttpHostValidateCallback); |
124 | | |
125 | 75 | DetectBufferTypeSetDescriptionByName("http_host", |
126 | 75 | "http host"); |
127 | | |
128 | 75 | g_http2_thread_id = DetectRegisterThreadCtxGlobalFuncs( |
129 | 75 | "http_host", SCHttp2ThreadBufDataInit, NULL, SCHttp2ThreadBufDataFree); |
130 | | |
131 | 75 | g_http_host_buffer_id = DetectBufferTypeGetByName("http_host"); |
132 | | |
133 | | /* http_raw_host content modifier */ |
134 | 75 | sigmatch_table[DETECT_AL_HTTP_RAW_HOST].name = "http_raw_host"; |
135 | 75 | sigmatch_table[DETECT_AL_HTTP_RAW_HOST].desc = "content modifier to match on the HTTP host header or the raw hostname from the HTTP uri"; |
136 | 75 | sigmatch_table[DETECT_AL_HTTP_RAW_HOST].url = "/rules/http-keywords.html#http-host-and-http-raw-host"; |
137 | 75 | sigmatch_table[DETECT_AL_HTTP_RAW_HOST].Setup = DetectHttpHRHSetup; |
138 | 75 | sigmatch_table[DETECT_AL_HTTP_RAW_HOST].flags |= SIGMATCH_NOOPT|SIGMATCH_INFO_CONTENT_MODIFIER; |
139 | 75 | sigmatch_table[DETECT_AL_HTTP_RAW_HOST].alternative = DETECT_HTTP_HOST_RAW; |
140 | | |
141 | | /* http.host sticky buffer */ |
142 | 75 | sigmatch_table[DETECT_HTTP_HOST_RAW].name = "http.host.raw"; |
143 | 75 | sigmatch_table[DETECT_HTTP_HOST_RAW].desc = "sticky buffer to match on the HTTP host header or the raw hostname from the HTTP uri"; |
144 | 75 | sigmatch_table[DETECT_HTTP_HOST_RAW].url = "/rules/http-keywords.html#http-host-and-http-raw-host"; |
145 | 75 | sigmatch_table[DETECT_HTTP_HOST_RAW].Setup = DetectHttpHostRawSetupSticky; |
146 | 75 | sigmatch_table[DETECT_HTTP_HOST_RAW].flags |= SIGMATCH_NOOPT|SIGMATCH_INFO_STICKY_BUFFER; |
147 | | |
148 | 75 | DetectAppLayerInspectEngineRegister2("http_raw_host", ALPROTO_HTTP1, SIG_FLAG_TOSERVER, |
149 | 75 | HTP_REQUEST_HEADERS, DetectEngineInspectBufferGeneric, GetRawData); |
150 | | |
151 | 75 | DetectAppLayerMpmRegister2("http_raw_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, |
152 | 75 | GetRawData, ALPROTO_HTTP1, HTP_REQUEST_HEADERS); |
153 | | |
154 | 75 | DetectAppLayerInspectEngineRegister2("http_raw_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, |
155 | 75 | HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetRawData2); |
156 | | |
157 | 75 | DetectAppLayerMpmRegister2("http_raw_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, |
158 | 75 | GetRawData2, ALPROTO_HTTP2, HTTP2StateOpen); |
159 | | |
160 | 75 | DetectBufferTypeSetDescriptionByName("http_raw_host", |
161 | 75 | "http raw host header"); |
162 | | |
163 | 75 | g_http2_raw_thread_id = DetectRegisterThreadCtxGlobalFuncs( |
164 | 75 | "http_raw_host", SCHttp2ThreadBufDataInit, NULL, SCHttp2ThreadBufDataFree); |
165 | | |
166 | 75 | g_http_raw_host_buffer_id = DetectBufferTypeGetByName("http_raw_host"); |
167 | 75 | } |
168 | | |
169 | | /** |
170 | | * \brief The setup function for the http_host keyword for a signature. |
171 | | * |
172 | | * \param de_ctx Pointer to the detection engine context. |
173 | | * \param s Pointer to the signature for the current Signature being |
174 | | * parsed from the rules. |
175 | | * \param m Pointer to the head of the SigMatch for the current rule |
176 | | * being parsed. |
177 | | * \param arg Pointer to the string holding the keyword value. |
178 | | * |
179 | | * \retval 0 On success |
180 | | * \retval -1 On failure |
181 | | */ |
182 | | static int DetectHttpHHSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) |
183 | 2.25k | { |
184 | 2.25k | return DetectEngineContentModifierBufferSetup( |
185 | 2.25k | de_ctx, s, arg, DETECT_AL_HTTP_HOST, g_http_host_buffer_id, ALPROTO_HTTP1); |
186 | 2.25k | } |
187 | | |
188 | | static bool DetectHttpHostValidateCallback(const Signature *s, const char **sigerror) |
189 | 17.5k | { |
190 | 62.4k | for (uint32_t x = 0; x < s->init_data->buffer_index; x++) { |
191 | 45.0k | if (s->init_data->buffers[x].id != (uint32_t)g_http_host_buffer_id) |
192 | 30.8k | continue; |
193 | 14.2k | const SigMatch *sm = s->init_data->buffers[x].head; |
194 | 33.6k | for (; sm != NULL; sm = sm->next) { |
195 | 19.5k | if (sm->type == DETECT_CONTENT) { |
196 | 4.44k | DetectContentData *cd = (DetectContentData *)sm->ctx; |
197 | 4.44k | if (cd->flags & DETECT_CONTENT_NOCASE) { |
198 | 21 | *sigerror = "http.host keyword " |
199 | 21 | "specified along with \"nocase\". " |
200 | 21 | "The hostname buffer is normalized " |
201 | 21 | "to lowercase, specifying " |
202 | 21 | "nocase is redundant."; |
203 | 21 | SCLogWarning("rule %u: %s", s->id, *sigerror); |
204 | 21 | return false; |
205 | 4.42k | } else { |
206 | 4.42k | uint32_t u; |
207 | 21.0k | for (u = 0; u < cd->content_len; u++) { |
208 | 16.7k | if (isupper(cd->content[u])) |
209 | 131 | break; |
210 | 16.7k | } |
211 | 4.42k | if (u != cd->content_len) { |
212 | 131 | *sigerror = "A pattern with " |
213 | 131 | "uppercase characters detected for http.host. " |
214 | 131 | "The hostname buffer is normalized to lowercase, " |
215 | 131 | "please specify a lowercase pattern."; |
216 | 131 | SCLogWarning("rule %u: %s", s->id, *sigerror); |
217 | 131 | return false; |
218 | 131 | } |
219 | 4.42k | } |
220 | 4.44k | } |
221 | 19.5k | } |
222 | 14.2k | } |
223 | | |
224 | 17.3k | return true; |
225 | 17.5k | } |
226 | | |
227 | | /** |
228 | | * \brief this function setup the http.host keyword used in the rule |
229 | | * |
230 | | * \param de_ctx Pointer to the Detection Engine Context |
231 | | * \param s Pointer to the Signature to which the current keyword belongs |
232 | | * \param str Should hold an empty string always |
233 | | * |
234 | | * \retval 0 On success |
235 | | */ |
236 | | static int DetectHttpHostSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) |
237 | 44.3k | { |
238 | 44.3k | if (DetectBufferSetActiveList(de_ctx, s, g_http_host_buffer_id) < 0) |
239 | 491 | return -1; |
240 | 43.8k | if (DetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0) |
241 | 361 | return -1; |
242 | 43.5k | return 0; |
243 | 43.8k | } |
244 | | |
245 | | static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, |
246 | | const DetectEngineTransforms *transforms, Flow *_f, |
247 | | const uint8_t _flow_flags, void *txv, const int list_id) |
248 | 269 | { |
249 | 269 | InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); |
250 | 269 | if (buffer->inspect == NULL) { |
251 | 228 | htp_tx_t *tx = (htp_tx_t *)txv; |
252 | | |
253 | 228 | if (tx->request_hostname == NULL) |
254 | 107 | return NULL; |
255 | | |
256 | 121 | const uint32_t data_len = bstr_len(tx->request_hostname); |
257 | 121 | const uint8_t *data = bstr_ptr(tx->request_hostname); |
258 | | |
259 | 121 | InspectionBufferSetup(det_ctx, list_id, buffer, data, data_len); |
260 | 121 | InspectionBufferApplyTransforms(buffer, transforms); |
261 | 121 | } |
262 | | |
263 | 162 | return buffer; |
264 | 269 | } |
265 | | |
266 | | static InspectionBuffer *GetData2(DetectEngineThreadCtx *det_ctx, |
267 | | const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, |
268 | | const int list_id) |
269 | 507 | { |
270 | 507 | InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); |
271 | 507 | if (buffer->inspect == NULL) { |
272 | 398 | uint32_t b_len = 0; |
273 | 398 | const uint8_t *b = NULL; |
274 | 398 | void *thread_buf = DetectThreadCtxGetGlobalKeywordThreadCtx(det_ctx, g_http2_thread_id); |
275 | 398 | if (thread_buf == NULL) |
276 | 0 | return NULL; |
277 | 398 | if (SCHttp2TxGetHostNorm(txv, &b, &b_len, thread_buf) != 1) |
278 | 237 | return NULL; |
279 | 161 | if (b == NULL || b_len == 0) |
280 | 2 | return NULL; |
281 | | |
282 | 159 | InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); |
283 | 159 | InspectionBufferApplyTransforms(buffer, transforms); |
284 | 159 | } |
285 | | |
286 | 268 | return buffer; |
287 | 507 | } |
288 | | |
289 | | static InspectionBuffer *GetRawData2(DetectEngineThreadCtx *det_ctx, |
290 | | const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, |
291 | | const int list_id) |
292 | 970 | { |
293 | 970 | InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); |
294 | 970 | if (buffer->inspect == NULL) { |
295 | 953 | uint32_t b_len = 0; |
296 | 953 | const uint8_t *b = NULL; |
297 | 953 | void *thread_buf = DetectThreadCtxGetGlobalKeywordThreadCtx(det_ctx, g_http2_raw_thread_id); |
298 | 953 | if (thread_buf == NULL) |
299 | 0 | return NULL; |
300 | | |
301 | 953 | if (SCHttp2TxGetHost(txv, &b, &b_len, thread_buf) != 1) |
302 | 656 | return NULL; |
303 | 297 | if (b == NULL || b_len == 0) |
304 | 0 | return NULL; |
305 | | |
306 | 297 | InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); |
307 | 297 | InspectionBufferApplyTransforms(buffer, transforms); |
308 | 297 | } |
309 | | |
310 | 314 | return buffer; |
311 | 970 | } |
312 | | |
313 | | /** |
314 | | * \brief The setup function for the http_raw_host keyword for a signature. |
315 | | * |
316 | | * \param de_ctx Pointer to the detection engine context. |
317 | | * \param s Pointer to the signature for the current Signature being |
318 | | * parsed from the rules. |
319 | | * \param m Pointer to the head of the SigMatch for the current rule |
320 | | * being parsed. |
321 | | * \param arg Pointer to the string holding the keyword value. |
322 | | * |
323 | | * \retval 0 On success |
324 | | * \retval -1 On failure |
325 | | */ |
326 | | int DetectHttpHRHSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) |
327 | 594 | { |
328 | 594 | return DetectEngineContentModifierBufferSetup( |
329 | 594 | de_ctx, s, arg, DETECT_AL_HTTP_RAW_HOST, g_http_raw_host_buffer_id, ALPROTO_HTTP1); |
330 | 594 | } |
331 | | |
332 | | /** |
333 | | * \brief this function setup the http.host keyword used in the rule |
334 | | * |
335 | | * \param de_ctx Pointer to the Detection Engine Context |
336 | | * \param s Pointer to the Signature to which the current keyword belongs |
337 | | * \param str Should hold an empty string always |
338 | | * |
339 | | * \retval 0 On success |
340 | | */ |
341 | | static int DetectHttpHostRawSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str) |
342 | 3.82k | { |
343 | 3.82k | if (DetectBufferSetActiveList(de_ctx, s, g_http_raw_host_buffer_id) < 0) |
344 | 2 | return -1; |
345 | 3.82k | if (DetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0) |
346 | 105 | return -1; |
347 | 3.72k | return 0; |
348 | 3.82k | } |
349 | | |
350 | | static InspectionBuffer *GetRawData(DetectEngineThreadCtx *det_ctx, |
351 | | const DetectEngineTransforms *transforms, Flow *_f, |
352 | | const uint8_t _flow_flags, void *txv, const int list_id) |
353 | 935 | { |
354 | 935 | InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); |
355 | 935 | if (buffer->inspect == NULL) { |
356 | 935 | htp_tx_t *tx = (htp_tx_t *)txv; |
357 | | |
358 | 935 | const uint8_t *data = NULL; |
359 | 935 | uint32_t data_len = 0; |
360 | | |
361 | 935 | if (tx->parsed_uri == NULL || tx->parsed_uri->hostname == NULL) { |
362 | 694 | if (tx->request_headers == NULL) |
363 | 0 | return NULL; |
364 | | |
365 | 694 | htp_header_t *h = (htp_header_t *)htp_table_get_c(tx->request_headers, |
366 | 694 | "Host"); |
367 | 694 | if (h == NULL || h->value == NULL) |
368 | 634 | return NULL; |
369 | | |
370 | 60 | data = (const uint8_t *)bstr_ptr(h->value); |
371 | 60 | data_len = bstr_len(h->value); |
372 | 241 | } else { |
373 | 241 | data = (const uint8_t *)bstr_ptr(tx->parsed_uri->hostname); |
374 | 241 | data_len = bstr_len(tx->parsed_uri->hostname); |
375 | 241 | } |
376 | | |
377 | 301 | InspectionBufferSetup(det_ctx, list_id, buffer, data, data_len); |
378 | 301 | InspectionBufferApplyTransforms(buffer, transforms); |
379 | 301 | } |
380 | | |
381 | 301 | return buffer; |
382 | 935 | } |
383 | | |
384 | | /************************************Unittests*********************************/ |
385 | | |
386 | | #ifdef UNITTESTS |
387 | | #include "tests/detect-http-host.c" |
388 | | #endif /* UNITTESTS */ |
389 | | |
390 | | /** |
391 | | * @} |
392 | | */ |