Why ModSecurity Requires the ACCEPT Header: Security Implications and Workarounds


5 views

While debugging a ModSecurity implementation, I encountered an unexpected behavior where requests without the optional ACCEPT header were being blocked. The CRS rule 960015 triggered with a CRITICAL severity, treating this as a protocol violation.

The ModSecurity Core Rule Set (CRS) enforces this requirement primarily because:

  • Legitimate browsers and API clients always include ACCEPT headers
  • Missing headers often indicate primitive bots or vulnerability scanners
  • It helps detect HTTP smuggling attempts
  • Many web frameworks expect ACCEPT headers for proper content negotiation

There are valid cases where you might want to disable this rule:

# Example of disabling the rule in modsecurity.conf
SecRuleRemoveById 960015

Or for specific endpoints:

# In your VirtualHost configuration
<Location "/api/special-endpoint">
    SecRuleRemoveById 960015
</Location>

Instead of completely disabling the rule, consider these approaches:

# 1. Set a default ACCEPT header in Apache
RequestHeader set Accept "text/html" "expr=%{REQUEST_HEADERS:Accept} == ''"

# 2. Modify the rule to be less strict
SecRuleUpdateTargetById 960015 !REQUEST_HEADERS:Accept

Use curl to verify your changes:

curl -v http://yoursite.com -H "Accept: "
curl -v http://yoursite.com/api/special-endpoint

Before disabling this protection, ensure:

  • Your application doesn't rely on content negotiation
  • You have other security measures in place
  • You're not exposing sensitive endpoints

Remember that security headers like ACCEPT help create a more predictable request profile that's easier to monitor for anomalies.


When working with mod_security's Core Rule Set (CRS), many developers encounter unexpected 400 errors due to missing Accept headers. While HTTP/1.1 technically makes this header optional, security frameworks often enforce stricter requirements.

The CRS rule 960015 checks for missing Accept headers because:

  • Legitimate browsers and API clients always include Accept headers
  • Many automated attacks omit standard headers to reduce fingerprinting
  • Absence of Accept headers often correlates with vulnerability scanners
  • It helps detect non-browser traffic attempting to mimic HTTP

Here's the relevant portion from modsecurity_crs_21_protocol_anomalies.conf:

SecRule REQUEST_HEADERS:Accept "@validateByteRange 32-126" \
    "phase:2,t:none,block,msg:'Request Missing an Accept Header',\
    id:960015,severity:'CRITICAL',tag:'PROTOCOL_VIOLATION/MISSING_HEADER'"

While generally recommended, you might need to bypass this rule for:

  • Legacy systems with custom clients
  • IoT devices with limited HTTP stack
  • Specific API endpoints with custom requirements

To disable the rule in your configuration:

SecRuleRemoveById 960015

For developers creating HTTP clients that need to work with mod_security:

// JavaScript fetch example
fetch('https://api.example.com', {
    headers: {
        'Accept': '*/*', // Wildcard accepts all content types
        'Content-Type': 'application/json'
    }
});

# Python requests example
import requests
headers = {'Accept': 'application/json'}
response = requests.get('https://api.example.com', headers=headers)

Instead of completely disabling the rule, consider these approaches:

  1. Update your rule to allow empty Accept headers for specific paths:
    SecRule REQUEST_URI "@beginsWith /api/v1/legacy" \
        "phase:1,id:1001,nolog,pass,ctl:ruleRemoveById=960015"
    
  2. Set a default Accept header at the reverse proxy level
  3. Use mod_security's anomaly scoring instead of immediate blocking

While the security benefits are clear, be aware that:

  • Header validation adds minimal overhead (typically < 0.1ms per request)
  • False positives can occur with legitimate low-level HTTP clients
  • The rule only triggers during the second phase of processing