首页 > 解决方案 > 错误消息 400:折叠页眉

问题描述

我们最近从 Jetty v 6_1_26 切换到 9_4_11。

我们遵循以下网址:http: //jetty.4.x6.nabble.com/Configuring-option-2-of-RFC-7230-paragraph-5-HTTP-header-folding-td4966330.html

并在我们的代码中进行了必要的更改以将合规模式设置为 RFC2616,因为我们需要在我们的产品中支持多行标头。

这是我们设置它的方式:

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 ));

    }

我们已经使用以下代码成功验证了合规模式正在发生变化。

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());
                    }
        }

这会将合规模式打印为“RFC2616”。

但即使将合规模式设置为 RFC2616,我们仍然会看到这个问题。

Bad Message 400: Folding Header

我们通过中间的代理服务器访问我们的服务器代码。

我们无法弄清楚是什么原因造成的。

标签: javajettymoderfc2616

解决方案


ServerConnector首先,除非您完全(我的意思是 100%)了解Jetty 9.x中的整体HttpConnectionFactory和行为,否则不要扩展。Endpoint一个小小的错误,你会破坏很多东西。这不是一个可扩展的公共 API,并且可能会在 Jetty 的未来版本中被标记为最终版本。

如果您需要自定义行为,请先查看HttpConfiguration.Customizer,然后如果您仍需要其他自定义,请改用自定义HttpConnectionFactory

接下来,要知道这HttpCompliance只是设置/集合的持有者HttpComplianceSection。您可能希望确保您没有HttpComplianceSection.NO_FIELD_FOLDING包含在您选择的HttpCompliance设置中。

最后,确保您识别并修复了那些有问题的客户端,近年来的趋势是对 HTTP 越来越严格,这是由于宽松的行为(例如您的线路折叠)导致/创建的众多安全问题。总有一天,即使您的负载均衡器、代理、路由器等也会拒绝这些类型的请求。

过时的 RFC2616 出于多种原因进行了更新,其中很大一部分是使用诸如( RFC2119 第 2 节MUST NOT中明确定义的短语)之类的语言将某些行为(例如行折叠)专门称为危险的,使得该行为对于更新后的行为是非可选的眼镜。IETF 在 2013 年弃用 Header Field 行折叠的原因是由于许多与 Header 注入漏洞变体相关的安全问题。启用标题字段行折叠后,您将无法防止响应拆分、会话固定、跨站点脚本、安全源检查和恶意重定向。

许多现代防火墙/网关/路由器/负载平衡器不支持标头折叠。

另请注意,HTTP/2 不支持标头折叠。

如果无法纠正那些有问题的客户端(无论出于何种原因),那么您唯一的选择就是不再升级您的任何服务器软件,以延迟这一天的发生。(您无法控制的其他中介可能会在这些请求到达您之前就失败!)

不得不解决客户端中使用过时的标题折叠问题,因为您无法控制的许多事情已经拒绝了该概念,您遇到唯一的选择就是修复客户端行为的那一天。

无论如何,这是一个使用 RFC2616 合规模式和行折叠的独立演示。

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);
            }
        }
    }
}

输出 ...

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}

如果您将HttpCompliance模式更改为 RFC7230,您将得到不同的结果。

--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>

推荐阅读