HTTP: Implement FlipRoles()

When Zeek flips roles of a HTTP connection subsequent to the HTTP analyzer
being attached, that analyzer would not update its own ContentLine analyzer
state, resulting in the wrong ContentLine analyzer being switched into
plain delivery mode.

In debug builds, this would result in assertion failures, in production
builds, the HTTP analyzer would receive HTTP bodies as individual header
lines, or conversely, individual header lines would be delivered as a
large chunk from the ContentLine analyzer.

PCAPs were generated locally using tcprewrite to select well-known-http ports
for both endpoints, then editcap to drop the first SYN packet.

Kudos to @JordanBarnartt for keeping at it.

Closes #3789
This commit is contained in:
Arne Welzel 2024-07-03 14:13:03 +02:00
parent c6368fc3f0
commit 377fd711bd
12 changed files with 88 additions and 0 deletions

View file

@ -1020,6 +1020,36 @@ void HTTP_Analyzer::Undelivered(uint64_t seq, int len, bool is_orig) {
}
}
void HTTP_Analyzer::FlipRoles() {
analyzer::tcp::TCP_ApplicationAnalyzer::FlipRoles();
// If FlipRoles() is invoked after we've upgraded to something,
// don't do anything. This shouldn't happen as flipping of TCP
// connections currently happens before any data is transferred,
// but better safe than sorry.
if ( upgraded || pia ) {
Weird("HTTP_late_flip_roles");
return;
}
// If we haven't upgraded but saw request or replies, just bail
// for the rest of this connection. Again, this should never happen
// right now, but raise a weird in case it starts to happen.
if ( num_requests > 0 || num_replies > 0 ) {
Weird("HTTP_late_flip_roles");
SetSkip(true);
return;
}
// IsOrig() of the support analyzer has been updated, but we still need
// to change the analyzer's local state and the partial skipping setting.
bool skip_partial_orig = content_line_orig->SkipPartial();
bool skip_partial_resp = content_line_resp->SkipPartial();
std::swap(content_line_orig, content_line_resp);
content_line_orig->SetSkipPartial(skip_partial_orig);
content_line_resp->SetSkipPartial(skip_partial_resp);
}
void HTTP_Analyzer::EndpointEOF(bool is_orig) {
analyzer::tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig);

View file

@ -167,6 +167,7 @@ public:
void Done() override;
void DeliverStream(int len, const u_char* data, bool orig) override;
void Undelivered(uint64_t seq, int len, bool orig) override;
void FlipRoles() override;
// Overridden from analyzer::tcp::TCP_ApplicationAnalyzer
void EndpointEOF(bool is_orig) override;

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
id.orig_h id.orig_p id.resp_h id.resp_p history service
127.0.0.1 1080 127.0.0.1 8000 ^hADadFf http

View file

@ -0,0 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
id.orig_h id.orig_p id.resp_h id.resp_p analyzers mime_type sha1
127.0.0.1 1080 127.0.0.1 8000 SHA1 image/png 1991cedee47909e324ac1b8bee2020d5690891e1
127.0.0.1 1080 127.0.0.1 8000 SHA1 text/json eae909a9c2827d827ef30a6675a6388770ddc88d

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
id.orig_h id.orig_p id.resp_h id.resp_p host method uri version user_agent status_code status_msg
127.0.0.1 1080 127.0.0.1 8000 localhost:8000 POST / 1.1 curl/7.81.0 200 OK

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
id.orig_h id.orig_p id.resp_h id.resp_p history service
127.0.0.1 1080 127.0.0.1 80 ^hADadFf http

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
id.orig_h id.orig_p id.resp_h id.resp_p analyzers mime_type sha1
127.0.0.1 1080 127.0.0.1 80 SHA1 image/png 1991cedee47909e324ac1b8bee2020d5690891e1

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
id.orig_h id.orig_p id.resp_h id.resp_p host method uri version user_agent status_code status_msg
127.0.0.1 1080 127.0.0.1 80 localhost GET /zeek.png 1.1 curl/7.81.0 200 OK

Binary file not shown.

View file

@ -0,0 +1,19 @@
# @TEST-DOC: Flipping roles of a HTTP connection didn't flip the content line analyzers, resulting in inconsistent deliveries. Regression test for #3789
# Pcap contains a POST of the Zeek logo, expecting SHA1 1991cedee47909e324ac1b8bee2020d5690891e1 in files.log
# @TEST-EXEC: zeek -b -r $TRACES/http/zeek-image-post-1080-8000-x.pcap %INPUT
# @TEST-EXEC: zeek-cut -m id.orig_h id.orig_p id.resp_h id.resp_p history service < conn.log > conn.log.cut
# @TEST-EXEC: zeek-cut -m id.orig_h id.orig_p id.resp_h id.resp_p host method uri version user_agent status_code status_msg < http.log > http.log.cut
# @TEST-EXEC: zeek-cut -m id.orig_h id.orig_p id.resp_h id.resp_p analyzers mime_type sha1 < files.log > files.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff http.log.cut
# @TEST-EXEC: btest-diff files.log.cut
@load base/protocols/conn
@load base/protocols/http
@load base/files/hash
event file_new(f: fa_file)
{
Files::add_analyzer(f, Files::ANALYZER_SHA1);
}

View file

@ -0,0 +1,19 @@
# @TEST-DOC: Flipping roles of a HTTP connection didn't flip the content line analyzers, resulting in inconsistent deliveries. Regression test for #3789
# Pcap contains a download of the Zeek logo, expecting SHA1 1991cedee47909e324ac1b8bee2020d5690891e1 in files.log
# @TEST-EXEC: zeek -b -r $TRACES/http/zeek-image-1080-80-x.pcap %INPUT
# @TEST-EXEC: zeek-cut -m id.orig_h id.orig_p id.resp_h id.resp_p history service < conn.log > conn.log.cut
# @TEST-EXEC: zeek-cut -m id.orig_h id.orig_p id.resp_h id.resp_p host method uri version user_agent status_code status_msg < http.log > http.log.cut
# @TEST-EXEC: zeek-cut -m id.orig_h id.orig_p id.resp_h id.resp_p analyzers mime_type sha1 < files.log > files.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff http.log.cut
# @TEST-EXEC: btest-diff files.log.cut
@load base/protocols/conn
@load base/protocols/http
@load base/files/hash
event file_new(f: fa_file)
{
Files::add_analyzer(f, Files::ANALYZER_SHA1);
}