CVE-2023-38199 – Multiple Content-Type Headers

The OWASP ModSecurity Core Rule Set (CRS) v3.3.4 does not detect the presence of multiple HTTP “Content-Type” header fields. As a result, on some platforms, it is possible to cause a CRS installation to process an HTTP request body differently (because of the different Content-Type) to how it would be processed by a backend web application.

See the advisory at https://nvd.nist.gov/vuln/detail/CVE-2023-38199.

Update: CRS version 3.3.5 has now been released to address this vulnerability.

The Vulnerability in Detail

This issue was initially reported to the CRS project on 24 March 2023 via the ModSecurity project. We quickly established that the CRS reference platform was not affected (ModSecurity 2.9.x on Apache 2.4). This is because the Apache web server merges identically titled header fields into one, separating any different values with commas (as described in the HTTP standard).

As an example, when sending the following HTTP request that contains two Content-Type headers:

POST / HTTP/1.1
Host: example.com
User-Agent: curl/8.1.2
Accept: */*
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
Content-Length: 64

Apache merges the two Content-Type headers into one:

Content-Type: application/x-www-form-urlencoded, application/json

When multiple Content-Type headers are merged in this way, CRS detects the presence of a malformed Content-Type header and raises an alert, via rule 920470:

…[id "920470"] [msg "Illegal Content-Type header"] [data "application/x-www-form-urlencoded, application/json"] [severity "CRITICAL"]…

This confirms an initial assessment by the ModSecurity team.

The problem is that the situation is different on other platforms. While Apache (our reference platform) merges Content-Type headers together allowing this behavior to be detected, Nginx, for example, will retain each separate, identically named header: no merging occurs. Therefore, the rule 920470 will not trigger the “Illegal Content-Type header” alert on Nginx and all allow the malformed request to pass without issue (assuming that the individual headers themselves were correctly formed).

Other platforms, namely those reimplementing ModSecurity, are likely to exhibit differing behaviors, too. As we get clear statements and further information from Nginx, Coraza, and other platforms that use CRS, this blog post will be updated.

The Danger of Content-Type Ambiguity

Let’s re-examine the example request from earlier:

POST / HTTP/1.1
Host: example.com
User-Agent: curl/8.1.2
Accept: */*
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
Content-Length: 64

Assuming that the advertised 64 byte request body follows the header data, could you confidently state what the type of that request would be? Would it be a JSON request or URL-encoded form data?

What would your WAF say: would it pick the first Content-Type or the second one? What would your backend server say: would it agree with your WAF or would it pick the other Content-Type? What about intervening proxies and load balancers along the way: would they all agree?

Here’s the rub: a WAF needs to know the correct Content-Type being handled so it can correctly parse and inspect that content in a meaningful way. XML content requires an XML parser; JSON requires a JSON parser; and so on. If your WAF interprets a request in one way but your backend servers interpret it in another way then this opens the door to crafting payloads designed to evade the WAF’s scrutiny. This is referred to as an impedance mismatch. For this reason, having an ambiguous Content-Type is dangerous.

Chances are that most services are not affected since it takes an implementation gap on the WAF platform plus the described impedance mismatch plus a skilled attacker to exploit the two. Still, it can be very dangerous for some services, so you should really dig into this and check your setup.

The Fix: A New Rule for CRS v3

Update: CRS version 3.3.5 has now been released to address this vulnerability. v3.3.5 includes the rule described below so there is no need to add the rule manually from version 3.3.5 onwards.

Although CRS on Apache detects the multiple Content-Type header scenario, relying on a platform-dependent solution is clearly insufficient for those not running Apache. For this reason, we’ve introduced an explicit “Multiple Content-Type Request Headers” detection rule in CRS v4, our upcoming milestone release. Here’s a backported version of this rule for use with CRS v3. Add the rule to your configuration before the CRS include.

SecRule &REQUEST_HEADERS:Content-Type "@gt 1" \
     "id:920620,\
     phase:1,\
     block,\
     t:none,\
     msg:'Multiple Content-Type Request Headers',\
     logdata:'%{MATCHED_VAR}',\
     tag:'application-multi',\
     tag:'language-multi',\
     tag:'platform-multi',\
     tag:'attack-protocol',\
     tag:'paranoia-level/1',\
     tag:'OWASP_CRS',\
     ver:'OWASP_CRS/3.3.4',\
     severity:'CRITICAL',\
     setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

The & operator allows us to count the number of instances of a variable, so our new rule is counting how many Content-Type headers are present and will match if there are more than 1. This rule provides explicit multiple Content-Type detection to platforms that otherwise would not have it, for example Nginx.

The Fix: A New Rule for CRS v4

And here is the original CRS v4 rule.

SecRule &REQUEST_HEADERS:Content-Type "@gt 1" \
    "id:920620,\
    phase:1,\
    block,\
    t:none,\
    msg:'Multiple Content-Type Request Headers',\
    logdata:'%{MATCHED_VAR}',\
    tag:'application-multi',\
    tag:'language-multi',\
    tag:'platform-multi',\
    tag:'attack-protocol',\
    tag:'paranoia-level/1',\
    tag:'OWASP_CRS',\
    ver:'OWASP_CRS/4.0.0-rc1',\
    severity:'CRITICAL',\
    setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

We merged this new rule into the CRS v4/dev tree on 12 June 2023 via pull request 3237.

Timeline in Detail

This security finding was brought to our attention by Qi Wang and his mentor JianJun Chen. We worked with them successfully and created the new rules explained above. Things went wrong, when Qi Wang tried to reserve a CVE number for future communication with MITRE. Unfortunately, MITRE took his draft advisory and published it immediately without further confirmation.This caught us on the left foot as well and we did not have a blog post nor a release ready.

We discussed a better advisory text with Qi Wang and he submitted that to MITRE to update the published text. Mitre took that into consideration but only adopted parts of it. Apparently, this is not Qi Wang’s failure and we confirm the successful partnership we had with him.

We would like to take this opportunity to reiterate that we have a security policy and that we can be contacted at security [at] coreruleset [dot] org. We’re happy to work with the community, including on providing formal CVE identifiers when appropriate. If you are getting in touch with MITRE yourself, please be aware they are rather difficult to work with. We can help you there with our experience.

Timeline

Updates to This Page

2023-07-17 08:57 UTC: Publication
2023-07-24 19:27 UTC: Added links to the v3.3.5 release announcement

Andrew Howe