A curious bug

I spent some time yesterday debugging a very curious bug.

One of the apps that my team maintains is a lightweight HTTP proxy written in Java. It acts as a Route Service for apps running in Cloud Foundry.

Today, a user reported that Cross-Origin Request (CORS) functionality in their application stopped working once they inserted this proxy into their request flow. Their analysis showed that the Origin header, which is critical to the CORS flow, wasn't reaching their application. Our proxy was filtering out this Origin header.

This report led to a very interesting debugging session which revealed a rather surprising root cause. Our application uses HttpURLConnection, which is the built-in Java class for making HTTP connections. It turns out that this class keeps a list of restrictedHeaders that it does not allow client code to specify. Here's the list:

String[] restrictedHeaders = {
    /* Restricted by XMLHttpRequest2 */
    //"Accept-Charset",
    //"Accept-Encoding",
    "Access-Control-Request-Headers",
    "Access-Control-Request-Method",
    "Connection", /* close is allowed */
    "Content-Length",
    //"Cookie",
    //"Cookie2",
    "Content-Transfer-Encoding",
    //"Date",
    //"Expect",
    "Host",
    "Keep-Alive",
    "Origin",
    // "Referer",
    // "TE",
    "Trailer",
    "Transfer-Encoding",
    "Upgrade",
    //"User-Agent",
    "Via"
};

If the calling code does set a value for one of these headers, it is either ignored or stripped, depending on the header.

Do you notice anything interesting about this list of headers? That first comment -- Restricted by XMLHttpRequest2 -- gives it away. This is the same list of headers that a web browser restricts application code from specifying!

From https://fetch.spec.whatwg.org/#forbidden-header-name:

A forbidden header name is a header name that is a byte-case-insensitive match for one of:

`Accept-Charset`
`Accept-Encoding`
`Access-Control-Request-Headers`
`Access-Control-Request-Method`
`Connection`
`Content-Length`
`Cookie`
`Cookie2`
`Date`
`DNT`
`Expect`
`Host`
`Keep-Alive`
`Origin`
`Referer`
`TE`
`Trailer`
`Transfer-Encoding`
`Upgrade`
`Via`

So why is this Java class enforcing restrictions that originated in the web browser world?

Applets

You may remember that not too long ago, Java applets were a big thing. They were the dominant mechanism for delivering rich content inside web browsers, before Flash came along.

So since applets were essentially Java code running inside a web browser, they faced similar security challenges as regular web pages. So it made sense to adopt some of the same security solutions as the browsers did - including restricting the headers that application code can specify!

Was my reasoning correct? Was this change specifically related to applets?

The JDK commit message that introduced this change in 2010 doesn't reveal much:

6980004: limit HTTP request cookie headers in HttpURLConnection

I could find two CVEs that obliquely reference this issue. Here's the description of CVE-2010-3573:

Unspecified vulnerability in the Networking component in Oracle Java SE ... allows remote attackers to affect confidentiality, integrity, and availability via unknown vectors. ... this is related to missing validation of request headers in the HttpURLConnection class when they are set by applets, which allows remote attackers to bypass the intended security policy.

So there's definitely a connection and this restriction makes sense in the applet execution context.

What is unclear to me is why didn't they enforce this restriction only in an applet context. Why was this made the default?


Markdown formatting supported