mirror of
https://github.com/OISF/suricata.git
synced 2026-10-11 22:09:53 +00:00
http1: limit the number of compression bombs per flow
Ticket: 8694 Otherwise, a flow full of small compression bombs is too slow to process. When the threshold is reached, decompression is skipped for the rest of the flow.
This commit is contained in:
committed by
Victor Julien
parent
bc41dcc854
commit
392b6aee29
@@ -97,4 +97,7 @@ alert http any any -> any any (msg:"SURICATA HTTP request too many headers"; flo
|
||||
alert http any any -> any any (msg:"SURICATA HTTP response too many headers"; flow:established,to_client; app-layer-event:http.response_too_many_headers; classtype:protocol-command-decode; sid:2221057; rev:1;)
|
||||
|
||||
#alert http any any -> any any (msg:"SURICATA HTTP response chunk extension"; flow:established; app-layer-event:http.response_chunk_extension; classtype:protocol-command-decode; sid:2221058; rev:1;)
|
||||
# next sid 2221059
|
||||
|
||||
alert http any any -> any any (msg:"SURICATA HTTP compression bomb limit reached"; flow:established; app-layer-event:http.compression_bomb_limit_reached; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221059; rev:1;)
|
||||
|
||||
# next sid 2221060
|
||||
|
||||
@@ -248,6 +248,20 @@ pub unsafe extern "C" fn htp_config_set_compression_bomb_limit(
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the maximum number of compression bombs LibHTP will decompress.
|
||||
/// # Safety
|
||||
/// When calling this method, you have to ensure that cfg is either properly initialized or NULL
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn htp_config_set_max_nb_compression_bombs(
|
||||
cfg: *mut Config, max_bombs: libc::size_t,
|
||||
) {
|
||||
if let Ok(max_bombs) = max_bombs.try_into() {
|
||||
if let Some(cfg) = cfg.as_mut() {
|
||||
cfg.compression_options.set_max_bombs(max_bombs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the maximum compression time LibHTP will allow.
|
||||
/// # Safety
|
||||
/// When calling this method, you have to ensure that cfg is either properly initialized or NULL
|
||||
|
||||
@@ -352,6 +352,9 @@ pub struct ConnectionParser {
|
||||
/// The hook that should be receiving raw connection data.
|
||||
pub(crate) response_data_receiver_hook: Option<DataHook>,
|
||||
|
||||
/// Number of compression bombs seen.
|
||||
pub(crate) bombs: u8,
|
||||
|
||||
/// Transactions processed by this parser
|
||||
transactions: Transactions,
|
||||
}
|
||||
@@ -402,6 +405,7 @@ impl ConnectionParser {
|
||||
response_state: State::Idle,
|
||||
response_state_previous: State::None,
|
||||
response_data_receiver_hook: None,
|
||||
bombs: 0,
|
||||
transactions: Transactions::new(cfg, &logger),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ const DEFAULT_TIME_LIMIT: u32 = 100_000;
|
||||
const DEFAULT_TIME_FREQ_TEST: u32 = 256;
|
||||
/// Default number of layers that will be decompressed
|
||||
const DEFAULT_LAYER_LIMIT: u32 = 2;
|
||||
/// Default number of bombs before skipping decompression for the flow
|
||||
const DEFAULT_BOMB_NB_LIMIT: u8 = 3;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
/// Decompression options
|
||||
@@ -32,6 +34,8 @@ pub(crate) struct Options {
|
||||
lzma_layers: Option<u32>,
|
||||
/// max output size for a compression bomb.
|
||||
bomb_limit: u64,
|
||||
/// max number of compression bombs before skipping decompression for the flow
|
||||
bomb_nb_limit: u8,
|
||||
/// max compressed-to-decrompressed ratio that should not be exceeded during decompression.
|
||||
bomb_ratio: u64,
|
||||
/// max time for a decompression bomb in microseconds.
|
||||
@@ -72,6 +76,16 @@ impl Options {
|
||||
self.bomb_limit
|
||||
}
|
||||
|
||||
/// Get the maximum compression bombs number.
|
||||
pub(crate) fn get_max_bombs(&self) -> u8 {
|
||||
self.bomb_nb_limit
|
||||
}
|
||||
|
||||
/// Set the maximum number of compression bombs before skipping decompression for the flow.
|
||||
pub(crate) fn set_max_bombs(&mut self, max_bombs: u8) {
|
||||
self.bomb_nb_limit = max_bombs;
|
||||
}
|
||||
|
||||
/// Set the compression bomb limit.
|
||||
pub(crate) fn set_bomb_limit(&mut self, bomblimit: u64) {
|
||||
self.bomb_limit = bomblimit;
|
||||
@@ -123,6 +137,7 @@ impl Default for Options {
|
||||
}),
|
||||
lzma_layers: Some(DEFAULT_LZMA_LAYERS),
|
||||
bomb_limit: DEFAULT_BOMB_LIMIT,
|
||||
bomb_nb_limit: DEFAULT_BOMB_NB_LIMIT,
|
||||
bomb_ratio: DEFAULT_BOMB_RATIO,
|
||||
time_limit: DEFAULT_TIME_LIMIT,
|
||||
time_test_freq: DEFAULT_TIME_FREQ_TEST,
|
||||
|
||||
@@ -110,6 +110,8 @@ pub enum HtpLogCode {
|
||||
LZMA_MEMLIMIT_REACHED,
|
||||
/// Reached configured time limit for decompression or reached bomb limit.
|
||||
COMPRESSION_BOMB,
|
||||
/// Reached configured time limit for decompression or reached bomb limit.
|
||||
COMPRESSION_BOMB_LIMIT_REACHED,
|
||||
/// Unexpected response body present.
|
||||
RESPONSE_BODY_UNEXPECTED,
|
||||
/// Content-length parsing contains extra leading characters.
|
||||
|
||||
@@ -1243,6 +1243,10 @@ impl ConnectionParser {
|
||||
/// Prepend a decompressor to the request
|
||||
fn request_prepend_decompressor(&mut self, encoding: HtpContentEncoding) -> Result<()> {
|
||||
let compression_options = self.cfg.compression_options;
|
||||
if self.bombs >= compression_options.get_max_bombs() {
|
||||
// skip decompression for this flow if too many bombs were seen
|
||||
return Ok(());
|
||||
}
|
||||
if encoding != HtpContentEncoding::None {
|
||||
// ensured by caller
|
||||
let req = self.request_mut().unwrap();
|
||||
@@ -1328,6 +1332,14 @@ impl ConnectionParser {
|
||||
request_entity_len, request_message_len,
|
||||
)
|
||||
);
|
||||
self.bombs += 1;
|
||||
if self.bombs == compression_options.get_max_bombs() {
|
||||
htp_error!(
|
||||
self.logger,
|
||||
HtpLogCode::COMPRESSION_BOMB_LIMIT_REACHED,
|
||||
format!("Compression bomb: happened {} times", self.bombs,)
|
||||
);
|
||||
}
|
||||
return Err(std::io::Error::other("compression_bomb_limit reached"));
|
||||
}
|
||||
Ok(tx_data.len())
|
||||
|
||||
@@ -1302,6 +1302,14 @@ impl ConnectionParser {
|
||||
response_entity_len, response_message_len,
|
||||
)
|
||||
);
|
||||
self.bombs += 1;
|
||||
if self.bombs == compression_options.get_max_bombs() {
|
||||
htp_error!(
|
||||
self.logger,
|
||||
HtpLogCode::COMPRESSION_BOMB_LIMIT_REACHED,
|
||||
format!("Compression bomb: happened {} times", self.bombs,)
|
||||
);
|
||||
}
|
||||
return Err(std::io::Error::other("compression_bomb_limit reached"));
|
||||
}
|
||||
Ok(tx_data.len())
|
||||
@@ -1310,6 +1318,10 @@ impl ConnectionParser {
|
||||
/// Prepend response decompressor
|
||||
fn response_prepend_decompressor(&mut self, encoding: HtpContentEncoding) -> Result<()> {
|
||||
let compression_options = self.cfg.compression_options;
|
||||
if self.bombs >= compression_options.get_max_bombs() {
|
||||
// skip decompression for this flow if too many bombs were seen
|
||||
return Ok(());
|
||||
}
|
||||
if encoding != HtpContentEncoding::None {
|
||||
// ensured by caller
|
||||
let resp = self.response_mut().unwrap();
|
||||
|
||||
@@ -196,6 +196,7 @@ SCEnumCharMap http_decoder_event_table[] = {
|
||||
|
||||
{ "LZMA_MEMLIMIT_REACHED", HTP_LOG_CODE_LZMA_MEMLIMIT_REACHED },
|
||||
{ "COMPRESSION_BOMB", HTP_LOG_CODE_COMPRESSION_BOMB },
|
||||
{ "COMPRESSION_BOMB_LIMIT_REACHED", HTP_LOG_CODE_COMPRESSION_BOMB_LIMIT_REACHED },
|
||||
|
||||
{ "REQUEST_TOO_MANY_HEADERS", HTP_LOG_CODE_REQUEST_TOO_MANY_HEADERS },
|
||||
{ "RESPONSE_TOO_MANY_HEADERS", HTP_LOG_CODE_RESPONSE_TOO_MANY_HEADERS },
|
||||
@@ -2222,6 +2223,20 @@ static void HTPConfigParseParameters(HTPCfgRec *cfg_prec, SCConfNode *s, struct
|
||||
SCLogConfig("Setting HTTP LZMA decompression layers to %" PRIu32 "", (int)limit);
|
||||
htp_config_set_lzma_layers(cfg_prec->cfg, limit);
|
||||
}
|
||||
} else if (strcasecmp("compression-bomb-count", p->name) == 0) {
|
||||
uint8_t limit = 0;
|
||||
if (ParseSizeStringU8(p->val, &limit) < 0) {
|
||||
FatalError("failed to parse 'compression-bomb-count' "
|
||||
"from conf file - %s.",
|
||||
p->val);
|
||||
}
|
||||
if (limit == 0) {
|
||||
FatalError("'compression-bomb-count' "
|
||||
"from conf file cannot be 0.");
|
||||
}
|
||||
/* set default soft-limit with our new hard limit */
|
||||
SCLogConfig("Setting HTTP compression bomb count limit to %" PRIu8, limit);
|
||||
htp_config_set_max_nb_compression_bombs(cfg_prec->cfg, (size_t)limit);
|
||||
} else if (strcasecmp("compression-bomb-limit", p->name) == 0) {
|
||||
uint32_t limit = 0;
|
||||
if (ParseSizeStringU32(p->val, &limit) < 0) {
|
||||
|
||||
@@ -1199,6 +1199,10 @@ app-layer:
|
||||
# Maximum decompressed size with a compression ratio
|
||||
# above 2048 (only LZMA can reach this ratio, deflate cannot)
|
||||
#compression-bomb-limit: 1 MiB
|
||||
# Maximum times in a single flow a compression bomb is allowed.
|
||||
# If this is reached no more decompression will happen for the
|
||||
# rest of that flow.
|
||||
#compression-bomb-count: 3
|
||||
# Maximum time spent decompressing a single transaction in usec
|
||||
#decompression-time-limit: 100000
|
||||
# Maximum number of live transactions per flow
|
||||
|
||||
Reference in New Issue
Block a user