Takeshi Terada, Professional Service Div.
Today's topic is "HTTP Header Injection."
HTTP Header Injection is a class of vulnerability (or attack), which can allow attackers to add new header fields, manipulate HTTP body data, or smuggle new HTTP response (HTTP response splitting) by injecting line-breaks or newline characters (CR, LF) into HTTP response headers via manipulating request parameters and the like.
As in the title, this blog post features the relevance between LWS and HTTP Header Injection. Let me start with the explanation of LWS, as LWS is not a commonly used term.
■ What is LWS?
RFC 2616 (↓), which is a standard of HTTP/1.1, allows folding a header value into multiple lines in both request and response header. LWS (Linear White Space) is used for this purpose.
In the RFC, LWS is defined as an extended BNF:
LWS = [CRLF] 1*( SP | HT)
In a natural language, it would be: "LWS is something that begins with a line-break (CRLF) followed by one or more SP (Space 0x20) or HT (Horizontal Tab 0x09)."
An example of how LWS is used is shown below:
According to RFC 2616, the second and third line of the example above are regarded as a folded part of the header value in the first line. Thus, the second and the third line are treated as a part of the single header value named "Some-Header."
This definition of LWS comes from RFC 1945 (↓), which was a standard of HTTP/1.0 written in the 1990s. Going further back in the history, you can find the term LWS in E-mail standards in the 1980s.
■ What's the problem with LWS?
The problem with LWS is that, even today, not all browsers or servers can interpret LWS correctly, even though LWS itself is not new at all as I mentioned above. That means header values that contain LWS may be interpreted differently depending on their environments, and this difference opens a slight window for attacks.
Specifically, the problem is IE ( <=IE11), which doesn 't respect RFC when interpreting LWS. In the example above, it wrongly interprets "bbbb:2" and "ccc:3" as new header fields. This IE's behavior can cause an unexpected HTTP Header Injection vulnerability.
An attack example is shown in the following PHP program:
header("Some-Header: ". $_GET['foo']);
This program outputs the value of the "foo" parameter to the response header value by using header function. Let's suppose the program is executed in PHP (v5.3 or a similar version) and given the following query string:
（%0D -> CR, %0A -> LF, %20 -> SP）
The server outputs the following headers in response:
IE recognizes the part "Set-Cookie: xxx=123" not as a part of Some-Header, but as an independent, normal header (Set-Cookie header). This behavior is unique to IE and not seen in other major browsers. It essentially means you can successfully conduct an "IE-specific HTTP Header Injection attack" with LWS.
Note that, in this example, the attacker is just adding a new header (Set-Cookie); however, two consecutive LWS allows the attacker to also manipulate HTTP body data.
One thing I would like to emphasize here is that PHP is never non-responsive when it comes to HTTP Header Injection attacks. In fact, PHP (>=5.1.2) has a security measure against it by prohibiting line-breaks in headers. However, complying with the RFC, PHP permits a line-break occurrence when it constitutes a valid LWS.
This brings me to the point that this seemingly defective function is by PHP's design that is evidently compliant to RFC. Therefore, it may be more natural to treat it as a bug in the browser (IE) rather than a bug in PHP.
ONsec lab shed a light on LWS attacks in their blog post in 2012 (↓), but the technique seems to have been known among a relatively small number of researchers before then. I myself I came to learn this technique when I heard about it from an ex-coworker before 2012.
■ A new change - new RFC and PHP releases
To tell the truth, what I discussed above is old. As mentioned in the beginning of this post, the situation around LWS has changed from last year (2014) to this year (2015).
Firstly, a new standard of HTTP/1.1, RFC 7230 was released in 2014 (↓). In this new RFC, LWS is represented as "obs-fold." "Obs" is an abbreviation of "obsolete," so it means the new standard has deprecated LWS.
The new RFC states in 3.2.4 that senders, with a few exceptions, MUST NOT send HTTP messages containing LWS. Thus the PHP's behavior that outputs LWS turned out to be non-compliant to the standard.
Faced with the suddenly unfavorable circumstances made by the new standard, PHP reacted relatively quickly. Since the release in Feb 2015 (PHP 5.4.38, 5.5.22, 5.6.6), PHP's header function began to reject headers with LWS (↓).
PHP developers, however, treated the change as a normal bug fix rather than a security fix. The bug doesn't seem to be well understood among PHP users and security community overall, even though the security impact of the change has been discussed and known by some (↓). This is partly why I’m taking up this topic here to attract more attention.
Repeating just for a reminder, the PHP's header function which can correctly handle LWS is in 5.4.38, 5.5.22, 5.6.6 or later versions. In other words, since the function in the older versions is affected by the issue, you need to either upgrade your PHP installation or address the issue from your PHP application's side if you pass an input value to a header value without processing.
Although the LWS issue seemed to have been non-specific to PHP, the environment that we most frequently found vulnerable in our penetration testing service was PHP. In that sense, it can be said that the issue of LWS HTTP Header Injection has already headed towards resolution.
■ The new RFC and browsers
The remaining problem is on the browser's side (IE).
As mentioned earlier, the new standard (RFC 7230) basically prohibits sending HTTP messages with LWS. On the other hand, it still mandates browsers to treat LWS (obs-fold) in messages from servers as an SP (MUST).
For this reason, I can say that the IE continues to be incompliant even under the new standard. I would also go as far as to say that the incompliance has become more apparent in the new RFC as it mandates LWS's interpretation in a clearer manner (MUST).
I reported this issue with LWS to Microsoft as IE’s vulnerability in October 2014, but disappointingly got a "Won't Fix" answer from them, which meant they didn’t have a plan to fix it for the time being. According to their reply, Microsoft recognized IE's incompliance with the RFC but the behavior rarely mattered and the issue should be solved on websites’ side.
■ Other attacks
Regarding HTTP Header Injection attacks, LWS is not the only byte sequence that needs to be handled carefully.
For instance, the RFC allows using a single LF (which does not come with CR) as a header separator, and many browsers recognizes as such. Hence the web applications that handle only CRLF are vulnerable to HTTP Header Injection.
In addition, many browsers interpret a single CR as a header separator (IE, Chrome, Safari, Opera), even though there is no clear description allowing it in RFC. In the past, PHP's header function did not trigger an error with CR, which was not treated as a separator by PHP, so attacks using CR were possible when both PHP and those browsers were in use.
There is no clear answer as to which component is responsible for such problem—it could be browsers, platforms like PHP or web applications—but I reported this issue with CR as PHP’s security bug in 2011 (PHP bug #54006). Eventually another person also reported the same issue to PHP (↓), and the bug was fixed in the release of 5.3.11 in 2012.
Probably because of that effort, we are seeing less HTTP Header Injection vulnerabilities in our penetration tests these days.
■ Conclusion and countermeasure
In this post, I featured recent topics on HTTP Header Injection. HTTP standards are becoming more security-conscious over time and more platforms like PHP have adopted security measures. As the result, web applications are gradually becoming less vulnerable to HTTP Header Injection.
That being said, it is still a vulnerability that continues to be detected in our penetration testing service, and its impact can be as serious as that of Cross-Site Scripting as I stated in the beginning of this post. That’s why a certain countermeasure should be in place.
The countermeasure is relatively simple. In order to prevent either CR or LF from occurring in the data that is output to header, you can trigger an error when you detect them or, if necessary, replace them with a space.
You don’t need to be conscious of the countermeasure if your platform performs these functions on your behalf. As for PHP, recent releases of 5.4 or later versions kindly performs them for you, as I already explained. If your platform is not equipped with these functionalities, you will need to implement the measures in your web application on your own.
As a side note, under the new RFC, measures such as replacing a line-break occurrence with an LWS or accepting LWS in output data are not deemed necessary. These processes will rather make your web application not only RFC-incompliant but also vulnerable to HTTP Header Injection attacks, just like the old PHP did in the past.