1 //--------------------------------------------------------------------------
2 // Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation. You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 //--------------------------------------------------------------------------
18 // http_msg_body.cc author Tom Peters <thopeter@cisco.com>
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "http_msg_body.h"
25
26 #include "decompress/file_olefile.h"
27 #include "file_api/file_flows.h"
28 #include "file_api/file_service.h"
29 #include "pub_sub/http_request_body_event.h"
30
31 #include "http_api.h"
32 #include "http_common.h"
33 #include "http_enum.h"
34 #include "http_js_norm.h"
35 #include "http_msg_header.h"
36 #include "http_msg_request.h"
37 #include "http_test_manager.h"
38 #include "http_uri.h"
39
40 using namespace snort;
41 using namespace HttpCommon;
42 using namespace HttpEnums;
43
HttpMsgBody(const uint8_t * buffer,const uint16_t buf_size,HttpFlowData * session_data_,SourceId source_id_,bool buf_owner,Flow * flow_,const HttpParaList * params_)44 HttpMsgBody::HttpMsgBody(const uint8_t* buffer, const uint16_t buf_size,
45 HttpFlowData* session_data_, SourceId source_id_, bool buf_owner, Flow* flow_,
46 const HttpParaList* params_) :
47 HttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_),
48 body_octets(session_data->body_octets[source_id]),
49 first_body(session_data->body_octets[source_id] == 0)
50 {
51 transaction->set_body(this);
52 get_related_sections();
53 }
54
publish()55 void HttpMsgBody::publish()
56 {
57 if (publish_length <= 0)
58 return;
59
60 const int32_t& pub_depth_remaining = session_data->publish_depth_remaining[source_id];
61 int32_t& publish_octets = session_data->publish_octets[source_id];
62 const bool last_piece = (session_data->cutter[source_id] == nullptr) || tcp_close ||
63 (pub_depth_remaining == 0);
64
65 HttpRequestBodyEvent http_request_body_event(this, publish_octets, last_piece, session_data);
66
67 DataBus::publish(HTTP2_REQUEST_BODY_EVENT_KEY, http_request_body_event, flow);
68 publish_octets += publish_length;
69 #ifdef REG_TEST
70 if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP))
71 {
72 fprintf(HttpTestManager::get_output_file(),
73 "Published %" PRId32 " bytes of request body. last: %s\n", publish_length,
74 (last_piece ? "true" : "false"));
75 fflush(HttpTestManager::get_output_file());
76 }
77 #endif
78 }
79
bookkeeping_regular_flush(uint32_t & partial_detect_length,uint8_t * & partial_detect_buffer,uint32_t & partial_js_detect_length,int32_t detect_length)80 void HttpMsgBody::bookkeeping_regular_flush(uint32_t& partial_detect_length,
81 uint8_t*& partial_detect_buffer, uint32_t& partial_js_detect_length, int32_t detect_length)
82 {
83 params->js_norm_param.js_norm->set_detection_depth(session_data->detect_depth_remaining[source_id]);
84
85 session_data->detect_depth_remaining[source_id] -= detect_length;
86 partial_detect_buffer = nullptr;
87 partial_detect_length = 0;
88 partial_js_detect_length = 0;
89 }
90
clean_partial(uint32_t & partial_inspected_octets,uint32_t & partial_detect_length,uint8_t * & partial_detect_buffer,uint32_t & partial_js_detect_length)91 void HttpMsgBody::clean_partial(uint32_t& partial_inspected_octets, uint32_t& partial_detect_length,
92 uint8_t*& partial_detect_buffer, uint32_t& partial_js_detect_length)
93 {
94 body_octets += msg_text.length();
95 partial_inspected_octets = session_data->partial_flush[source_id] ? msg_text.length() : 0;
96
97 if (session_data->partial_flush[source_id])
98 return;
99
100 if (session_data->detect_depth_remaining[source_id] > 0)
101 {
102 delete[] partial_detect_buffer;
103 const int32_t detect_length =
104 (partial_js_detect_length <= session_data->detect_depth_remaining[source_id]) ?
105 partial_js_detect_length : session_data->detect_depth_remaining[source_id];
106 bookkeeping_regular_flush(partial_detect_length, partial_detect_buffer,
107 partial_js_detect_length, detect_length);
108 }
109 }
110
analyze()111 void HttpMsgBody::analyze()
112 {
113 uint32_t& partial_inspected_octets = session_data->partial_inspected_octets[source_id];
114
115 // When there have been partial inspections we focus on the part of the message we have not
116 // seen before
117 if (partial_inspected_octets > 0)
118 {
119 assert(msg_text.length() >= (int32_t)partial_inspected_octets);
120 // For regular flush, file processing needs to be finalized.
121 // Continue even if there is no new information
122 if ((msg_text.length() == (int32_t)partial_inspected_octets)
123 && session_data->partial_flush[source_id])
124 return;
125
126 msg_text_new.set(msg_text.length() - partial_inspected_octets,
127 msg_text.start() + partial_inspected_octets);
128 }
129 else
130 {
131 // First flush of inspection section - set full file decompress buffer size
132 session_data->file_decomp_buffer_size_remaining[source_id] =
133 FileService::decode_conf.get_decompress_buffer_size();
134 msg_text_new.set(msg_text);
135 }
136
137 int32_t& pub_depth_remaining = session_data->publish_depth_remaining[source_id];
138 if (pub_depth_remaining > 0)
139 {
140 publish_length = (pub_depth_remaining > msg_text_new.length()) ?
141 msg_text_new.length() : pub_depth_remaining;
142 pub_depth_remaining -= publish_length;
143 }
144
145 if (session_data->file_depth_remaining[source_id] > 0 or
146 session_data->detect_depth_remaining[source_id] > 0)
147 {
148 do_utf_decoding(msg_text_new, decoded_body);
149
150 if (session_data->file_depth_remaining[source_id] > 0)
151 {
152 do_file_processing(decoded_body);
153 }
154
155 if (session_data->detect_depth_remaining[source_id] > 0)
156 {
157 do_file_decompression(decoded_body, decompressed_file_body);
158
159 uint32_t& partial_detect_length = session_data->partial_detect_length[source_id];
160 uint8_t*& partial_detect_buffer = session_data->partial_detect_buffer[source_id];
161 uint32_t& partial_js_detect_length = session_data->partial_js_detect_length[source_id];
162
163 if (partial_detect_length > 0)
164 {
165 const int32_t total_length = partial_detect_length +
166 decompressed_file_body.length();
167 assert(total_length <=
168 (int64_t)FileService::decode_conf.get_decompress_buffer_size());
169 uint8_t* const cumulative_buffer = new uint8_t[total_length];
170 memcpy(cumulative_buffer, partial_detect_buffer, partial_detect_length);
171 memcpy(cumulative_buffer + partial_detect_length, decompressed_file_body.start(),
172 decompressed_file_body.length());
173 cumulative_data.set(total_length, cumulative_buffer, true);
174 do_legacy_js_normalization(cumulative_data, js_norm_body);
175 // Partial inspections don't update detect_depth_remaining.
176 // If there is no new data or same data will be sent to detection because
177 // we already reached detect_depth, don't do another detection
178 if ((int32_t)partial_js_detect_length == js_norm_body.length() ||
179 partial_js_detect_length >= session_data->detect_depth_remaining[source_id])
180 {
181 clean_partial(partial_inspected_octets, partial_detect_length,
182 partial_detect_buffer, partial_js_detect_length);
183 return;
184 }
185 }
186 else
187 do_legacy_js_normalization(decompressed_file_body, js_norm_body);
188
189 ++session_data->pdu_idx;
190
191 const int32_t detect_length =
192 (js_norm_body.length() <= session_data->detect_depth_remaining[source_id]) ?
193 js_norm_body.length() : session_data->detect_depth_remaining[source_id];
194
195 detect_data.set(detect_length, js_norm_body.start());
196
197 delete[] partial_detect_buffer;
198
199 if (!session_data->partial_flush[source_id])
200 {
201 bookkeeping_regular_flush(partial_detect_length, partial_detect_buffer,
202 partial_js_detect_length, detect_length);
203 }
204 else
205 {
206 Field* decompressed = (cumulative_data.length() > 0) ?
207 &cumulative_data : &decompressed_file_body;
208 uint8_t* const save_partial = new uint8_t[decompressed->length()];
209 memcpy(save_partial, decompressed->start(), decompressed->length());
210 partial_detect_buffer = save_partial;
211 partial_detect_length = decompressed->length();
212 partial_js_detect_length = js_norm_body.length();
213 }
214
215 set_file_data(const_cast<uint8_t*>(detect_data.start()),
216 (unsigned)detect_data.length());
217 }
218 }
219 body_octets += msg_text.length();
220 partial_inspected_octets = session_data->partial_flush[source_id] ? msg_text.length() : 0;
221 }
222
do_utf_decoding(const Field & input,Field & output)223 void HttpMsgBody::do_utf_decoding(const Field& input, Field& output)
224 {
225 if ((source_id == SRC_CLIENT) || (session_data->utf_state == nullptr) || (input.length() == 0))
226 {
227 output.set(input);
228 return;
229 }
230
231 if (session_data->utf_state->is_utf_encoding_present())
232 {
233 int bytes_copied;
234 bool decoded;
235 uint8_t* buffer = new uint8_t[input.length()];
236 decoded = session_data->utf_state->decode_utf(
237 input.start(), input.length(), buffer, input.length(), &bytes_copied);
238
239 if (!decoded)
240 {
241 delete[] buffer;
242 output.set(input);
243 add_infraction(INF_UTF_NORM_FAIL);
244 create_event(EVENT_UTF_NORM_FAIL);
245 }
246 else if (bytes_copied > 0)
247 {
248 output.set(bytes_copied, buffer, true);
249 }
250 else
251 {
252 delete[] buffer;
253 output.set(input);
254 }
255 }
256
257 else
258 output.set(input);
259 }
260
get_ole_data()261 void HttpMsgBody::get_ole_data()
262 {
263 uint8_t* ole_data_ptr;
264 uint32_t ole_len;
265
266 session_data->fd_state->get_ole_data(ole_data_ptr, ole_len);
267
268 if (ole_data_ptr)
269 {
270 ole_data.set(ole_len, ole_data_ptr, false);
271
272 //Reset the ole data ptr once it is stored in msg body
273 session_data->fd_state->ole_data_reset();
274 }
275 }
276
do_file_decompression(const Field & input,Field & output)277 void HttpMsgBody::do_file_decompression(const Field& input, Field& output)
278 {
279 if ((source_id == SRC_CLIENT) || (session_data->fd_state == nullptr))
280 {
281 output.set(input);
282 return;
283 }
284 const uint32_t buffer_size = session_data->file_decomp_buffer_size_remaining[source_id];
285 uint8_t* buffer = new uint8_t[buffer_size];
286 session_data->fd_alert_context.infractions = transaction->get_infractions(source_id);
287 session_data->fd_alert_context.events = session_data->events[source_id];
288 session_data->fd_state->Next_In = input.start();
289 session_data->fd_state->Avail_In = (uint32_t)input.length();
290 session_data->fd_state->Next_Out = buffer;
291 session_data->fd_state->Avail_Out = buffer_size;
292
293 const fd_status_t status = File_Decomp(session_data->fd_state);
294
295 switch(status)
296 {
297 case File_Decomp_DecompError:
298 File_Decomp_Alert(session_data->fd_state, session_data->fd_state->Error_Event);
299 // Fall through
300 case File_Decomp_NoSig:
301 case File_Decomp_Error:
302 delete[] buffer;
303 output.set(input);
304 File_Decomp_StopFree(session_data->fd_state);
305 session_data->fd_state = nullptr;
306 break;
307 case File_Decomp_BlockOut:
308 add_infraction(INF_FILE_DECOMPR_OVERRUN);
309 create_event(EVENT_FILE_DECOMPR_OVERRUN);
310 // Fall through
311 default:
312 const uint32_t output_length = session_data->fd_state->Next_Out - buffer;
313 output.set(output_length, buffer, true);
314 assert((uint64_t)session_data->file_decomp_buffer_size_remaining[source_id] >=
315 output_length);
316 session_data->file_decomp_buffer_size_remaining[source_id] -= output_length;
317 get_ole_data();
318
319 break;
320 }
321 }
322
fd_event_callback(void * context,int event)323 void HttpMsgBody::fd_event_callback(void* context, int event)
324 {
325 HttpInfractions* infractions = ((HttpFlowData::FdCallbackContext*)context)->infractions;
326 HttpEventGen* events = ((HttpFlowData::FdCallbackContext*)context)->events;
327 switch (event)
328 {
329 case FILE_DECOMP_ERR_SWF_ZLIB_FAILURE:
330 *infractions += INF_SWF_ZLIB_FAILURE;
331 events->create_event(EVENT_SWF_ZLIB_FAILURE);
332 break;
333 case FILE_DECOMP_ERR_SWF_LZMA_FAILURE:
334 *infractions += INF_SWF_LZMA_FAILURE;
335 events->create_event(EVENT_SWF_LZMA_FAILURE);
336 break;
337 case FILE_DECOMP_ERR_PDF_DEFL_FAILURE:
338 *infractions += INF_PDF_DEFL_FAILURE;
339 events->create_event(EVENT_PDF_DEFL_FAILURE);
340 break;
341 case FILE_DECOMP_ERR_PDF_UNSUP_COMP_TYPE:
342 *infractions += INF_PDF_UNSUP_COMP_TYPE;
343 events->create_event(EVENT_PDF_UNSUP_COMP_TYPE);
344 break;
345 case FILE_DECOMP_ERR_PDF_CASC_COMP:
346 *infractions += INF_PDF_CASC_COMP;
347 events->create_event(EVENT_PDF_CASC_COMP);
348 break;
349 case FILE_DECOMP_ERR_PDF_PARSE_FAILURE:
350 *infractions += INF_PDF_PARSE_FAILURE;
351 events->create_event(EVENT_PDF_PARSE_FAILURE);
352 break;
353 default:
354 assert(false);
355 break;
356 }
357 }
358
do_enhanced_js_normalization(const Field & input,Field & output)359 void HttpMsgBody::do_enhanced_js_normalization(const Field& input, Field& output)
360 {
361 if (session_data->js_data_lost_once)
362 return;
363
364 auto infractions = transaction->get_infractions(source_id);
365 auto back = !session_data->partial_flush[source_id];
366 auto http_header = get_header(source_id);
367 auto normalizer = params->js_norm_param.js_norm;
368
369 if (session_data->is_pdu_missed())
370 {
371 *infractions += INF_JS_PDU_MISS;
372 session_data->events[HttpCommon::SRC_SERVER]->create_event(EVENT_JS_PDU_MISS);
373 session_data->js_data_lost_once = true;
374 return;
375 }
376
377 if (http_header and http_header->is_external_js())
378 normalizer->do_external(input, output, infractions, session_data, back);
379 else
380 normalizer->do_inline(input, output, infractions, session_data, back);
381 }
382
do_legacy_js_normalization(const Field & input,Field & output)383 void HttpMsgBody::do_legacy_js_normalization(const Field& input, Field& output)
384 {
385 if (!params->js_norm_param.normalize_javascript || source_id == SRC_CLIENT)
386 {
387 output.set(input);
388 return;
389 }
390
391 params->js_norm_param.js_norm->do_legacy(input, output,
392 transaction->get_infractions(source_id), session_data->events[source_id],
393 params->js_norm_param.max_javascript_whitespaces);
394 }
395
do_file_processing(const Field & file_data)396 void HttpMsgBody::do_file_processing(const Field& file_data)
397 {
398 // Using the trick that cutter is deleted when regular or chunked body is complete
399 Packet* p = DetectionEngine::get_current_packet();
400 const bool front = (body_octets == 0) &&
401 (session_data->partial_inspected_octets[source_id] == 0);
402 const bool back = (session_data->cutter[source_id] == nullptr) || tcp_close;
403
404 FilePosition file_position;
405 if (front && back) file_position = SNORT_FILE_FULL;
406 else if (front) file_position = SNORT_FILE_START;
407 else if (back) file_position = SNORT_FILE_END;
408 else file_position = SNORT_FILE_MIDDLE;
409
410 // Chunked body with nothing but the zero length chunk?
411 if (front && (file_data.length() == 0))
412 {
413 return;
414 }
415
416 if (!session_data->mime_state[source_id])
417 {
418 const int32_t fp_length = (file_data.length() <=
419 session_data->file_depth_remaining[source_id]) ?
420 file_data.length() : session_data->file_depth_remaining[source_id];
421
422 FileFlows* file_flows = FileFlows::get_file_flows(flow);
423 if (!file_flows)
424 return;
425
426 const FileDirection dir = source_id == SRC_SERVER ? FILE_DOWNLOAD : FILE_UPLOAD;
427
428 const uint64_t file_index = get_header(source_id)->get_file_cache_index();
429
430 bool continue_processing_file = file_flows->file_process(p, file_index, file_data.start(),
431 fp_length, session_data->file_octets[source_id], dir,
432 get_header(source_id)->get_multi_file_processing_id(), file_position);
433 if (continue_processing_file)
434 {
435 session_data->file_depth_remaining[source_id] -= fp_length;
436
437 // With the first piece of the file we must provide the filename and URI
438 if (front)
439 {
440 if (request != nullptr)
441 {
442 const uint8_t* filename_buffer;
443 const uint8_t* uri_buffer;
444 uint32_t filename_length;
445 uint32_t uri_length;
446 get_file_info(dir, filename_buffer, filename_length, uri_buffer, uri_length);
447
448 continue_processing_file = file_flows->set_file_name(filename_buffer,
449 filename_length, 0,
450 get_header(source_id)->get_multi_file_processing_id(), uri_buffer,
451 uri_length);
452 }
453 }
454 }
455 if (!continue_processing_file)
456 {
457 // file processing doesn't want any more data
458 session_data->file_depth_remaining[source_id] = 0;
459 }
460 session_data->file_octets[source_id] += fp_length;
461 }
462 else
463 {
464 // FIXIT-M this interface does not convey any indication of end of message body. If the
465 // message body ends in the middle of a MIME message the partial file will not be flushed.
466 session_data->mime_state[source_id]->process_mime_data(p, file_data.start(),
467 file_data.length(), true, SNORT_FILE_POSITION_UNKNOWN);
468 session_data->file_octets[source_id] += file_data.length();
469 }
470 }
471
472 // Parses out the filename and URI associated with this file.
473 // For the filename, if the message has a Content-Disposition header with a filename attribute,
474 // use that. Otherwise use the segment of the URI path after the last '/' but not including the
475 // query or fragment. For the uri, use the request raw uri. If there is no URI or nothing in the
476 // path after the last slash, the filename and uri buffers may be empty. The normalized URI is used.
get_file_info(FileDirection dir,const uint8_t * & filename_buffer,uint32_t & filename_length,const uint8_t * & uri_buffer,uint32_t & uri_length)477 void HttpMsgBody::get_file_info(FileDirection dir, const uint8_t*& filename_buffer,
478 uint32_t& filename_length, const uint8_t*& uri_buffer, uint32_t& uri_length)
479 {
480 filename_buffer = uri_buffer = nullptr;
481 filename_length = uri_length = 0;
482 HttpUri* http_uri = request->get_http_uri();
483
484 // First handle the content-disposition case
485 if (dir == FILE_UPLOAD)
486 {
487 const Field& cd_filename = get_header(source_id)->get_content_disposition_filename();
488 if (cd_filename.length() > 0)
489 {
490 filename_buffer = cd_filename.start();
491 filename_length = cd_filename.length();
492 }
493 }
494
495 if (http_uri)
496 {
497 const Field& uri_field = http_uri->get_norm_classic();
498 if (uri_field.length() > 0)
499 {
500 uri_buffer = uri_field.start();
501 uri_length = uri_field.length();
502 }
503
504 // Don't overwrite the content-disposition filename
505 if (filename_length > 0)
506 return;
507
508 const Field& path = http_uri->get_norm_path();
509 if (path.length() > 0)
510 {
511 int last_slash_index = path.length() - 1;
512 while (last_slash_index >= 0)
513 {
514 if (path.start()[last_slash_index] == '/')
515 break;
516 last_slash_index--;
517 }
518 if (last_slash_index >= 0)
519 {
520 filename_length = (path.length() - (last_slash_index + 1));
521 if (filename_length > 0)
522 filename_buffer = path.start() + last_slash_index + 1;
523 }
524 }
525 }
526 }
527
get_classic_client_body()528 const Field& HttpMsgBody::get_classic_client_body()
529 {
530 return classic_normalize(detect_data, classic_client_body, false, params->uri_param);
531 }
532
get_decomp_vba_data()533 const Field& HttpMsgBody::get_decomp_vba_data()
534 {
535 if (decompressed_vba_data.length() != STAT_NOT_COMPUTE)
536 return decompressed_vba_data;
537
538 if (ole_data.length() <= 0)
539 {
540 decompressed_vba_data.set(STAT_NO_SOURCE);
541 return decompressed_vba_data;
542 }
543
544 uint8_t* buf = nullptr;
545 uint32_t buf_len = 0;
546
547 VBA_DEBUG(vba_data_trace, DEFAULT_TRACE_OPTION_ID, TRACE_INFO_LEVEL, CURRENT_PACKET,
548 "Found OLE file. Sending %d bytes for the processing.\n",
549 ole_data.length());
550
551 oleprocess(ole_data.start(), ole_data.length(), buf, buf_len);
552
553 if (buf && buf_len)
554 decompressed_vba_data.set(buf_len, buf, true);
555 else
556 decompressed_vba_data.set(STAT_NOT_PRESENT);
557
558 return decompressed_vba_data;
559 }
560
get_norm_js_data()561 const Field& HttpMsgBody::get_norm_js_data()
562 {
563 if (norm_js_data.length() != STAT_NOT_COMPUTE)
564 return norm_js_data;
565
566 do_enhanced_js_normalization(decompressed_file_body, norm_js_data);
567
568 if (norm_js_data.length() == STAT_NOT_COMPUTE)
569 norm_js_data.set(STAT_NOT_PRESENT);
570
571 return norm_js_data;
572 }
573
get_publish_length() const574 int32_t HttpMsgBody::get_publish_length() const
575 {
576 return publish_length;
577 }
578
579 #ifdef REG_TEST
580 // Common elements of print_section() for body sections
print_body_section(FILE * output,const char * body_type_str)581 void HttpMsgBody::print_body_section(FILE* output, const char* body_type_str)
582 {
583 HttpMsgSection::print_section_title(output, body_type_str);
584 fprintf(output, "octets seen %" PRIi64 "\n", body_octets);
585 detect_data.print(output, "Detect data");
586 get_classic_buffer(HTTP_BUFFER_CLIENT_BODY, 0, 0).print(output,
587 HttpApi::classic_buffer_names[HTTP_BUFFER_CLIENT_BODY-1]);
588 get_classic_buffer(HTTP_BUFFER_RAW_BODY, 0, 0).print(output,
589 HttpApi::classic_buffer_names[HTTP_BUFFER_RAW_BODY-1]);
590
591 HttpMsgSection::print_section_wrapup(output);
592 }
593 #endif
594
595