/src/suricata7/src/detect-http-client-body.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2021 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_client_body 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-state.h" |
42 | | #include "detect-engine-prefilter.h" |
43 | | #include "detect-engine-content-inspection.h" |
44 | | #include "detect-content.h" |
45 | | #include "detect-pcre.h" |
46 | | // PrefilterMpmFiledata |
47 | | #include "detect-file-data.h" |
48 | | |
49 | | #include "flow.h" |
50 | | #include "flow-var.h" |
51 | | #include "flow-util.h" |
52 | | |
53 | | #include "util-debug.h" |
54 | | #include "util-unittest.h" |
55 | | #include "util-unittest-helper.h" |
56 | | #include "util-spm.h" |
57 | | |
58 | | #include "app-layer.h" |
59 | | #include "app-layer-parser.h" |
60 | | #include "app-layer-htp.h" |
61 | | #include "detect-http-client-body.h" |
62 | | #include "stream-tcp.h" |
63 | | #include "util-profiling.h" |
64 | | |
65 | | static int DetectHttpClientBodySetup(DetectEngineCtx *, Signature *, const char *); |
66 | | static int DetectHttpClientBodySetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str); |
67 | | #ifdef UNITTESTS |
68 | | static void DetectHttpClientBodyRegisterTests(void); |
69 | | #endif |
70 | | static void DetectHttpClientBodySetupCallback(const DetectEngineCtx *de_ctx, |
71 | | Signature *s); |
72 | | static int g_http_client_body_buffer_id = 0; |
73 | | |
74 | | static uint8_t DetectEngineInspectBufferHttpBody(DetectEngineCtx *de_ctx, |
75 | | DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine, |
76 | | const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); |
77 | | |
78 | | static int PrefilterMpmHttpRequestBodyRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, |
79 | | MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id); |
80 | | |
81 | | /** |
82 | | * \brief Registers the keyword handlers for the "http_client_body" keyword. |
83 | | */ |
84 | | void DetectHttpClientBodyRegister(void) |
85 | 73 | { |
86 | | /* http_client_body content modifier */ |
87 | 73 | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].name = "http_client_body"; |
88 | 73 | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].desc = "content modifier to match only on HTTP request-body"; |
89 | 73 | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].url = "/rules/http-keywords.html#http-client-body"; |
90 | 73 | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].Setup = DetectHttpClientBodySetup; |
91 | | #ifdef UNITTESTS |
92 | | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].RegisterTests = DetectHttpClientBodyRegisterTests; |
93 | | #endif |
94 | 73 | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].flags |= SIGMATCH_NOOPT ; |
95 | 73 | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].flags |= SIGMATCH_INFO_CONTENT_MODIFIER; |
96 | 73 | sigmatch_table[DETECT_AL_HTTP_CLIENT_BODY].alternative = DETECT_HTTP_REQUEST_BODY; |
97 | | |
98 | | /* http.request_body sticky buffer */ |
99 | 73 | sigmatch_table[DETECT_HTTP_REQUEST_BODY].name = "http.request_body"; |
100 | 73 | sigmatch_table[DETECT_HTTP_REQUEST_BODY].desc = "sticky buffer to match the HTTP request body buffer"; |
101 | 73 | sigmatch_table[DETECT_HTTP_REQUEST_BODY].url = "/rules/http-keywords.html#http-client-body"; |
102 | 73 | sigmatch_table[DETECT_HTTP_REQUEST_BODY].Setup = DetectHttpClientBodySetupSticky; |
103 | 73 | sigmatch_table[DETECT_HTTP_REQUEST_BODY].flags |= SIGMATCH_NOOPT; |
104 | 73 | sigmatch_table[DETECT_HTTP_REQUEST_BODY].flags |= SIGMATCH_INFO_STICKY_BUFFER; |
105 | | |
106 | 73 | DetectAppLayerInspectEngineRegister2("http_client_body", ALPROTO_HTTP1, SIG_FLAG_TOSERVER, |
107 | 73 | HTP_REQUEST_BODY, DetectEngineInspectBufferHttpBody, NULL); |
108 | | |
109 | 73 | DetectAppLayerMpmRegister2("http_client_body", SIG_FLAG_TOSERVER, 2, |
110 | 73 | PrefilterMpmHttpRequestBodyRegister, NULL, ALPROTO_HTTP1, HTP_REQUEST_BODY); |
111 | | |
112 | 73 | DetectAppLayerInspectEngineRegister2("http_client_body", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, |
113 | 73 | HTTP2StateDataClient, DetectEngineInspectFiledata, NULL); |
114 | 73 | DetectAppLayerMpmRegister2("http_client_body", SIG_FLAG_TOSERVER, 2, |
115 | 73 | PrefilterMpmFiledataRegister, NULL, ALPROTO_HTTP2, HTTP2StateDataClient); |
116 | | |
117 | 73 | DetectBufferTypeSetDescriptionByName("http_client_body", |
118 | 73 | "http request body"); |
119 | | |
120 | 73 | DetectBufferTypeRegisterSetupCallback("http_client_body", |
121 | 73 | DetectHttpClientBodySetupCallback); |
122 | | |
123 | 73 | g_http_client_body_buffer_id = DetectBufferTypeGetByName("http_client_body"); |
124 | 73 | } |
125 | | |
126 | | static void DetectHttpClientBodySetupCallback(const DetectEngineCtx *de_ctx, |
127 | | Signature *s) |
128 | 23.6k | { |
129 | 23.6k | SCLogDebug("callback invoked by %u", s->id); |
130 | 23.6k | AppLayerHtpEnableRequestBodyCallback(); |
131 | | |
132 | | /* client body needs to be inspected in sync with stream if possible */ |
133 | 23.6k | s->init_data->init_flags |= SIG_FLAG_INIT_NEED_FLUSH; |
134 | 23.6k | } |
135 | | |
136 | | /** |
137 | | * \brief The setup function for the http_client_body keyword for a signature. |
138 | | * |
139 | | * \param de_ctx Pointer to the detection engine context. |
140 | | * \param s Pointer to signature for the current Signature being parsed |
141 | | * from the rules. |
142 | | * \param m Pointer to the head of the SigMatchs for the current rule |
143 | | * being parsed. |
144 | | * \param arg Pointer to the string holding the keyword value. |
145 | | * |
146 | | * \retval 0 On success |
147 | | * \retval -1 On failure |
148 | | */ |
149 | | int DetectHttpClientBodySetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) |
150 | 7.21k | { |
151 | 7.21k | return DetectEngineContentModifierBufferSetup(de_ctx, s, arg, DETECT_AL_HTTP_CLIENT_BODY, |
152 | 7.21k | g_http_client_body_buffer_id, ALPROTO_HTTP1); |
153 | 7.21k | } |
154 | | |
155 | | /** |
156 | | * \brief this function setup the http.request_body keyword used in the rule |
157 | | * |
158 | | * \param de_ctx Pointer to the Detection Engine Context |
159 | | * \param s Pointer to the Signature to which the current keyword belongs |
160 | | * \param str Should hold an empty string always |
161 | | * |
162 | | * \retval 0 On success |
163 | | */ |
164 | | static int DetectHttpClientBodySetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str) |
165 | 2.31k | { |
166 | 2.31k | if (DetectBufferSetActiveList(de_ctx, s, g_http_client_body_buffer_id) < 0) |
167 | 32 | return -1; |
168 | 2.28k | if (DetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0) |
169 | 18 | return -1; |
170 | 2.26k | return 0; |
171 | 2.28k | } |
172 | | |
173 | | static inline HtpBody *GetRequestBody(htp_tx_t *tx) |
174 | 864 | { |
175 | 864 | HtpTxUserData *htud = (HtpTxUserData *)htp_tx_get_user_data(tx); |
176 | 864 | if (htud == NULL) { |
177 | 0 | SCLogDebug("no htud"); |
178 | 0 | return NULL; |
179 | 0 | } |
180 | | |
181 | 864 | return &htud->request_body; |
182 | 864 | } |
183 | | |
184 | | typedef struct PrefilterMpmHttpRequestBody { |
185 | | int list_id; |
186 | | int base_list_id; |
187 | | const MpmCtx *mpm_ctx; |
188 | | const DetectEngineTransforms *transforms; |
189 | | } PrefilterMpmHttpRequestBody; |
190 | | |
191 | | static void PrefilterMpmHttpRequestBodyFree(void *ptr) |
192 | 2.20k | { |
193 | 2.20k | SCFree(ptr); |
194 | 2.20k | } |
195 | | |
196 | | static inline InspectionBuffer *HttpRequestBodyXformsGetDataCallback(DetectEngineThreadCtx *det_ctx, |
197 | | const DetectEngineTransforms *transforms, const int list_id, InspectionBuffer *base_buffer) |
198 | 68 | { |
199 | 68 | InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); |
200 | 68 | if (buffer->inspect != NULL) |
201 | 23 | return buffer; |
202 | | |
203 | 45 | InspectionBufferSetup(det_ctx, list_id, buffer, base_buffer->inspect, base_buffer->inspect_len); |
204 | 45 | buffer->inspect_offset = base_buffer->inspect_offset; |
205 | 45 | InspectionBufferApplyTransforms(buffer, transforms); |
206 | 45 | SCLogDebug("xformed buffer %p size %u", buffer, buffer->inspect_len); |
207 | 45 | SCReturnPtr(buffer, "InspectionBuffer"); |
208 | 68 | } |
209 | | |
210 | | static InspectionBuffer *HttpRequestBodyGetDataCallback(DetectEngineThreadCtx *det_ctx, |
211 | | const DetectEngineTransforms *transforms, Flow *f, const uint8_t flow_flags, void *txv, |
212 | | const int list_id, const int base_id) |
213 | 6.04k | { |
214 | 6.04k | SCEnter(); |
215 | | |
216 | 6.04k | InspectionBuffer *buffer = InspectionBufferGet(det_ctx, base_id); |
217 | 6.04k | if (base_id != list_id && buffer->inspect != NULL) |
218 | 25 | return HttpRequestBodyXformsGetDataCallback(det_ctx, transforms, list_id, buffer); |
219 | 6.02k | else if (buffer->inspect != NULL) |
220 | 45 | return buffer; |
221 | | |
222 | 5.97k | htp_tx_t *tx = txv; |
223 | 5.97k | HtpState *htp_state = f->alstate; |
224 | 5.97k | const uint8_t flags = flow_flags; |
225 | | |
226 | 5.97k | HtpBody *body = GetRequestBody(tx); |
227 | 5.97k | if (body == NULL) { |
228 | 0 | return NULL; |
229 | 0 | } |
230 | | |
231 | | /* no new data */ |
232 | 5.97k | if (body->body_inspected == body->content_len_so_far) { |
233 | 2.50k | SCLogDebug("no new data"); |
234 | 2.50k | return NULL; |
235 | 2.50k | } |
236 | | |
237 | 3.47k | HtpBodyChunk *cur = body->first; |
238 | 3.47k | if (cur == NULL) { |
239 | 0 | SCLogDebug("No http chunks to inspect for this transaction"); |
240 | 0 | return NULL; |
241 | 0 | } |
242 | | |
243 | 3.47k | SCLogDebug("request.body_limit %u request_body.content_len_so_far %" PRIu64 |
244 | 3.47k | ", request.inspect_min_size %" PRIu32 ", EOF %s, progress > body? %s", |
245 | 3.47k | htp_state->cfg->request.body_limit, body->content_len_so_far, |
246 | 3.47k | htp_state->cfg->request.inspect_min_size, flags & STREAM_EOF ? "true" : "false", |
247 | 3.47k | (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, tx, flags) > |
248 | 3.47k | HTP_REQUEST_BODY) |
249 | 3.47k | ? "true" |
250 | 3.47k | : "false"); |
251 | | |
252 | 3.47k | if (!htp_state->cfg->http_body_inline) { |
253 | | /* inspect the body if the transfer is complete or we have hit |
254 | | * our body size limit */ |
255 | 3.47k | if ((htp_state->cfg->request.body_limit == 0 || |
256 | 3.47k | body->content_len_so_far < htp_state->cfg->request.body_limit) && |
257 | 3.29k | body->content_len_so_far < htp_state->cfg->request.inspect_min_size && |
258 | 3.29k | !(AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, tx, flags) > |
259 | 3.29k | HTP_REQUEST_BODY) && |
260 | 951 | !(flags & STREAM_EOF)) { |
261 | 950 | SCLogDebug("we still haven't seen the entire request body. " |
262 | 950 | "Let's defer body inspection till we see the " |
263 | 950 | "entire body."); |
264 | 950 | return NULL; |
265 | 950 | } |
266 | 3.47k | } |
267 | | |
268 | | /* get the inspect buffer |
269 | | * |
270 | | * make sure that we have at least the configured inspect_win size. |
271 | | * If we have more, take at least 1/4 of the inspect win size before |
272 | | * the new data. |
273 | | */ |
274 | 2.52k | uint64_t offset = 0; |
275 | 2.52k | if (body->body_inspected > htp_state->cfg->request.inspect_min_size) { |
276 | 0 | BUG_ON(body->content_len_so_far < body->body_inspected); |
277 | 0 | uint64_t inspect_win = body->content_len_so_far - body->body_inspected; |
278 | 0 | SCLogDebug("inspect_win %"PRIu64, inspect_win); |
279 | 0 | if (inspect_win < htp_state->cfg->request.inspect_window) { |
280 | 0 | uint64_t inspect_short = htp_state->cfg->request.inspect_window - inspect_win; |
281 | 0 | if (body->body_inspected < inspect_short) |
282 | 0 | offset = 0; |
283 | 0 | else |
284 | 0 | offset = body->body_inspected - inspect_short; |
285 | 0 | } else { |
286 | 0 | offset = body->body_inspected - (htp_state->cfg->request.inspect_window / 4); |
287 | 0 | } |
288 | 0 | } |
289 | | |
290 | 2.52k | const uint8_t *data; |
291 | 2.52k | uint32_t data_len; |
292 | | |
293 | 2.52k | StreamingBufferGetDataAtOffset(body->sb, |
294 | 2.52k | &data, &data_len, offset); |
295 | 2.52k | InspectionBufferSetup(det_ctx, base_id, buffer, data, data_len); |
296 | 2.52k | buffer->inspect_offset = offset; |
297 | 2.52k | body->body_inspected = body->content_len_so_far; |
298 | 2.52k | SCLogDebug("body->body_inspected now: %" PRIu64, body->body_inspected); |
299 | | |
300 | 2.52k | if (base_id != list_id) { |
301 | 43 | buffer = HttpRequestBodyXformsGetDataCallback(det_ctx, transforms, list_id, buffer); |
302 | 43 | } |
303 | 2.52k | SCReturnPtr(buffer, "InspectionBuffer"); |
304 | 2.52k | } |
305 | | |
306 | | static uint8_t DetectEngineInspectBufferHttpBody(DetectEngineCtx *de_ctx, |
307 | | DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine, |
308 | | const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) |
309 | 562 | { |
310 | 562 | bool eof = |
311 | 562 | (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > engine->progress); |
312 | 562 | const InspectionBuffer *buffer = HttpRequestBodyGetDataCallback( |
313 | 562 | det_ctx, engine->v2.transforms, f, flags, txv, engine->sm_list, engine->sm_list_base); |
314 | 562 | if (buffer == NULL || buffer->inspect == NULL) { |
315 | 319 | return eof ? DETECT_ENGINE_INSPECT_SIG_CANT_MATCH : DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
316 | 319 | } |
317 | | |
318 | 243 | const uint32_t data_len = buffer->inspect_len; |
319 | 243 | const uint8_t *data = buffer->inspect; |
320 | 243 | const uint64_t offset = buffer->inspect_offset; |
321 | | |
322 | 243 | uint8_t ci_flags = eof ? DETECT_CI_FLAGS_END : 0; |
323 | 243 | ci_flags |= (offset == 0 ? DETECT_CI_FLAGS_START : 0); |
324 | 243 | ci_flags |= buffer->flags; |
325 | | |
326 | 243 | det_ctx->discontinue_matching = 0; |
327 | 243 | det_ctx->buffer_offset = 0; |
328 | 243 | det_ctx->inspection_recursion_counter = 0; |
329 | | |
330 | | /* Inspect all the uricontents fetched on each |
331 | | * transaction at the app layer */ |
332 | 243 | int r = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, (uint8_t *)data, |
333 | 243 | data_len, offset, ci_flags, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); |
334 | 243 | if (r == 1) { |
335 | 233 | return DETECT_ENGINE_INSPECT_SIG_MATCH; |
336 | 233 | } |
337 | | |
338 | 10 | if (flags & STREAM_TOSERVER) { |
339 | 10 | if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, txv, flags) > |
340 | 10 | HTP_REQUEST_BODY) |
341 | 5 | return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; |
342 | 10 | } else { |
343 | 0 | if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, txv, flags) > |
344 | 0 | HTP_RESPONSE_BODY) |
345 | 0 | return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; |
346 | 0 | } |
347 | 5 | return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
348 | 10 | } |
349 | | |
350 | | /** \brief HTTP Request body callback |
351 | | * |
352 | | * \param det_ctx detection engine thread ctx |
353 | | * \param pectx inspection context |
354 | | * \param p packet to inspect |
355 | | * \param f flow to inspect |
356 | | * \param txv tx to inspect |
357 | | * \param idx transaction id |
358 | | * \param flags STREAM_* flags including direction |
359 | | */ |
360 | | static void PrefilterTxHttpRequestBody(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p, |
361 | | Flow *f, void *txv, const uint64_t idx, const AppLayerTxData *_txd, const uint8_t flags) |
362 | 956 | { |
363 | 956 | SCEnter(); |
364 | | |
365 | 956 | const PrefilterMpmHttpRequestBody *ctx = (const PrefilterMpmHttpRequestBody *)pectx; |
366 | 956 | const MpmCtx *mpm_ctx = ctx->mpm_ctx; |
367 | 956 | const int list_id = ctx->list_id; |
368 | | |
369 | 956 | InspectionBuffer *buffer = HttpRequestBodyGetDataCallback( |
370 | 956 | det_ctx, ctx->transforms, f, flags, txv, list_id, ctx->base_list_id); |
371 | 956 | if (buffer == NULL) |
372 | 657 | return; |
373 | | |
374 | 299 | if (buffer->inspect_len >= mpm_ctx->minlen) { |
375 | 240 | (void)mpm_table[mpm_ctx->mpm_type].Search( |
376 | 240 | mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, buffer->inspect, buffer->inspect_len); |
377 | 240 | PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len); |
378 | 240 | } |
379 | 299 | } |
380 | | |
381 | | static int PrefilterMpmHttpRequestBodyRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, |
382 | | MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id) |
383 | 1.75k | { |
384 | 1.75k | PrefilterMpmHttpRequestBody *pectx = SCCalloc(1, sizeof(*pectx)); |
385 | 1.75k | if (pectx == NULL) |
386 | 0 | return -1; |
387 | 1.75k | pectx->list_id = list_id; |
388 | 1.75k | pectx->base_list_id = mpm_reg->sm_list_base; |
389 | 1.75k | SCLogDebug("list_id %d base_list_id %d", list_id, pectx->base_list_id); |
390 | 1.75k | pectx->mpm_ctx = mpm_ctx; |
391 | 1.75k | pectx->transforms = &mpm_reg->transforms; |
392 | | |
393 | 1.75k | return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxHttpRequestBody, mpm_reg->app_v2.alproto, |
394 | 1.75k | mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmHttpRequestBodyFree, |
395 | 1.75k | mpm_reg->pname); |
396 | 1.75k | } |
397 | | |
398 | | #ifdef UNITTESTS |
399 | | #include "detect-engine-alert.h" |
400 | | #include "tests/detect-http-client-body.c" |
401 | | #endif /* UNITTESTS */ |
402 | | |
403 | | /** |
404 | | * @} |
405 | | */ |