h2o / h2o

H2O - the optimized HTTP/1, HTTP/2, HTTP/3 server

Home Page:https://h2o.examp1e.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bugs in HTTP/2 to HTTP/1.1 translation

kenballus opened this issue · comments

H2O, when configured as a proxy, is a little permissive in the HTTP/2 that it accepts.

The first bug

H2O accepts HTTP/2 headers with empty names. When an HTTP/2 request containing such a header is translated into HTTP/1.1, the resulting HTTP/1.1 message is invalid.
To be clear, I'm not sure whether the HTTP/2 RFC even disallows empty header names, but the HTTP/1.1 RFC definitely does. Thus, during a downgrade, such headers should either be translated into something else or omitted.

To reproduce

  • Configure H2O to act as a proxy using the following config file, where PROXY_BACKEND_PLACEHOLDER should be replaced with a server you control:
listen:
  port: 80

hosts:
  "default":
    paths:
      "/":
        proxy.reverse.url: "http://PROXY_BACKEND_PLACEHOLDER:80"
  • Send the following HTTP/2 request to the proxy:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00=\x01\x05\x00\x00\x00\x01\x00\n:authority\tlocalhost\x00\x05:path\x01/\x00\x07:method\x03GET\x00\x07:scheme\x04http\x00\x00\x00
  • H2O should forward the following HTTP/1.1 request to your server:
GET / HTTP/1.1\r\nhost: PROXY_BACKEND_PLACEHOLDER:80\r\nconnection: keep-alive\r\n: \r\nx-forwarded-proto: http\r\nx-forwarded-for: <IP REDACTED>\r\nvia: 2 localhost\r\n\r\n
  • Note that this is invalid HTTP/1.1 because it contains a header with no name. In fact, the H2O proxy will respond 400 to the request that it just emitted, because of the nameless header.

Proposed fix

Do not downgrade HTTP/2 requests that contain nameless headers.

The second bug

RFC 9113 Section 8.2.1 says this:

A field value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB, 0x20 or 0x09).

H2O does not enforce this check.

To reproduce

  • Configure H2O with the same config file given above.
  • Send it the following HTTP/2 request:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00E\x01\x05\x00\x00\x00\x01\x00\n:authority\tlocalhost\x00\x05:path\x01/\x00\x07:method\x03GET\x00\x07:scheme\x04http\x00\x05test1\x03\ta\t
  • H2O should forward the following HTTP/1.1 request to your server:
GET / HTTP/1.1\r\nhost: PROXY_BACKEND_PLACEHOLDER:80\r\nconnection: keep-alive\r\ntest1: \ta\t\r\nx-forwarded-proto: http\r\nx-forwarded-for: <IP REDACTED>\r\nvia: 2 localhost\r\n\r\n
  • Note that this request contains a header value that both begins and ends with \t.

Proposed fix

Respond 400, then close the stream when you receive an HTTP/2 request with a field value that begins or ends with ASCII whitespace, as the standard requires.

Thank you for bringing these issues into attention. Yes, validation rules have been tightened in RFC 9113, we should adhere to that. And now that we are going to be as strict as that, it makes sense to just reject empty header names as well.