From: Amos Jeffries Date: Fri, 14 Aug 2020 15:05:31 +0000 (+0000) Subject: Forbid obs-fold and bare CR whitespace in framing header fields (#701) X-Git-Tag: 4.15-20210522-snapshot~70 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=580448c9d95aaa94f7224a46a226f7cb3ba29f17;p=thirdparty%2Fsquid.git Forbid obs-fold and bare CR whitespace in framing header fields (#701) Header folding has been used for various attacks on HTTP proxies and servers. RFC 7230 prohibits sending obs-fold (in any header field) and allows the recipient to reject messages with folded headers. To reduce the attack space, Squid now rejects messages with folded Content-Length and Transfer-Encoding header field values. TODO: Follow RFC 7230 status code recommendations when rejecting. Bare CR is a CR character that is not followed by a LF character. Similar to folding, bare CRs has been used for various attacks. HTTP does not allow sending bare CRs in Content-Length and Transfer-Encoding header field values. Squid now rejects messages with bare CR characters in those framing-related field values. When rejecting, Squid informs the admin with a level-1 WARNING such as obs-fold seen in framing-sensitive Content-Length: ... --- diff --git a/src/HttpHeader.cc b/src/HttpHeader.cc index d5e92de778..52f7ae8ab0 100644 --- a/src/HttpHeader.cc +++ b/src/HttpHeader.cc @@ -372,6 +372,9 @@ HttpHeader::parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz, H return -1; } +// XXX: callers treat this return as boolean. +// XXX: A better mechanism is needed to signal different types of error. +// lexicon, syntax, semantics, validation, access policy - are all (ab)using 'return 0' int HttpHeader::parse(const char *header_start, size_t hdrLen, Http::ContentLengthInterpreter &clen) { @@ -400,9 +403,12 @@ HttpHeader::parse(const char *header_start, size_t hdrLen, Http::ContentLengthIn const char *field_start = field_ptr; const char *field_end; + const char *hasBareCr = nullptr; + size_t lines = 0; do { const char *this_line = field_ptr; field_ptr = (const char *)memchr(field_ptr, '\n', header_end - field_ptr); + ++lines; if (!field_ptr) { // missing @@ -437,6 +443,7 @@ HttpHeader::parse(const char *header_start, size_t hdrLen, Http::ContentLengthIn /* Barf on stray CR characters */ if (memchr(this_line, '\r', field_end - this_line)) { + hasBareCr = "bare CR"; debugs(55, warnOnError, "WARNING: suspicious CR characters in HTTP header {" << getStringPrefix(field_start, field_end-field_start) << "}"); @@ -486,6 +493,18 @@ HttpHeader::parse(const char *header_start, size_t hdrLen, Http::ContentLengthIn return 0; } + if (lines > 1 || hasBareCr) { + const auto framingHeader = (e->id == Http::HdrType::CONTENT_LENGTH || e->id == Http::HdrType::TRANSFER_ENCODING); + if (framingHeader) { + if (!hasBareCr) // already warned about bare CRs + debugs(55, warnOnError, "WARNING: obs-fold in framing-sensitive " << e->name << ": " << e->value); + delete e; + PROF_stop(HttpHeaderParse); + clean(); + return 0; + } + } + if (e->id == Http::HdrType::CONTENT_LENGTH && !clen.checkField(e->value)) { delete e;