Why text/plain is evil for Web Application Firewall and Input validation
When sending an HTTP request that includes a message body, it’s essential to specify the type of the data being sent. This is because the server (or the web application) needs to know how to interpret the content of the message body to process it correctly. Moreover, a Web Application Firewall needs to know which body processor to enable in order to parse the message body and validate all user inputs.
Request header Content-Type
The “content-type” request header is used to indicate the type of data that is being sent in the message body of a HTTP request. It specifies the MIME type of the content, which is a standardized format used to represent different types of data, such as text, images, or audio. The content-type header is included in the request message to provide the server (or the web application) with the necessary information to correctly interpret the data in the message body.
For example, a JSON API HTTP request, usually has the following headers and body:
POST /login HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 35
{"username":"foo","password":"bar"}
In this case both the server and the web application knows what type of message body is being sent and how to parse it. There’re many applications that don’t check the “content-type” header before decoding a JSON message body. This means that a user can send a content-type header different from “application/json” which can lead to security vulnerabilities, unexpected behavior or input validation bypass when using a Web Application Firewall.
When a Web Application Firewall receives a request header content-type, it usually select which body processor to use to parse the request body. For example ModSecurity have 3 different body processor: Urlencode, JSON and XML.
What is ModSecurity?
ModSecurity is a web application firewall that provides a layer of security between web applications and the internet. It’s an open-source software module that can be loaded into the web server (e.g., Apache or Nginx) to monitor and filter HTTP traffic.
ModSecurity is designed to protect web applications from various attacks, including SQL injection, cross-site scripting (XSS), remote file inclusion, and other web-based attacks. It works by examining incoming HTTP requests and responses and applying rules to identify and block malicious traffic.
ModSecurity uses a rule-based language to define security policies that determine how traffic is handled. These rules can be customized to meet the specific needs of an application or environment. ModSecurity also provides a range of built-in rules and plugins to detect and prevent common web-based attacks.
In addition to its core functionality as a web application firewall, ModSecurity also provides advanced logging and reporting features to help administrators monitor and analyze traffic. It can log data at various levels of detail, from basic traffic information to full HTTP request and response bodies.
ModSecurity is widely used in the industry and is supported by a large community of developers and users. Its modular architecture and extensive configuration options make it a flexible and powerful tool for securing web applications.
More information at https://github.com/spiderLabs/ModSecurity
As reported in the OWASP Core Rule Set configuration file:
Bypass Warning: some applications may not rely on the content-type request header in order to parse the request body. This could make an attacker able to send malicious URLENCODED/JSON/XML payloads without being detected by the WAF. Allowing request content-type that doesn’t activate any body processor (for example: “text/plain”, “application/x-amf”, “application/octet-stream”, etc…) could lead to a WAF bypass. For example, a malicious JSON payload submitted with a “text/plain” content type may still be interpreted as JSON by a backend application but would not trigger the JSON body parser at the WAF, leading to a bypass.
What is the OWASP Core Rule Set project?
the OWASP® ModSecurity Core Rule Set (CRS) is a set of generic attack detection rules for use with ModSecurity or compatible web application firewalls. The CRS aims to protect web applications from a wide range of attacks, including the OWASP Top Ten, with a minimum of false alerts. The CRS provides protection against many common attack categories, including:
SQL Injection (SQLi)
Cross Site Scripting (XSS)
Local File Inclusion (LFI)
Remote File Inclusion (RFI)
PHP Code Injection
Java Code Injection HTTPoxy
Shellshock
Unix/Windows Shell Injection
Session Fixation
Scripting/Scanner/Bot Detection
Metadata/Error Leakages
More information at https://coreruleset.org/
Bypass
Now, let say that we would like to validate the username field in JSON body, because we want to prevent login using admin
username. With ModSecurity we can create a rule like this:
SecRule ARGS:json.username "@rx ^admin$" "id:123,\
phase:2,\
t:lowercase,\
deny,\
msg:'Blocked admin login'"
This rule can be translated as “if the lowercase version of the username
JSON field is admin
then block the request”. ARGS:json.username is set by the ModSecurity JSON body processor that parse the request body and create related variables inside the ARGS collection. This is possible only if the request content-type is “application/json”.
Now, let say that the web application doesn’t check if the content-type is JSON and parse anyway the request body. In this case, we can easily bypass the Web Application Firewall by sending a generic content-type like text/plain:
POST /login HTTP/1.1
Host: api.example.com
Content-Type: text/plain
Content-Length: 35
{"username":"admin","password":"bar"}
Now let’s think about the OWASP Core Rule Set. Many rules provided by the opensource project inspect ARGS value looking for injections (such as HTML/JavaScript injection, SQL injection, etc…). This is possible, as said before, thanks to the JSON body processor. For example, the following SQL injection attempt:
POST /login HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 48
{"username":"admin","password":"foo' OR 1=1--"}
The password value contains a classic SQL injection test. If we send this request against a ModSecurity running the OWASP Core Rule Set at paranoia level 1, it correctly block our request:
curl -H "x-format-output: txt-matched-rules" \
-d $'{"username":"admin","password":"foo\' OR 1=1--"}' \
-H 'Content-Type: application/json' \
'https://sandbox.coreruleset.org/'
942100 PL1 SQL Injection Attack Detected via libinjection
Suppose now that your web application doesn’t check the Content-Type request header value, and it decodes anyway the request body. If the Web Application Firewall doesn’t blocks content types without any specific body processor available, it will fall back to the URLENCODE one, leading to possible bypass like:
POST /login HTTP/1.1
Host: api.example.com
Content-Type: text/plain
Content-Length: 48
{"username":"admin","password":"foo' OR 1=1--"}
In conclusion
setting the correct “content-type” header for URL-encoded, JSON, or XML requests is crucial for ensuring the security and reliability of web applications. By specifying the content-type, all third parties can accurately interpret the data in the request body, preventing unexpected behavior and potential security vulnerabilities.
Web Application Firewalls, such as ModSecurity, rely on accurate content-type information to detect and prevent attacks. If the content-type is missing or incorrect, attackers can potentially bypass the firewall’s protection by disguising malicious payloads as legitimate data.
Setting the correct content-type is a critical step in securing web applications. By doing so, developers can prevent unexpected behavior and potential security vulnerabilities, while web application firewalls can more effectively detect and block attacks. By working together, developers and administrators can help ensure the security and reliability of web applications in an increasingly complex threat landscape.