From 2a858d252e56267d3cf2b8d850fb4ae6f24ec5a9 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Mon, 15 Jan 2024 21:06:24 +0100 Subject: [PATCH] MIME: Cap nested MIME analysis depth to 100 OSS-Fuzz managed to produce a MIME multipart message construction with thousands of nested entities (or that's what Zeek makes out of it anyhow). Prevent such deep analysis by capping at a nesting depth of 100, preventing unnecessary resource usage. A new weird named exceeded_mime_max_depth is reported when this limit is reached. This change reduces the runtime of the OSS-Fuzz reproducer from ~45 seconds to ~2.5 seconds. The test PCAP was produced from a Python script using the email package and sending the rendered version via POST to a HTTP server. Closes #208 --- NEWS | 4 ++++ scripts/base/init-bare.zeek | 10 +++++++++ src/analyzer/protocol/mime/CMakeLists.txt | 1 + src/analyzer/protocol/mime/MIME.cc | 19 +++++++++++++++++- src/analyzer/protocol/mime/MIME.h | 2 ++ src/analyzer/protocol/mime/consts.bif | 1 + .../canonified_loaded_scripts.log | 1 + .../canonified_loaded_scripts.log | 1 + testing/btest/Baseline/plugins.hooks/output | 6 ++++++ .../http.log | 12 +++++++++++ .../weird.log | 11 ++++++++++ .../btest/Traces/http/deeply-nested-mime.pcap | Bin 0 -> 45183 bytes .../protocols/http/deeply-nested-mime.zeek | 7 +++++++ 13 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/analyzer/protocol/mime/consts.bif create mode 100644 testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/http.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/weird.log create mode 100644 testing/btest/Traces/http/deeply-nested-mime.pcap create mode 100644 testing/btest/scripts/base/protocols/http/deeply-nested-mime.zeek diff --git a/NEWS b/NEWS index 330270c755..351cd9bc79 100644 --- a/NEWS +++ b/NEWS @@ -184,6 +184,10 @@ Changed Functionality two encapsulation layers. Two layers are already easily reached in AWS GLB environments. +- Nested MIME message analysis is now capped at a maximum depth of 100 to prevent + unbounded MIME message nesting. This limit is configurable with ``MIME::max_depth``. + A new weird named ``exceeded_mime_max_depth`` is reported when reached. + Removed Functionality --------------------- diff --git a/scripts/base/init-bare.zeek b/scripts/base/init-bare.zeek index 95cc63ec78..ef0dba7ab4 100644 --- a/scripts/base/init-bare.zeek +++ b/scripts/base/init-bare.zeek @@ -2780,6 +2780,16 @@ export { } # end export +module MIME; +export { + ## Stop analysis of nested multipart MIME entities if this depth is + ## reached. Setting this value to 0 removes the limit. + const max_depth = 100 &redef; + +} # end export + + + module MOUNT3; export { diff --git a/src/analyzer/protocol/mime/CMakeLists.txt b/src/analyzer/protocol/mime/CMakeLists.txt index 774766d2d8..86cccb9b2e 100644 --- a/src/analyzer/protocol/mime/CMakeLists.txt +++ b/src/analyzer/protocol/mime/CMakeLists.txt @@ -10,4 +10,5 @@ zeek_add_plugin( MIME.cc Plugin.cc BIFS + consts.bif events.bif) diff --git a/src/analyzer/protocol/mime/MIME.cc b/src/analyzer/protocol/mime/MIME.cc index e375529565..e0773ee159 100644 --- a/src/analyzer/protocol/mime/MIME.cc +++ b/src/analyzer/protocol/mime/MIME.cc @@ -7,6 +7,7 @@ #include "zeek/Base64.h" #include "zeek/NetVar.h" #include "zeek/Reporter.h" +#include "zeek/analyzer/protocol/mime/consts.bif.h" #include "zeek/analyzer/protocol/mime/events.bif.h" #include "zeek/digest.h" #include "zeek/file_analysis/Manager.h" @@ -450,8 +451,10 @@ MIME_Entity::MIME_Entity(MIME_Message* output_message, MIME_Entity* parent_entit init(); parent = parent_entity; message = output_message; - if ( parent ) + if ( parent ) { content_encoding = parent->ContentTransferEncoding(); + depth = parent->Depth() + 1; + } want_all_headers = (bool)mime_all_headers; } @@ -479,6 +482,7 @@ void MIME_Entity::init() { base64_decoder = nullptr; + depth = 0; data_buf_length = 0; data_buf_data = nullptr; data_buf_offset = -1; @@ -1070,6 +1074,19 @@ void MIME_Entity::SubmitAllHeaders() { message->SubmitAllHeaders(headers); } void MIME_Entity::BeginChildEntity() { ASSERT(current_child_entity == nullptr); + + // If the maximum depth for analysis is reached, don't create a new + // child entity. Instead, its header/body will be delivered to the + // current entity. + if ( zeek::BifConst::MIME::max_depth > 0 && Depth() >= zeek::BifConst::MIME::max_depth ) { + if ( message->GetAnalyzer() ) { + const char* addl = zeek::util::fmt("%" PRIu64, Depth()); + message->GetAnalyzer()->Weird("exceeded_mime_max_depth", addl); + } + + return; + } + current_child_entity = NewChildEntity(); message->BeginEntity(current_child_entity); } diff --git a/src/analyzer/protocol/mime/MIME.h b/src/analyzer/protocol/mime/MIME.h index 2ecdae6122..9b8ea9a7b2 100644 --- a/src/analyzer/protocol/mime/MIME.h +++ b/src/analyzer/protocol/mime/MIME.h @@ -101,6 +101,7 @@ public: virtual void EndOfData(); MIME_Entity* Parent() const { return parent; } + zeek_uint_t Depth() const { return depth; }; int MIMEContentType() const { return content_type; } const StringValPtr& GetContentType() const { return content_type_str; } const StringValPtr& GetContentSubType() const { return content_subtype_str; } @@ -172,6 +173,7 @@ protected: zeek::detail::Base64Converter* base64_decoder; + zeek_uint_t depth; int data_buf_length; char* data_buf_data; int data_buf_offset; diff --git a/src/analyzer/protocol/mime/consts.bif b/src/analyzer/protocol/mime/consts.bif new file mode 100644 index 0000000000..c8d7317c2e --- /dev/null +++ b/src/analyzer/protocol/mime/consts.bif @@ -0,0 +1 @@ +const MIME::max_depth: count; diff --git a/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log index 37b0ea5102..13a58ed17e 100644 --- a/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.bare-load-baseline/canonified_loaded_scripts.log @@ -176,6 +176,7 @@ scripts/base/init-frameworks-and-bifs.zeek build/scripts/base/bif/plugins/Zeek_KRB.events.bif.zeek build/scripts/base/bif/plugins/Zeek_Login.events.bif.zeek build/scripts/base/bif/plugins/Zeek_Login.functions.bif.zeek + build/scripts/base/bif/plugins/Zeek_MIME.consts.bif.zeek build/scripts/base/bif/plugins/Zeek_MIME.events.bif.zeek build/scripts/base/bif/plugins/Zeek_Modbus.events.bif.zeek build/scripts/base/bif/plugins/Zeek_MQTT.types.bif.zeek diff --git a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log index 18aacdd010..43d87b3515 100644 --- a/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log +++ b/testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log @@ -176,6 +176,7 @@ scripts/base/init-frameworks-and-bifs.zeek build/scripts/base/bif/plugins/Zeek_KRB.events.bif.zeek build/scripts/base/bif/plugins/Zeek_Login.events.bif.zeek build/scripts/base/bif/plugins/Zeek_Login.functions.bif.zeek + build/scripts/base/bif/plugins/Zeek_MIME.consts.bif.zeek build/scripts/base/bif/plugins/Zeek_MIME.events.bif.zeek build/scripts/base/bif/plugins/Zeek_Modbus.events.bif.zeek build/scripts/base/bif/plugins/Zeek_MQTT.types.bif.zeek diff --git a/testing/btest/Baseline/plugins.hooks/output b/testing/btest/Baseline/plugins.hooks/output index f40858f96c..386e891e7a 100644 --- a/testing/btest/Baseline/plugins.hooks/output +++ b/testing/btest/Baseline/plugins.hooks/output @@ -385,6 +385,7 @@ 0.000000 MetaHookPost LoadFile(0, ./Zeek_KRB.types.bif.zeek, <...>/Zeek_KRB.types.bif.zeek) -> -1 0.000000 MetaHookPost LoadFile(0, ./Zeek_Login.events.bif.zeek, <...>/Zeek_Login.events.bif.zeek) -> -1 0.000000 MetaHookPost LoadFile(0, ./Zeek_Login.functions.bif.zeek, <...>/Zeek_Login.functions.bif.zeek) -> -1 +0.000000 MetaHookPost LoadFile(0, ./Zeek_MIME.consts.bif.zeek, <...>/Zeek_MIME.consts.bif.zeek) -> -1 0.000000 MetaHookPost LoadFile(0, ./Zeek_MIME.events.bif.zeek, <...>/Zeek_MIME.events.bif.zeek) -> -1 0.000000 MetaHookPost LoadFile(0, ./Zeek_MQTT.events.bif.zeek, <...>/Zeek_MQTT.events.bif.zeek) -> -1 0.000000 MetaHookPost LoadFile(0, ./Zeek_MQTT.types.bif.zeek, <...>/Zeek_MQTT.types.bif.zeek) -> -1 @@ -673,6 +674,7 @@ 0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_KRB.types.bif.zeek, <...>/Zeek_KRB.types.bif.zeek) -> (-1, ) 0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_Login.events.bif.zeek, <...>/Zeek_Login.events.bif.zeek) -> (-1, ) 0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_Login.functions.bif.zeek, <...>/Zeek_Login.functions.bif.zeek) -> (-1, ) +0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_MIME.consts.bif.zeek, <...>/Zeek_MIME.consts.bif.zeek) -> (-1, ) 0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_MIME.events.bif.zeek, <...>/Zeek_MIME.events.bif.zeek) -> (-1, ) 0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_MQTT.events.bif.zeek, <...>/Zeek_MQTT.events.bif.zeek) -> (-1, ) 0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_MQTT.types.bif.zeek, <...>/Zeek_MQTT.types.bif.zeek) -> (-1, ) @@ -1309,6 +1311,7 @@ 0.000000 MetaHookPre LoadFile(0, ./Zeek_KRB.types.bif.zeek, <...>/Zeek_KRB.types.bif.zeek) 0.000000 MetaHookPre LoadFile(0, ./Zeek_Login.events.bif.zeek, <...>/Zeek_Login.events.bif.zeek) 0.000000 MetaHookPre LoadFile(0, ./Zeek_Login.functions.bif.zeek, <...>/Zeek_Login.functions.bif.zeek) +0.000000 MetaHookPre LoadFile(0, ./Zeek_MIME.consts.bif.zeek, <...>/Zeek_MIME.consts.bif.zeek) 0.000000 MetaHookPre LoadFile(0, ./Zeek_MIME.events.bif.zeek, <...>/Zeek_MIME.events.bif.zeek) 0.000000 MetaHookPre LoadFile(0, ./Zeek_MQTT.events.bif.zeek, <...>/Zeek_MQTT.events.bif.zeek) 0.000000 MetaHookPre LoadFile(0, ./Zeek_MQTT.types.bif.zeek, <...>/Zeek_MQTT.types.bif.zeek) @@ -1597,6 +1600,7 @@ 0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_KRB.types.bif.zeek, <...>/Zeek_KRB.types.bif.zeek) 0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_Login.events.bif.zeek, <...>/Zeek_Login.events.bif.zeek) 0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_Login.functions.bif.zeek, <...>/Zeek_Login.functions.bif.zeek) +0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_MIME.consts.bif.zeek, <...>/Zeek_MIME.consts.bif.zeek) 0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_MIME.events.bif.zeek, <...>/Zeek_MIME.events.bif.zeek) 0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_MQTT.events.bif.zeek, <...>/Zeek_MQTT.events.bif.zeek) 0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_MQTT.types.bif.zeek, <...>/Zeek_MQTT.types.bif.zeek) @@ -2232,6 +2236,7 @@ 0.000000 | HookLoadFile ./Zeek_KRB.types.bif.zeek <...>/Zeek_KRB.types.bif.zeek 0.000000 | HookLoadFile ./Zeek_Login.events.bif.zeek <...>/Zeek_Login.events.bif.zeek 0.000000 | HookLoadFile ./Zeek_Login.functions.bif.zeek <...>/Zeek_Login.functions.bif.zeek +0.000000 | HookLoadFile ./Zeek_MIME.consts.bif.zeek <...>/Zeek_MIME.consts.bif.zeek 0.000000 | HookLoadFile ./Zeek_MIME.events.bif.zeek <...>/Zeek_MIME.events.bif.zeek 0.000000 | HookLoadFile ./Zeek_MQTT.events.bif.zeek <...>/Zeek_MQTT.events.bif.zeek 0.000000 | HookLoadFile ./Zeek_MQTT.types.bif.zeek <...>/Zeek_MQTT.types.bif.zeek @@ -2520,6 +2525,7 @@ 0.000000 | HookLoadFileExtended ./Zeek_KRB.types.bif.zeek <...>/Zeek_KRB.types.bif.zeek 0.000000 | HookLoadFileExtended ./Zeek_Login.events.bif.zeek <...>/Zeek_Login.events.bif.zeek 0.000000 | HookLoadFileExtended ./Zeek_Login.functions.bif.zeek <...>/Zeek_Login.functions.bif.zeek +0.000000 | HookLoadFileExtended ./Zeek_MIME.consts.bif.zeek <...>/Zeek_MIME.consts.bif.zeek 0.000000 | HookLoadFileExtended ./Zeek_MIME.events.bif.zeek <...>/Zeek_MIME.events.bif.zeek 0.000000 | HookLoadFileExtended ./Zeek_MQTT.events.bif.zeek <...>/Zeek_MQTT.events.bif.zeek 0.000000 | HookLoadFileExtended ./Zeek_MQTT.types.bif.zeek <...>/Zeek_MQTT.types.bif.zeek diff --git a/testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/http.log b/testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/http.log new file mode 100644 index 0000000000..f4096b6a73 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/http.log @@ -0,0 +1,12 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path http +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p trans_depth method host uri referrer version user_agent origin request_body_len response_body_len status_code status_msg info_code info_msg tags username password proxied orig_fuids orig_filenames orig_mime_types resp_fuids resp_filenames resp_mime_types +#types time string addr port addr port count string string string string string string string count count count string count string set[enum] string string set[string] vector[string] vector[string] vector[string] vector[string] vector[string] vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 48724 127.0.0.1 8080 1 POST localhost:8080 / - 1.0 python-requests/2.25.1 - 0 497 501 Unsupported method ('POST') - - (empty) - - - Fx3nQj3PL606RGh4A5,Fdjahd1XxlaUKC7vn8,Fe52j84XySnHI9mUz3,FxA6cG3PA85BxWwwue,FXB5842rxVtgRiI2Vb,Fw9fsX12UMGC0mGdXa,Fl4MFh2QO56Q341Pta,FYZdwQ25lK4xMv9Qob,FTr1zV3F6Fwdhnw5Ik,F6LDXlarWo5lMUQOi,F7m83Od9JOgipOzrc,FVzPLi3cocOyPUvib5,FH7s3jYnZ1R4gqO1g,FrlqlF3HqNAWohppk1,FobeqY2mIbUTMYzkva - text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain,text/plain FWhx6m3AbtxIJtthb - text/html +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 48724 127.0.0.1 8080 2 - - - - - - - 39686 0 - - - - (empty) - - - - - - - - - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/weird.log b/testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/weird.log new file mode 100644 index 0000000000..0c85ed52de --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.http.deeply-nested-mime/weird.log @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path weird +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p name addl notice peer source +#types time string addr port addr port string string bool string string +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 48724 127.0.0.1 8080 exceeded_mime_max_depth 100 F zeek HTTP +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Traces/http/deeply-nested-mime.pcap b/testing/btest/Traces/http/deeply-nested-mime.pcap new file mode 100644 index 0000000000000000000000000000000000000000..021acd6991400003cc33bb25f7097ea170eae833 GIT binary patch literal 45183 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