From: Amos Jeffries Date: Thu, 16 Jul 2015 05:09:30 +0000 (-0700) Subject: Violate RFC 2396 and 3986 URI handling requirements. X-Git-Tag: merge-candidate-3-v1~38^2~12 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e47e0802cd75e90bdf81d52da518d35d643f6c1c;p=thirdparty%2Fsquid.git Violate RFC 2396 and 3986 URI handling requirements. For the past 17 years RFC 2396 has specified explicit characters which are disallowed in any form of URI due to their use in potential or real malware attacks against network servers. 10 years ago RFC 3986 updated this by allowing some previously disallowed characters, and moving to a model of explicitly listing all characters allowed in each segment of a URI with explicit %-encoding requirement for all other characters. Squid has recently been updated to parse that RFC 3986 syntax closely, with minimal tolerance for garbage as outlined explicitly in RFC 7230. However, various major corporations are still building popular tools that violate the RFC 3986 Security Considerations surrounding safe encoding of characters in URI they transmit as HTTP request URLs. This patch allows Squid with --enable-http-violations (on by default) and configured for lenient parsing (on by default) to accept a subset of characters which are expressly forbidden but actively used un-encoded. Malware attacks utilizing these characters to perform URL-injection are mitigated by treating the client request as an HTTP "0.9" protocol message. Such messages are not permitted to use nonidempotent HTTP methods which affect server state, and most Mime headers from the client are ignored. --- diff --git a/src/http/one/Parser.cc b/src/http/one/Parser.cc index 7352e606fa..dca2c76810 100644 --- a/src/http/one/Parser.cc +++ b/src/http/one/Parser.cc @@ -43,7 +43,8 @@ Http::One::Parser::grabMimeBlock(const char *which, const size_t limit) { // MIME headers block exist in (only) HTTP/1.x and ICY const bool expectMime = (msgProtocol_.protocol == AnyP::PROTO_HTTP && msgProtocol_.major == 1) || - msgProtocol_.protocol == AnyP::PROTO_ICY; + msgProtocol_.protocol == AnyP::PROTO_ICY || + hackExpectsMime_; if (expectMime) { /* NOTE: HTTP/0.9 messages do not have a mime header block. diff --git a/src/http/one/Parser.h b/src/http/one/Parser.h index e59202a371..d63b3be9d5 100644 --- a/src/http/one/Parser.h +++ b/src/http/one/Parser.h @@ -41,7 +41,7 @@ class Parser : public RefCountable public: typedef SBuf::size_type size_type; - Parser() : parseStatusCode(Http::scNone), parsingStage_(HTTP_PARSE_NONE) {} + Parser() : parseStatusCode(Http::scNone), parsingStage_(HTTP_PARSE_NONE), hackExpectsMime_(false) {} virtual ~Parser() {} /// Set this parser back to a default state. @@ -131,6 +131,9 @@ protected: /// buffer holding the mime headers (if any) SBuf mimeHeaderBlock_; + + /// Whether the invalid HTTP as HTTP/0.9 hack expects a mime header block + bool hackExpectsMime_; }; } // namespace One diff --git a/src/http/one/RequestParser.cc b/src/http/one/RequestParser.cc index 97545d8b29..6da9f15106 100644 --- a/src/http/one/RequestParser.cc +++ b/src/http/one/RequestParser.cc @@ -261,6 +261,7 @@ Http::One::RequestParser::parseRequestFirstLine() WspDelim += CharacterSet::HTAB + CharacterSet("VT,FF","\x0B\x0C") + CharacterSet::CR; + debugs(74, 5, "using Parser relaxed WSP characters"); } // only search for method if we have not yet found one @@ -288,6 +289,8 @@ Http::One::RequestParser::parseRequestFirstLine() if (Config.onoff.relaxed_header_parser) { // whitespace tolerant + int warnOnError = (Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2); + // NOTES: // * this would be static, except WspDelim changes with reconfigure // * HTTP-version charset is included by uriValidCharacters() @@ -305,7 +308,7 @@ Http::One::RequestParser::parseRequestFirstLine() uri_ = rTok.remaining(); msgProtocol_ = Http::ProtocolVersion(1, (*digit.rawContent() - '0')); if (uri_.isEmpty()) { - debugs(33, 5, "invalid request-line. missing URL"); + debugs(33, warnOnError, "invalid request-line. missing URL"); parseStatusCode = Http::scBadRequest; return -1; } @@ -325,11 +328,77 @@ Http::One::RequestParser::parseRequestFirstLine() return 1; } - debugs(33, 5, "invalid request-line. not HTTP"); + debugs(33, warnOnError, "invalid request-line. not HTTP"); parseStatusCode = Http::scBadRequest; return -1; } + if (!tok.atEnd()) { + +#if USE_HTTP_VIOLATIONS + /* + * RFC 3986 explicitly lists the characters permitted in URI. + * A non-permitted character was found somewhere in the request-line. + * However, as long as we can find the LF, accept the characters + * which we know are invalid in any URI but actively used. + */ + LfDelim.add('\0'); // Java + LfDelim.add(' '); // IIS + LfDelim.add('\"'); // Bing + LfDelim.add('\\'); // MSIE, Firefox + LfDelim.add('|'); // Amazon + LfDelim.add('^'); // Microsoft News + + // other ASCII characters for which RFC 2396 has explicitly disallowed use + // since 1998 and which were not later permitted by RFC 3986 in 2005. + LfDelim.add('<'); // HTML embedded in URL + LfDelim.add('>'); // HTML embedded in URL + LfDelim.add('`'); // Shell Script embedded in URL + LfDelim.add('{'); // JSON or Javascript embedded in URL + LfDelim.add('}'); // JSON or Javascript embedded in URL + + // reset the tokenizer from anything the above did, then seek the LF character. + tok.reset(buf_); + + if (tok.prefix(line, LfDelim) && tok.skip('\n')) { + + Http1::Tokenizer rTok(line); + + // strip terminating CR (if any) + SBuf nil; + (void)rTok.suffix(nil,CharacterSet::CR); // optional CR in terminator + line = rTok.remaining(); + + // strip terminating 'WSP HTTP-version' (if any) + if (rTok.suffix(nil,CharacterSet::DIGIT) && rTok.skipSuffix(Http1magic) && rTok.suffix(nil,WspDelim)) { + hackExpectsMime_ = true; // client thinks its speaking HTTP, probably sent a mime block. + uri_ = rTok.remaining(); + } else + uri_ = line; // no HTTP/1.x label found. Use the whole line. + + if (uri_.isEmpty()) { + debugs(33, warnOnError, "invalid request-line. missing URL"); + parseStatusCode = Http::scBadRequest; + return -1; + } + + debugs(33, warnOnError, "invalid request-line. treating as HTTP/0.9" << (hackExpectsMime_?" (with mime)":"")); + msgProtocol_ = Http::ProtocolVersion(0,9); + parseStatusCode = Http::scOkay; + buf_ = tok.remaining(); // incremental parse checkpoint + return 1; + + } else if (tok.atEnd()) { + debugs(74, 5, "Parser needs more data"); + return 0; + } + // else, drop back to invalid request-line handling +#endif + const SBuf t = tok.remaining(); + debugs(33, warnOnError, "invalid request-line characters." << Raw("data", t.rawContent(), t.length())); + parseStatusCode = Http::scBadRequest; + return -1; + } debugs(74, 5, "Parser needs more data"); return 0; }