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