From 2be225662e62d2f0a106d7b9d7b42acea46bdef6 Mon Sep 17 00:00:00 2001 From: Giuseppe Longo Date: Wed, 19 Aug 2026 15:50:55 +0200 Subject: [PATCH] swf: ignore FileLength when sizing output SWF FileLength was used to determine the decompressed buffer size when decompress-depth is set to zero, but this field can be manipulated and it can lead to evasion. This patch treat FileLength as metadata, the length bytes are just copied, and when decompress-depth is set to 0, a maximum value of 50MB is considered for the decompressed buffer. Ticket: #8855 (cherry picked from commit e707b21e21a75fb3b87c595fe1400ef66c211dc1) --- doc/userguide/configuration/suricata-yaml.rst | 5 +- doc/userguide/rules/http-keywords.rst | 5 +- src/tests/detect-file-data.c | 174 ++++++++++++++++++ src/util-file-decompression.c | 13 +- src/util-file-swf-decompression.c | 29 --- src/util-file-swf-decompression.h | 1 - 6 files changed, 183 insertions(+), 44 deletions(-) diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index 1bdf13d9fd..9d0de114d6 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -1749,8 +1749,9 @@ use of libhtp. # Specifies the maximum amount of data to decompress, # set 0 for unlimited. # decompress-depth: - # Specifies the maximum amount of decompressed data to obtain, - # set 0 for unlimited. + # Specifies the maximum amount of decompressed data to obtain. The + # output buffer grows on demand up to this limit. Set 0 to use the + # default limit of 50 MB. swf-decompression: enabled: yes type: both diff --git a/doc/userguide/rules/http-keywords.rst b/doc/userguide/rules/http-keywords.rst index 12caf75046..79dfd60b14 100644 --- a/doc/userguide/rules/http-keywords.rst +++ b/doc/userguide/rules/http-keywords.rst @@ -868,8 +868,9 @@ setting. # Specifies the maximum amount of data to decompress, # set 0 for unlimited. # decompress-depth: - # Specifies the maximum amount of decompressed data to obtain, - # set 0 for unlimited. + # Specifies the maximum amount of decompressed data to obtain. The + # output buffer grows on demand up to this limit. Set 0 to use the + # default limit of 50 MB. swf-decompression: enabled: yes type: both diff --git a/src/tests/detect-file-data.c b/src/tests/detect-file-data.c index 623de42620..9a75a7303a 100644 --- a/src/tests/detect-file-data.c +++ b/src/tests/detect-file-data.c @@ -62,6 +62,18 @@ static uint8_t *DetectFiledataSwfCreateCws(const uint8_t *plain, uint32_t plain_ return cws; } +// clang-format off +static const uint8_t swf_lzma_fixture[] = { + 0x5a, 0x57, 0x53, 0x17, 0x5c, 0x24, 0x00, 0x00, 0xb7, 0x21, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x3b, 0xff, 0xfc, 0x8e, 0x19, 0xfa, 0xdf, 0xe7, 0x66, 0x08, 0xa0, 0x3d, 0x3e, 0x85, + 0xf5, 0x75, 0x6f, 0xd0, 0x7e, 0x61, 0x35, 0x1b, 0x1a, 0x8b, 0x16, 0x4d, 0xdf, 0x05, 0x32, 0xfe, + 0xa4, 0x4c, 0x46, 0x49, 0xb7, 0x7b, 0x6b, 0x75, 0xf9, 0x2b, 0x5c, 0x37, 0x29, 0x0b, 0x91, 0x37, + 0x01, 0x37, 0x0e, 0xe9, 0xf2, 0xe1, 0xfc, 0x9e, 0x64, 0xda, 0x6c, 0x11, 0x21, 0x33, 0xed, 0xa0, + 0x0e, 0x76, 0x70, 0xa0, 0xcd, 0x98, 0x2e, 0x76, 0x80, 0xf0, 0xe0, 0x59, 0x56, 0x06, 0x08, 0xe9, + 0xca, 0xeb, 0xa2, 0xc6, 0xdb, 0x5a, 0x86, +}; +// clang-format on + /** * \test A malicious SWF whose header claims a huge decompressed size but * carries only a few bytes of body must not drive a large allocation @@ -185,6 +197,158 @@ static int DetectFiledataSwfDecompressAllocTest04(void) PASS; } +/** + * \test A CWS FileLength value of one must not truncate decompression or + * discard content beyond the first output byte. + */ +static int DetectFiledataSwfDecompressFileLengthTest01(void) +{ + const uint32_t plain_len = 16 * 1024; + uint8_t *plain = SCMalloc(plain_len); + FAIL_IF_NULL(plain); + memset(plain, 'A', plain_len); + plain[plain_len - 1] = 'B'; + + uint32_t cws_len = 0; + uint8_t *cws = DetectFiledataSwfCreateCws(plain, plain_len, 1, Z_BEST_COMPRESSION, &cws_len); + FAIL_IF_NULL(cws); + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression( + cws, cws_len, det_ctx, &out_buffer, HTTP_SWF_COMPRESSION_ZLIB, 0, 0); + + FAIL_IF(r != 1); + FAIL_IF(out_buffer.len != SWF_HEADER_LEN + plain_len); + FAIL_IF(out_buffer.buf[out_buffer.len - 1] != 'B'); + FAIL_IF(memcmp(out_buffer.buf + 4, cws + 4, 4) != 0); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + SCFree(cws); + SCFree(plain); + PASS; +} + +/** + * \test A ZWS FileLength value of one must not truncate decompression. + */ +static int DetectFiledataSwfDecompressFileLengthTest02(void) +{ + uint8_t zws[sizeof(swf_lzma_fixture)]; + memcpy(zws, swf_lzma_fixture, sizeof(zws)); + zws[4] = 1; + zws[5] = 0; + zws[6] = 0; + zws[7] = 0; + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression( + zws, (uint32_t)sizeof(zws), det_ctx, &out_buffer, HTTP_SWF_COMPRESSION_LZMA, 0, 0); + + FAIL_IF(r != 1); + FAIL_IF(out_buffer.len <= SWF_HEADER_LEN + 1); + FAIL_IF(memcmp(out_buffer.buf + 4, zws + 4, 4) != 0); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + PASS; +} + +/** + * \test A valid small FileLength is preserved in the synthesized FWS header. + */ +static int DetectFiledataSwfDecompressHeaderTest01(void) +{ + const uint8_t plain[] = "small body"; + const uint32_t file_len = SWF_HEADER_LEN + sizeof(plain); + uint32_t cws_len = 0; + uint8_t *cws = DetectFiledataSwfCreateCws( + plain, sizeof(plain), file_len, Z_BEST_COMPRESSION, &cws_len); + FAIL_IF_NULL(cws); + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression( + cws, cws_len, det_ctx, &out_buffer, HTTP_SWF_COMPRESSION_ZLIB, 0, 0); + + FAIL_IF(r != 1); + FAIL_IF(memcmp(out_buffer.buf + 4, cws + 4, 4) != 0); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + SCFree(cws); + PASS; +} + +/** + * \test The configured decompression depth remains the CWS output limit. + */ +static int DetectFiledataSwfDecompressDepthTest01(void) +{ + const uint32_t plain_len = 16 * 1024; + const uint32_t decompress_depth = 5000; + uint8_t *plain = SCMalloc(plain_len); + FAIL_IF_NULL(plain); + memset(plain, 'A', plain_len); + plain[plain_len - 1] = 'B'; + + uint32_t cws_len = 0; + uint8_t *cws = DetectFiledataSwfCreateCws( + plain, plain_len, plain_len + SWF_HEADER_LEN, Z_BEST_COMPRESSION, &cws_len); + FAIL_IF_NULL(cws); + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression( + cws, cws_len, det_ctx, &out_buffer, HTTP_SWF_COMPRESSION_ZLIB, decompress_depth, 0); + + FAIL_IF(r != 1); + FAIL_IF(out_buffer.len != SWF_HEADER_LEN + decompress_depth); + FAIL_IF(memcmp(out_buffer.buf + SWF_HEADER_LEN, plain, decompress_depth) != 0); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + SCFree(cws); + SCFree(plain); + PASS; +} + +/** + * \test The configured decompression depth remains the ZWS output limit. + */ +static int DetectFiledataSwfDecompressDepthTest02(void) +{ + const uint32_t decompress_depth = 1; + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression(swf_lzma_fixture, (uint32_t)sizeof(swf_lzma_fixture), det_ctx, + &out_buffer, HTTP_SWF_COMPRESSION_LZMA, decompress_depth, 0); + + FAIL_IF(r != 1); + FAIL_IF(out_buffer.len != SWF_HEADER_LEN + decompress_depth); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + PASS; +} + static int DetectEngineSMTPFiledataTest02(void) { DetectEngineCtx *de_ctx = DetectEngineCtxInit(); @@ -227,6 +391,16 @@ void DetectFiledataRegisterTests(void) "DetectFiledataSwfDecompressAllocTest03", DetectFiledataSwfDecompressAllocTest03); UtRegisterTest( "DetectFiledataSwfDecompressAllocTest04", DetectFiledataSwfDecompressAllocTest04); + UtRegisterTest("DetectFiledataSwfDecompressFileLengthTest01", + DetectFiledataSwfDecompressFileLengthTest01); + UtRegisterTest("DetectFiledataSwfDecompressFileLengthTest02", + DetectFiledataSwfDecompressFileLengthTest02); + UtRegisterTest( + "DetectFiledataSwfDecompressHeaderTest01", DetectFiledataSwfDecompressHeaderTest01); + UtRegisterTest( + "DetectFiledataSwfDecompressDepthTest01", DetectFiledataSwfDecompressDepthTest01); + UtRegisterTest( + "DetectFiledataSwfDecompressDepthTest02", DetectFiledataSwfDecompressDepthTest02); UtRegisterTest("DetectEngineSMTPFiledataTest02", DetectEngineSMTPFiledataTest02); UtRegisterTest("DetectFiledataParseTest04", DetectFiledataParseTest04); } diff --git a/src/util-file-decompression.c b/src/util-file-decompression.c index 2ddbb3d6d8..b00bb1b72b 100644 --- a/src/util-file-decompression.c +++ b/src/util-file-decompression.c @@ -118,15 +118,8 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, return 0; } - /* get flash decompressed file length */ - uint32_t decompressed_swf_len = FileGetSwfDecompressedLen(buffer, buffer_len); - if (decompressed_swf_len == 0) { - decompressed_swf_len = MIN_SWF_LEN; - } - - uint32_t decompressed_data_limit = decompressed_swf_len; - if (decompress_depth > 0 && decompress_depth < decompressed_data_limit) - decompressed_data_limit = decompress_depth; + uint32_t decompressed_data_limit = + (decompress_depth == 0) ? MAX_SWF_DECOMPRESSED_LEN : decompress_depth; uint32_t initial_buffer_len = MIN(SWF_DECOMPRESS_INITIAL_BUFFER_LEN, decompressed_data_limit + SWF_HEADER_LEN); @@ -144,7 +137,7 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, out_buffer->buf[1] = 'W'; out_buffer->buf[2] = 'S'; out_buffer->buf[3] = swf_version; - memcpy(out_buffer->buf + 4, &decompressed_swf_len, 4); + memcpy(out_buffer->buf + 4, buffer + 4, 4); uint32_t decompressed_data_produced = 0; diff --git a/src/util-file-swf-decompression.c b/src/util-file-swf-decompression.c index b9a4bb5a66..c640779a1f 100644 --- a/src/util-file-swf-decompression.c +++ b/src/util-file-swf-decompression.c @@ -37,35 +37,6 @@ #include -/* - * Return uncompressed file length - * in little-endian order - */ -uint32_t FileGetSwfDecompressedLen(const uint8_t *buffer, - const uint32_t buffer_len) -{ - if (buffer_len < 8) { - return 0; - } - - uint32_t a = buffer[4]; - uint32_t b = buffer[5]; - uint32_t c = buffer[6]; - uint32_t d = buffer[7]; - - uint32_t value = (((a & 0xff) << 24UL) | - ((b & 0xff) << 16UL) | - ((c & 0xff) << 8UL) | - (d & 0xff)); - - uint32_t len = (((value >> 24) & 0x000000FFUL) | - ((value >> 8) & 0x0000FF00UL) | - ((value << 8) & 0x00FF0000UL) | - ((value << 24) & 0xFF000000UL)); - - return MIN(MAX_SWF_DECOMPRESSED_LEN, len); -} - uint8_t FileGetSwfVersion(const uint8_t *buffer, const uint32_t buffer_len) { if (buffer_len > 3) diff --git a/src/util-file-swf-decompression.h b/src/util-file-swf-decompression.h index 478ec88346..6a169fe1b4 100644 --- a/src/util-file-swf-decompression.h +++ b/src/util-file-swf-decompression.h @@ -36,7 +36,6 @@ #define SWF_DECOMPRESS_INITIAL_BUFFER_LEN 4096 uint8_t FileGetSwfVersion(const uint8_t *buffer, const uint32_t buffer_len); -uint32_t FileGetSwfDecompressedLen(const uint8_t *buffer, uint32_t buffer_len); int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, const uint8_t *compressed_data, uint32_t compressed_data_len, InspectionBuffer *out_buffer, uint32_t decompressed_data_limit, uint32_t *decompressed_data_produced);