# ------------------------------------------------------------------------ # OWASP ModSecurity Core Rule Set ver.3.1.0 # Copyright (c) 2006-2018 Trustwave and contributors. All rights reserved. # # The OWASP ModSecurity Core Rule Set is distributed under # Apache Software License (ASL) version 2 # Please see the enclosed LICENSE file for full details. # ------------------------------------------------------------------------ # # Anti-Automation rules to detect Denial of Service attacks. # # Description of mechanics: # When a request hits a non-static resource (TX:STATIC_EXTENSIONS), then a counter for the IP # address is being raised (IP:DOS_COUNTER). If the counter (IP:DOS_COUNTER) hits a limit # (TX:DOS_COUNTER_THRESHOLD), then a burst is identified (IP:DOS_BURST_COUNTER) and the # counter (IP:DOS_COUNTER) is reset. The burst counter expires within a timeout period # (TX:DOS_BURST_TIME_SLICE). # If the burst counter (IP:DOS_BURST_COUNTER) is greater equal 2, then the blocking flag # is being set (IP:DOS_BLOCK). The blocking flag (IP:DOS_BLOCK) expires within a timeout # period (TX:DOS_BLOCK_TIMEOUT). All this counting happens in phase 5. # There is a stricter sibling to this rule (912170) in paranoia level 2, where the # burst counter check (IP:DOS_BURST_COUNTER) hits at greater equal 1. # # The blocking is done in phase 1: When the blocking flag is encountered (IP:DOS_BLOCK), # then the request is dropped without sending a response. If this happens, then a # counter is # raised (IP:DOS_BLOCK_COUNTER). # When an IP address is blocked for the first time, then the blocking is reported in a # message and a flag (IP:DOS_BLOCK_FLAG) is set. This flag expires in 60 seconds. # When an IP address is blocked and the flag (IP:DOS_BLOCK_FLAG) is set, then the # blocking is not being reported (to prevent a flood of alerts). When the flag # (IP:DOS_BLOCK_FLAG) has expired and a new request is being blocked, then the # counter (IP:DOS_BLOCK_COUNTER) is being reset to 0 and the block is being treated # as the first block (-> alert). # In order to be able to display the counter (IP:DOS_BLOCK_COUNTER) and resetting # it at the same time, we copy the counter (IP:DOS_BLOCK_COUNTER) into a different # variable (TX:DOS_BLOCK_COUNTER), which is then displayed in turn. # # Variables: # IP:DOS_BLOCK Flag if an IP address should be blocked # IP:DOS_BLOCK_COUNTER Counter of blocked requests # IP:DOS_BLOCK_FLAG Flag keeping track of alert. Flag expires after 60 seconds. # IP:DOS_BURST_COUNTER Burst counter # IP:DOS_COUNTER Request counter (static resources are ignored) # TX:DOS_BLOCK_COUNTER Copy of IP:DOS_BLOCK_COUNTER (needed for display reasons) # TX:DOS_BLOCK_TIMEOUT Period in seconds a blocked IP will be blocked # TX:DOS_COUNTER_THRESHOLD Limit of requests, where a burst is identified # TX:DOS_BURST_TIME_SLICE Period in seconds when we will forget a burst # TX:STATIC_EXTENSIONS Paths which can be ignored with regards to DoS # # As a precondition for these rules, please set the following three variables: # - TX:DOS_BLOCK_TIMEOUT # - TX:DOS_COUNTER_THRESHOLD # - TX:DOS_BURST_TIME_SLICE # # And make sure that TX:STATIC_EXTENSIONS is also set. # # # -= Paranoia Level 0 (empty) =- (apply unconditionally) # # # Skip if variables defining DoS protection are not set # SecRule &TX:dos_burst_time_slice "@eq 0" \ "id:912100,\ phase:1,\ pass,\ t:none,\ nolog,\ chain,\ skipAfter:END-DOS-PROTECTION-CHECKS" SecRule &TX:dos_counter_threshold "@eq 0" \ "chain" SecRule &TX:dos_block_timeout "@eq 0" SecRule &TX:dos_burst_time_slice "@eq 0" \ "id:912110,\ phase:5,\ pass,\ t:none,\ nolog,\ chain,\ skipAfter:END-DOS-PROTECTION-CHECKS" SecRule &TX:dos_counter_threshold "@eq 0" \ "chain" SecRule &TX:dos_block_timeout "@eq 0" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 1" "id:912011,phase:1,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 1" "id:912012,phase:2,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" # # -= Paranoia Level 1 (default) =- (apply only when tx.executing_paranoia_level is sufficiently high: 1 or higher) # # # -=[ Anti-Automation / DoS Protection : Block ]=- # ################################################### # TEMPORARY WORKAROUND FOR not working `expirevar` ################################################### # Expire variables SecRule &IP:DOS_BLOCK_FLAG_EXPIRE_TIMESTAMP "@eq 1" \ "id:912180,\ phase:1,\ pass,\ t:none,\ nolog,\ chain" SecRule IP:DOS_BLOCK_FLAG_EXPIRE_TIMESTAMP "@lt %{TIME_EPOCH}" \ "setvar:'!ip.dos_block_flag',\ setvar:'!ip.dos_block_flag_expire_timestamp'" SecRule &IP:DOS_BURST_COUNTER_EXPIRE_TIMESTAMP "@eq 1" \ "id:912181,\ phase:1,\ pass,\ t:none,\ nolog,\ chain" SecRule IP:DOS_BURST_COUNTER_EXPIRE_TIMESTAMP "@lt %{TIME_EPOCH}" \ "setvar:'!ip.dos_burst_counter',\ setvar:'!ip.dos_burst_counter_expire_timestamp'" SecRule &IP:DOS_BLOCK_EXPIRE_TIMESTAMP "@eq 1" \ "id:912182,\ phase:1,\ pass,\ t:none,\ nolog,\ chain" SecRule IP:DOS_BLOCK_EXPIRE_TIMESTAMP "@lt %{TIME_EPOCH}" \ "setvar:'!ip.dos_block',\ setvar:'!ip.dos_block_expire_timestamp'" ####################################################################### # # Block and track # of requests and log # SecRule IP:DOS_BLOCK "@eq 1" \ "id:912120,\ phase:1,\ drop,\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ msg:'Denial of Service (DoS) attack identified from %{tx.real_ip} (%{tx.dos_block_counter} hits since last alert)',\ chain" SecRule &IP:DOS_BLOCK_FLAG "@eq 0" \ "setvar:'ip.dos_block_counter=+1',\ setvar:'ip.dos_block_flag=1',\ setvar:'tx.dos_block_counter=%{ip.dos_block_counter}',\ setvar:'ip.dos_block_counter=0',\ expirevar:'ip.dos_block_flag=60',\ setvar:'ip.dos_block_flag_expire_timestamp=%{TIME_EPOCH}',\ setvar:'ip.dos_block_flag_expire_timestamp=+60'" # # Block and track # of requests but don't log # SecRule IP:DOS_BLOCK "@eq 1" \ "id:912130,\ phase:1,\ drop,\ t:none,\ nolog,\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ setvar:'ip.dos_block_counter=+1'" # # -=[ Anti-Automation / DoS Protection: Count requests ]=- # # # Skip if we have blocked the request # SecRule IP:DOS_BLOCK "@eq 1" \ "id:912140,\ phase:5,\ pass,\ t:none,\ nolog,\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ skipAfter:END-DOS-PROTECTION-CHECKS" # # DOS Counter: Count the number of requests to non-static resources # SecRule REQUEST_BASENAME "@rx .*?(\.[a-z0-9]{1,10})?$" \ "id:912150,\ phase:5,\ pass,\ capture,\ t:none,t:lowercase,\ nolog,\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ setvar:'tx.extension=/%{TX.1}/',\ chain" SecRule TX:EXTENSION "!@within %{tx.static_extensions}" \ "setvar:'ip.dos_counter=+1'" # # Check DOS Counter # If the request count is greater than or equal to user settings, # we raise the burst counter. This happens via two separate rules: # - 912160: raise from 0 to 1 # - 912161: raise from 1 to 2 # # This approach with two rules avoids raising the burst counter # from 0 to 2 via two concurrent requests. We do not raise the # burst counter beyond 2. # # SecRule IP:DOS_COUNTER "@ge %{tx.dos_counter_threshold}" \ "id:912160,\ phase:5,\ pass,\ t:none,\ nolog,\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ chain" SecRule &IP:DOS_BURST_COUNTER "@eq 0" \ "setvar:'ip.dos_burst_counter=1',\ setvar:'!ip.dos_counter',\ expirevar:'ip.dos_burst_counter=%{tx.dos_burst_time_slice}',\ setvar:'ip.dos_burst_counter_expire_timestamp=%{TIME_EPOCH}',\ setvar:'ip.dos_burst_counter_expire_timestamp=+%{tx.dos_burst_time_slice}'" SecRule IP:DOS_COUNTER "@ge %{tx.dos_counter_threshold}" \ "id:912161,\ phase:5,\ pass,\ t:none,\ nolog,\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ chain" SecRule &IP:DOS_BURST_COUNTER "@ge 1" \ "setvar:'ip.dos_burst_counter=2',\ setvar:'!ip.dos_counter',\ expirevar:'ip.dos_burst_counter=%{tx.dos_burst_time_slice}',\ setvar:'ip.dos_burst_counter_expire_timestamp=%{TIME_EPOCH}',\ setvar:'ip.dos_burst_counter_expire_timestamp=+%{tx.dos_burst_time_slice}'" # # Check DOS Burst Counter and set Block # Check the burst counter - if greater than or equal to 2, then we set the IP # block variable for a given expiry and issue an alert. # SecRule IP:DOS_BURST_COUNTER "@ge 2" \ "id:912170,\ phase:5,\ pass,\ t:none,\ log,\ msg:'Potential Denial of Service (DoS) Attack from %{tx.real_ip} - # of Request Bursts: %{ip.dos_burst_counter}',\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ setvar:'ip.dos_block=1',\ expirevar:'ip.dos_block=%{tx.dos_block_timeout}',\ setvar:'ip.dos_block_expire_timestamp=%{TIME_EPOCH}',\ setvar:'ip.dos_block_expire_timestamp=+%{tx.dos_block_timeout}'" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 2" "id:912013,phase:1,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 2" "id:912014,phase:2,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 2" "id:912019,phase:5,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" # # -= Paranoia Level 2 =- (apply only when tx.executing_paranoia_level is sufficiently high: 2 or higher) # # # Check DOS Burst Counter and set Block # Check the burst counter - if greater than or equal to 1, then we set the IP # block variable for a given expiry and issue an alert. # # This is a stricter sibling of rule 912170. # SecRule IP:DOS_BURST_COUNTER "@ge 1" \ "id:912171,\ phase:5,\ pass,\ t:none,\ log,\ msg:'Potential Denial of Service (DoS) Attack from %{tx.real_ip} - # of Request Bursts: %{ip.dos_burst_counter}',\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-dos',\ tag:'paranoia-level/2',\ setvar:'ip.dos_block=1',\ expirevar:'ip.dos_block=%{tx.dos_block_timeout}',\ setvar:'ip.dos_block_expire_timestamp=%{TIME_EPOCH}',\ setvar:'ip.dos_block_expire_timestamp=+%{tx.dos_block_timeout}'" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 3" "id:912015,phase:1,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 3" "id:912016,phase:2,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" # # -= Paranoia Level 3 =- (apply only when tx.executing_paranoia_level is sufficiently high: 3 or higher) # SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 4" "id:912017,phase:1,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 4" "id:912018,phase:2,pass,nolog,skipAfter:END-REQUEST-912-DOS-PROTECTION" # # -= Paranoia Level 4 =- (apply only when tx.executing_paranoia_level is sufficiently high: 4 or higher) # # # -= Paranoia Levels Finished =- # SecMarker "END-REQUEST-912-DOS-PROTECTION" SecMarker "END-DOS-PROTECTION-CHECKS"