Bad Message 400: Folding Header

6.3k Views Asked by At

We recently switched from Jetty v 6_1_26 to 9_4_11.

We followed following url: http://jetty.4.x6.nabble.com/Configuring-option-2-of-RFC-7230-paragraph-5-HTTP-header-folding-td4966330.html

And made required changes in our code to set the compliance mode to RFC2616 as we need multiple-line header support in our product.

This is how we have set it:

public class JettyPsServerConnector extends ServerConnector {
  public JettyServerConnector(Server server, PsSelectorProvider psProvider,Map<String,String> configMap, boolean useSSL) throws Exception{

    super(server,0,-1, new SslConnectionFactory( getSSLContextFactory(configMap),HttpVersion.HTTP_1_1.asString()),
                           new HttpConnectionFactory( getHTTPConfiguration(), HttpCompliance.RFC2616 ));

    }

And we have verified it successfully using following code that the compliance mode is getting changed.

Connector[] connectorArray = server.getConnectors();
for(Connector conn: connectorArray){
    Collection col = conn.getConnectionFactories();
    for(ConnectionFactory con: col){
        if (con instanceof HttpConnectionFactory)
            System.out.println("HTTP Compliance Mode:"+((HttpConnectionFactory)con).getHttpCompliance());
                    }
        }

This prints compliance mode as 'RFC2616'.

But even after setting the compliance mode to RFC2616 - we still see this issue.

Bad Message 400: Folding Header

We are hitting our server code through a proxy server in between.

We are not able to figure out what can cause this.

1

There are 1 best solutions below

1
On

First, don't extend from ServerConnector unless you fully (and I mean 100%) understand the entire HttpConnectionFactory and Endpoint behaviors within Jetty 9.x. One tiny mistake and you will break many things. This is not intended to be an extensible public API and will likely be marked final in a future version of Jetty.

If you need custom behavior, start by looking at the HttpConfiguration.Customizer, then if you still need other customization, use a custom HttpConnectionFactory instead.

Next, know that HttpCompliance is just a holder for a Set/Collection of HttpComplianceSection settings. You might want to ensure that you don't have the HttpComplianceSection.NO_FIELD_FOLDING included in your chosen HttpCompliance setting.

Finally, make sure you get those problematic clients identified and fixed, the trend in recent years to be more and more strict with HTTP, due to the numerous security issues that the relaxed behaviors (such as your line folding) cause/create. There will be a day where even your load balancer, proxy, router, etc will reject those kinds of requests too.

The obsolete RFC2616 was updated for many reasons, a large chunk was to specifically call out certain behaviors (such as line folding) as dangerous using language such as MUST NOT (a phrase defined clearly in RFC2119 Section 2) making the behavior non-optional for the updated specs. The reason the IETF deprecated Header Field line folding in 2013 was due to many security issues related to variations of Header injection vulnerabilities. With header field line folding enabled, you have no protection against response splitting, session fixation, cross-site scripting, security origin checks, and malicious redirection.

Many modern firewalls / gateways / routers / load balancers do not support header folding.

Also note that HTTP/2 does not support header folding.

If correcting those problematic clients is not possible (for whatever reason) then your only option is to not upgrade any of your server software from here on out to delay that day from occurring. (other intermediaries outside of your control can fail those requests before it even reaches you!)

You will have to address the usage obsolete header folding in your clients, as many things outside of your control are already rejecting that concept, you will hit a day where your only option left is to fix the client behavior.

Anyway, here's a standalone demo of this behavior with RFC2616 compliance mode and Line Folding.

package jetty;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URI;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.IO;

public class HttpComplianceDemo
{
    public static void main(String[] args) throws Exception
    {
        Server server = new Server();

        HttpConfiguration http_config = new HttpConfiguration();
        http_config.setSendServerVersion(true);

        HttpCompliance compliance = HttpCompliance.RFC2616;
        ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(http_config, compliance));
        connector.setPort(9090);
        server.addConnector(connector);

        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        context.addServlet(DumpServlet.class, "/dump");
        context.addServlet(DefaultServlet.class, "/");

        HandlerList handlers = new HandlerList();
        handlers.addHandler(context);
        handlers.addHandler(new DefaultHandler());

        server.setHandler(handlers);

        try
        {
            server.start();

            testLineFolding(server.getURI().resolve("/"));
        }
        finally
        {
            server.stop();
        }
    }

    private static void testLineFolding(URI serverUri) throws IOException
    {
        String host = serverUri.getHost();
        int port = serverUri.getPort();

        try (Socket socket = new Socket(host, port);
             OutputStream out = socket.getOutputStream();
             InputStream in = socket.getInputStream())
        {
            StringBuilder rawRequest = new StringBuilder();
            rawRequest.append("GET /dump HTTP/1.1\r\n");
            rawRequest.append("Host: ").append(serverUri.getRawAuthority()).append("\r\n");
            rawRequest.append("Connection: close\r\n");
            rawRequest.append("X-Foo: name\r\n"); // the header with line folding
            rawRequest.append(" extra\r\n");
            rawRequest.append("\r\n");

            byte bufRequest[] = rawRequest.toString().getBytes(UTF_8);
            System.out.println("--request--");
            System.out.println(new String(bufRequest, UTF_8));
            out.write(bufRequest);
            out.flush();

            ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
            IO.copy(in, outBuf);
            String response = new String(outBuf.toByteArray(), UTF_8);
            System.out.println("--Response--");
            System.out.println(response);
        }
    }

    public static class DumpServlet extends HttpServlet
    {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setContentType("text/plain");
            PrintWriter out = resp.getWriter();
            Enumeration<String> enNames = req.getHeaderNames();
            while (enNames.hasMoreElements())
            {
                String name = enNames.nextElement();
                String value = req.getHeader(name);
                out.printf("- [HEADER:%s=%s]\n", name, value);
            }
        }
    }
}

Output ...

2018-08-08 11:21:27.811:INFO::main: Logging initialized @338ms to org.eclipse.jetty.util.log.StdErrLog
2018-08-08 11:21:27.946:INFO:oejs.Server:main: jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 9.0.4+11
2018-08-08 11:21:28.004:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@78aab498{/,null,AVAILABLE}
2018-08-08 11:21:28.204:INFO:oejs.AbstractConnector:main: Started ServerConnector@15ff3e9e{HTTP/1.1,[http/1.1]}{0.0.0.0:9090}
2018-08-08 11:21:28.205:INFO:oejs.Server:main: Started @739ms
--request--
GET /dump HTTP/1.1
Host: 192.168.0.119:9090
Connection: close
X-Foo: name
 extra


--Response--
HTTP/1.1 200 OK
Connection: close
Date: Wed, 08 Aug 2018 16:21:28 GMT
Content-Type: text/plain;charset=iso-8859-1
Content-Length: 91
Server: Jetty(9.4.11.v20180605)

- [HEADER:Connection=close]
- [HEADER:X-Foo=name extra]
- [HEADER:Host=192.168.0.119:9090]

2018-08-08 11:21:28.307:INFO:oejs.AbstractConnector:main: Stopped ServerConnector@15ff3e9e{HTTP/1.1,[http/1.1]}{0.0.0.0:9090}
2018-08-08 11:21:28.310:INFO:oejsh.ContextHandler:main: Stopped o.e.j.s.ServletContextHandler@78aab498{/,null,UNAVAILABLE}

If you change the HttpCompliance mode to say RFC7230, you'll get a different result.

--request--
GET /dump HTTP/1.1
Host: 192.168.0.119:9090
Connection: close
X-Foo: name
 extra


--Response--
HTTP/1.1 400 Header Folding
Content-Type: text/html;charset=iso-8859-1
Content-Length: 57
Connection: close
Server: Jetty(9.4.11.v20180605)

<h1>Bad Message 400</h1><pre>reason: Header Folding</pre>