Project

General

Profile

[Solved] Lighttpd allows SP octets within HTTP/1.1 request targets

Added by kenballus 4 months ago

Lighttpd accepts HTTP/1.1 requests with spaces in origin form request targets. This allows for cache poisoning under the right circumstances.

When Lighttpd receives a request of the following form (notice the 2 spaces between the request target and the HTTP version), it sees a path consisting of a forward slash followed by a space.

GET /  HTTP/1.1\r\n
Host: whatever\r\n
\r\n

Spaces are not permitted in the origin-form of an HTTP/1.1 request target. (See RFCs 9112, 9110, and 3986 for the ABNF rules to confirm this).

Here are some snippets from RFC 9112 about how to handle this situation:

Although the request-line grammar rule requires that each of the component elements be separated by a single SP octet, recipients MAY instead parse on whitespace-delimited word boundaries and, aside from the CRLF terminator, treat any form of whitespace as the SP separator while ignoring preceding or trailing whitespace; such whitespace includes one or more of the following octets: SP, HTAB, VT (%x0B), FF (%x0C), or bare CR. However, lenient parsing can result in request smuggling security vulnerabilities if there are multiple recipients of the message and each has its own unique interpretation of robustness (see Section 11.2).

Recipients of an invalid request-line SHOULD respond with either a 400 (Bad Request) error or a 301 (Moved Permanently) redirect with the request-target properly encoded. A recipient SHOULD NOT attempt to autocorrect and then process the request without a redirect, since the invalid request-line might be deliberately crafted to bypass security filters along the request chain.

In summary, the RFC discourages trying to handle the invalid URI on a best-effort basis, and suggests 3 options:
1. Ignore the extra space
2. Respond 400.
3. Respond 301 with the redirect set to a percent-encoded version of the request URI.

By sticking the extra space into the URI, as Lighttpd does, Lighttpd opens itself up to cache poisoning attacks when it's paired with a cache server that parses request lines on word boundaries, but also forwards extra whitespace in request lines as-is. I am aware of 3 such gateway servers.


Replies (1)

RE: Lighttpd allows SP octets within HTTP/1.1 request targets - Added by gstrauss 4 months ago

As with your previous recent reports, the bug appears to me to be in those gateway servers which are sending an invalid request-line for which the RFC recommends the receiving server return 400 or 301.

RFC 7230 and RFC 9112 contain the following, which is not present in RFC 2616:

A recipient SHOULD NOT attempt to autocorrect and then process the request without a redirect, since the invalid request-line might be deliberately crafted to bypass security filters along the request chain.

Yes, this is an area that lighttpd could be more strictly conforming to HTTP/1.1 RFC recommendations, even though the behavior in lighttpd is allowed. At the moment, lighttpd will accept extra trailing literal spaces in the URI, and also will accept spaces within the URI of the HTTP/1.x request-line. lighttpd URL normalization rules (unless explictly disabled in lighttpd.conf) will turn the literal spaces into %20, so lighttpd subsequently operates on and sends a valid URI to backends. This behavior is the same for lighttpd handling of HTTP/1.x request-line and lighttpd handling of HTTP/2 :path pseudo-header. This behavior is different from that of those broken gateways which are sending invalid request-line.

This patch will modify lighttpd to strictly reject trailing space(s) after the URI in the request-line for HTTP/1.1 requests, which will now be slightly different behavior from how lighttpd handles HTTP/2 :path.

--- a/src/request.c
+++ b/src/request.c
@@ -914,6 +914,8 @@ static int http_request_parse_reqline(request_st * const restrict r, const char
         /*(space char must exist if http_request_parse_proto_loose() succeeds)*/
         for (p = ptr + len - 9; p[-1] != ' '; --p) ;
     }
+    if (p[-2] == ' ')
+        return http_request_header_line_invalid(r, 400, "invalid request line (separators) -> 400");

     /* method is expected to be a short string in the general case */
     size_t i = 0;

Instead of the above -- and at the cost of an extra scan of the (potential very long) URI string -- lighttpd code could call http_request_check_uri_strict() even when lighttpd is about to scan the string again to normalize the URI. Doing so would be more strictly conforming with RFC recommendations, and would subsequently be less flexible in handling sloppy requests.

At the moment, I plan to add the small patch above to reject trailing space on the URI in the HTTP/1.x request-line.

    (1-1/1)