diff --git a/CHANGES b/CHANGES index a8e7a4083d..7e1feb2cfe 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,13 @@ +2.4-17 | 2015-06-28 13:02:41 -0700 + + * BIT-1314: Add detection for Quantum Insert attacks. The TCP + reassembler can now keep a history of old TCP segments using the + tcp_max_old_segments option. An overlapping segment with different + data will then generate an rexmit_inconsistency event. The default + for tcp_max_old_segments is zero, which disabled any additional + buffering. (Yun Zheng Hu) + 2.4-14 | 2015-06-28 12:30:12 -0700 * BIT-1400: Allow '<' and '>' in MIME multipart boundaries. The spec diff --git a/VERSION b/VERSION index 9329bc912d..3ad6a68544 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.4-14 +2.4-17 diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index 23f4fd43dd..d04e5194df 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -954,6 +954,11 @@ const tcp_max_above_hole_without_any_acks = 16384 &redef; ## .. bro:see:: tcp_max_initial_window tcp_max_above_hole_without_any_acks const tcp_excessive_data_without_further_acks = 10 * 1024 * 1024 &redef; +## Number of TCP segments to buffer beyond what's been acknowledged already +## to detect retransmission inconsistencies. Zero disables any additonal +## buffering. +const tcp_max_old_segments = 0 &redef; + ## For services without a handler, these sets define originator-side ports ## that still trigger reassembly. ## diff --git a/src/NetVar.cc b/src/NetVar.cc index 123e947701..dc049005bb 100644 --- a/src/NetVar.cc +++ b/src/NetVar.cc @@ -49,6 +49,7 @@ double tcp_partial_close_delay; int tcp_max_initial_window; int tcp_max_above_hole_without_any_acks; int tcp_excessive_data_without_further_acks; +int tcp_max_old_segments; RecordType* socks_address; @@ -354,6 +355,7 @@ void init_net_var() opt_internal_int("tcp_max_above_hole_without_any_acks"); tcp_excessive_data_without_further_acks = opt_internal_int("tcp_excessive_data_without_further_acks"); + tcp_max_old_segments = opt_internal_int("tcp_max_old_segments"); socks_address = internal_type("SOCKS::Address")->AsRecordType(); diff --git a/src/NetVar.h b/src/NetVar.h index bf2d9a5712..efadaaad6d 100644 --- a/src/NetVar.h +++ b/src/NetVar.h @@ -52,6 +52,7 @@ extern double tcp_reset_delay; extern int tcp_max_initial_window; extern int tcp_max_above_hole_without_any_acks; extern int tcp_excessive_data_without_further_acks; +extern int tcp_max_old_segments; extern RecordType* socks_address; diff --git a/src/Reassem.cc b/src/Reassem.cc index 8bf965427b..eb6a6a6c0d 100644 --- a/src/Reassem.cc +++ b/src/Reassem.cc @@ -34,12 +34,53 @@ uint64 Reassembler::total_size = 0; Reassembler::Reassembler(uint64 init_seq) { blocks = last_block = 0; + old_blocks = last_old_block = 0; + total_old_blocks = max_old_blocks = 0; trim_seq = last_reassem_seq = init_seq; } Reassembler::~Reassembler() { ClearBlocks(); + ClearOldBlocks(); + } + +void Reassembler::CheckOverlap(DataBlock *head, DataBlock *tail, + uint64 seq, uint64 len, const u_char* data) + { + if ( ! head || ! tail ) + return; + + uint64 orig_seq = seq; + uint64 orig_upper = seq + len; + const u_char* orig_data = data; + + for ( DataBlock* b = head; b; b = b->next ) + { + uint64 seq = orig_seq; + uint64 upper = orig_upper; + const u_char* data = orig_data; + + if ( upper <= b->seq ) + continue; + + if ( seq >= b->upper ) + continue; + + if ( seq < b->seq ) + { + data += (b->seq - seq); + seq = b->seq; + } + + if ( upper > b->upper ) + upper = b->upper; + + uint64 overlap_offset = seq - b->seq; + uint64 overlap_len = upper - seq; + if ( overlap_len ) + Overlap(&b->block[overlap_offset], data, overlap_len); + } } void Reassembler::NewBlock(double t, uint64 seq, uint64 len, const u_char* data) @@ -49,10 +90,14 @@ void Reassembler::NewBlock(double t, uint64 seq, uint64 len, const u_char* data) uint64 upper_seq = seq + len; + CheckOverlap(old_blocks, last_old_block, seq, len, data); + if ( upper_seq <= trim_seq ) // Old data, don't do any work for it. return; + CheckOverlap(blocks, last_block, seq, len, data); + if ( seq < trim_seq ) { // Partially old data, just keep the good stuff. uint64 amount_old = trim_seq - seq; @@ -119,7 +164,36 @@ uint64 Reassembler::TrimToSeq(uint64 seq) num_missing += seq - blocks->upper; } - delete blocks; + if ( max_old_blocks ) + { + // Move block over to old_blocks queue. + blocks->next = 0; + + if ( last_old_block ) + { + blocks->prev = last_old_block; + last_old_block->next = blocks; + } + else + { + blocks->prev = 0; + old_blocks = blocks; + } + + last_old_block = blocks; + total_old_blocks++; + + while ( old_blocks && total_old_blocks > max_old_blocks ) + { + DataBlock* next = old_blocks->next; + delete old_blocks; + old_blocks = next; + total_old_blocks--; + } + } + + else + delete blocks; blocks = b; } @@ -156,6 +230,18 @@ void Reassembler::ClearBlocks() last_block = 0; } +void Reassembler::ClearOldBlocks() + { + while ( old_blocks ) + { + DataBlock* b = old_blocks->next; + delete old_blocks; + old_blocks = b; + } + + last_old_block = 0; + } + uint64 Reassembler::TotalSize() const { uint64 size = 0; @@ -239,7 +325,7 @@ DataBlock* Reassembler::AddAndCheck(DataBlock* b, uint64 seq, uint64 upper, uint64 b_len = b->upper - overlap_start; uint64 overlap_len = min(new_b_len, b_len); - Overlap(&b->block[overlap_offset], data, overlap_len); +// Overlap(&b->block[overlap_offset], data, overlap_len); if ( overlap_len < new_b_len ) { diff --git a/src/Reassem.h b/src/Reassem.h index 39617f7816..e55c809990 100644 --- a/src/Reassem.h +++ b/src/Reassem.h @@ -36,6 +36,7 @@ public: // Delete all held blocks. void ClearBlocks(); + void ClearOldBlocks(); int HasBlocks() const { return blocks != 0; } uint64 LastReassemSeq() const { return last_reassem_seq; } @@ -50,6 +51,8 @@ public: // Sum over all data buffered in some reassembler. static uint64 TotalMemoryAllocation() { return total_size; } + void SetMaxOldBlocks(uint32 count) { max_old_blocks = count; } + protected: Reassembler() { } @@ -65,10 +68,19 @@ protected: DataBlock* AddAndCheck(DataBlock* b, uint64 seq, uint64 upper, const u_char* data); + void CheckOverlap(DataBlock *head, DataBlock *tail, + uint64 seq, uint64 len, const u_char* data); + DataBlock* blocks; DataBlock* last_block; + + DataBlock* old_blocks; + DataBlock* last_old_block; + uint64 last_reassem_seq; uint64 trim_seq; // how far we've trimmed + uint32 max_old_blocks; + uint32 total_old_blocks; static uint64 total_size; }; diff --git a/src/analyzer/protocol/tcp/TCP_Reassembler.cc b/src/analyzer/protocol/tcp/TCP_Reassembler.cc index 16bb9cc56d..bbcd9cb43a 100644 --- a/src/analyzer/protocol/tcp/TCP_Reassembler.cc +++ b/src/analyzer/protocol/tcp/TCP_Reassembler.cc @@ -42,6 +42,9 @@ TCP_Reassembler::TCP_Reassembler(analyzer::Analyzer* arg_dst_analyzer, seq_to_skip = 0; in_delivery = false; + if ( tcp_max_old_segments ) + SetMaxOldBlocks(tcp_max_old_segments); + if ( tcp_contents ) { // Val dst_port_val(ntohs(Conn()->RespPort()), TYPE_PORT); diff --git a/src/event.bif b/src/event.bif index 6531bef6d8..dc6388fbb5 100644 --- a/src/event.bif +++ b/src/event.bif @@ -282,7 +282,8 @@ event packet_contents%(c: connection, contents: string%); ## reassembling a TCP stream, Bro buffers all payload until it sees the ## responder acking it. If during that time, the sender resends a chunk of ## payload but with different content than originally, this event will be -## raised. +## raised. In addition, if :bro:id:`tcp_max_old_segments` is larger than zero, +## mismatches with that older still-buffered data will likewise trigger the event. ## ## c: The connection showing the inconsistency. ## diff --git a/testing/btest/Baseline/core.tcp.quantum-insert/.stdout b/testing/btest/Baseline/core.tcp.quantum-insert/.stdout new file mode 100644 index 0000000000..3f2c5fad1b --- /dev/null +++ b/testing/btest/Baseline/core.tcp.quantum-insert/.stdout @@ -0,0 +1,4 @@ +----- rexmit_inconsistency ----- +1429652006.683290 c: [orig_h=178.200.100.200, orig_p=39976/tcp, resp_h=96.126.98.124, resp_p=80/tcp] +1429652006.683290 t1: HTTP/1.1 200 OK\x0d\x0aContent-Length: 5\x0d\x0a\x0d\x0aBANG! +1429652006.683290 t2: HTTP/1.1 200 OK\x0d\x0aServer: nginx/1.4.4\x0d\x0aDate: diff --git a/testing/btest/Traces/tcp/qi_internet_SYNACK_curl_jsonip.pcap b/testing/btest/Traces/tcp/qi_internet_SYNACK_curl_jsonip.pcap new file mode 100644 index 0000000000..d906d9cac9 Binary files /dev/null and b/testing/btest/Traces/tcp/qi_internet_SYNACK_curl_jsonip.pcap differ diff --git a/testing/btest/core/tcp/quantum-insert.bro b/testing/btest/core/tcp/quantum-insert.bro new file mode 100644 index 0000000000..3fff5cd911 --- /dev/null +++ b/testing/btest/core/tcp/quantum-insert.bro @@ -0,0 +1,12 @@ +# @TEST-EXEC: bro -b -r $TRACES/tcp/qi_internet_SYNACK_curl_jsonip.pcap %INPUT +# @TEST-EXEC: btest-diff .stdout + +# Quantum Insert like attack, overlapping TCP packet with different content +redef tcp_max_old_segments = 10; +event rexmit_inconsistency(c: connection, t1: string, t2: string) + { + print "----- rexmit_inconsistency -----"; + print fmt("%.6f c: %s", network_time(), c$id); + print fmt("%.6f t1: %s", network_time(), t1); + print fmt("%.6f t2: %s", network_time(), t2); + }