# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. 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.
# ------------------------------------------------------------------------
#
# -= Paranoia Level 0 (empty) =- (apply unconditionally)
#
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:941011,phase:1,pass,nolog,skipAfter:END-REQUEST-941-APPLICATION-ATTACK-XSS"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:941012,phase:2,pass,nolog,skipAfter:END-REQUEST-941-APPLICATION-ATTACK-XSS"
#
# -= Paranoia Level 1 (default) =- (apply only when tx.detection_paranoia_level is sufficiently high: 1 or higher)
#
# In CRS v4.0, we have added REQUEST_FILENAME to the list of variables to
# be checked for XSS to catch path-based XSS exploits such as:
# /index.php/%3Csvg/onload=alert()
#
# However, the REQUEST_FILENAME is always populated (while ARGS etc. are
# only set on some requests) and we found that always checking the
# REQUEST_FILENAME has a significant performance impact.
# Therefore, we are disabling the REQUEST_FILENAME XSS checks when the
# REQUEST_FILENAME is clearly not containing special characters necessary
# for a successful XSS.
#
# Some bona-fide REQUEST_FILENAMEs will still contain special characters
# and will be checked by the rules, but it will be a much lower amount,
# and that is a trade-off we are willing to make.
#
# So, we check for XSS in REQUEST_FILENAME only if it contains
# other characters than alphanumeric characters, hyphens, underscores etc.
# typically found in filenames and paths:
#
# - ascii 20 (whitespace)
# - ascii 45-47 (- . /)
# - ascii 48-57 (0-9)
# - ascii 65-90 (A-Z)
# - ascii 95 (underscore)
# - ascii 97-122 (a-z)
#
# If just these characters are present, we remove REQUEST_FILENAME from the target
# list of all the 941xxx rules starting 941100.
#
# Please note that it would be preferable to start without REQUEST_FILENAME in the
# target list and to add it on a case to case base, but the rule language does not
# support this feature at runtime.
#
SecRule REQUEST_FILENAME "!@validateByteRange 20, 45-47, 48-57, 65-90, 95, 97-122" \
"id:941010,\
phase:1,\
pass,\
t:none,\
nolog,\
ctl:ruleRemoveTargetById=941100-941999;REQUEST_FILENAME"
#
# -=[ Libinjection - XSS Detection ]=-
#
# Ref: https://github.com/client9/libinjection
# Ref: https://speakerdeck.com/ngalbreath/libinjection-from-sqli-to-xss
#
# -=[ Targets ]=-
#
# 941100: PL1 : REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|
# REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|
# ARGS_NAMES|ARGS|XML:/*
#
# 941101: PL2 : REQUEST_FILENAME|REQUEST_HEADERS:Referer
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|ARGS_NAMES|ARGS|XML:/* "@detectXSS" \
"id:941100,\
phase:2,\
block,\
t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls,\
msg:'XSS Attack Detected via libinjection',\
logdata:'Matched Data: XSS data found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-xss',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
#
# -=[ XSS Filters - Category 1 ]=-
# http://xssplayground.net23.net/xssfilter.html
# script tag based XSS vectors, e.g.,
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_FILENAME|REQUEST_HEADERS:User-Agent|REQUEST_HEADERS:Referer|ARGS_NAMES|ARGS|XML:/* "@rx (?i)
#
# This enables an attacker to craft a string that will be delivered in a form that a browser will execute as script
# while being ignored by input filters.
#
# This rule looks for start tag sequene that looks like "<...>" (checks fo hex and plain to be sure).
# Because the bytes matched occur in many different languages encoded as multibyte characters (e.g. UTF-8)
# (e.g. German umlauts, Russian characters) this isn't very helpful and can cause many false positives. We, therefore,
# use a chained rule to also look for an end tag sequence that looks like "". Only if the chained rule matches will
# the request be blocked.
#
# This is of course still not perfect but should at least make it harder to hide most tags using this technique while
# requiring very specific patterns in a language to match, which should get rid of most false positives.
# These rules would, for example, not guard against an element without an end tag, e.g. "".
#
# US-ASCII on Wikipedia: https://en.wikipedia.org/wiki/ASCII
# ISO 8859-1 on Wikipedia: https://en.wikipedia.org/wiki/ISO/IEC_8859-1
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx \xbc[^\xbe>]*[\xbe>]|<[^\xbe]*\xbe" \
"id:941310,\
phase:2,\
block,\
capture,\
t:none,t:lowercase,t:urlDecode,t:htmlEntityDecode,t:jsDecode,\
msg:'US-ASCII Malformed Encoding XSS Filter - Attack Detected',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-tomcat',\
tag:'attack-xss',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
chain"
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx (?:\xbc\s*/\s*[^\xbe>]*[\xbe>])|(?:<\s*/\s*[^\xbe]*\xbe)" \
"t:none,t:lowercase,t:urlDecode,t:htmlEntityDecode,t:jsDecode,\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
#
# https://nedbatchelder.com/blog/200704/xss_with_utf7.html
# UTF-7 encoding XSS filter evasion for IE.
# Reported by Vladimir Ivanov
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx \+ADw-.*(?:\+AD4-|>)|<.*\+AD4-" \
"id:941350,\
phase:2,\
block,\
capture,\
t:none,t:urlDecode,t:htmlEntityDecode,t:jsDecode,\
msg:'UTF-7 Encoding IE XSS - Attack Detected',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-internet-explorer',\
tag:'attack-xss',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
#
# Defend against JSFuck and Hieroglyphy obfuscation of Javascript code
#
# https://en.wikipedia.org/wiki/JSFuck
# https://github.com/alcuadrado/hieroglyphy
#
# These JS obfuscations mostly aim for client side XSS exploits, hence the
# integration of this rule into the XSS rule group. But serverside JS could
# also be attacked via these techniques.
#
# Detection pattern / Core elements of JSFuck and Hieroglyphy are the
# following two items:
# !![]
# !+[]
#
# ModSecurity always transforms "+" into " " with query strings and the
# URLENCODE body processor (but not for JSON). So we need to check for
# the following patterns:
# !![]
# !+[]
# ! []
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx ![!+ ]\[\]" \
"id:941360,\
phase:2,\
block,\
capture,\
t:none,\
msg:'JSFuck / Hieroglyphy obfuscation detected',\
logdata:'Matched Data: Suspicious payload found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'attack-xss',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242/63',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
#
# Prevent 941180 bypass by using JavaScript global variables
# Refer to: https://www.secjuice.com/bypass-xss-filters-using-javascript-global-variables/
#
# Examples:
# - /?search=/?a=";+alert(self["document"]["cookie"]);//
# - /?search=/?a=";+document+/*foo*/+.+/*bar*/+cookie;//
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx (?:self|document|this|top|window)\s*(?:/\*|[\[)]).+?(?:\]|\*/)" \
"id:941370,\
phase:2,\
block,\
capture,\
t:none,t:urlDecodeUni,t:compressWhitespace,\
msg:'JavaScript global variable found',\
logdata:'Matched Data: Suspicious JS global variable found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'attack-xss',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242/63',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
#
# JavaScript methods which take code as a string types are considered unsafe.
# Unsafe JS functions like eval(), setInterval(), setTimeout()
# Unsafe JS constructor new Function()
# https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#dangerous-contexts
# https://snyk.io/blog/5-ways-to-prevent-code-injection-in-javascript-and-node-js/
#
# Regular expression generated from regex-assembly/941390.ra.
# To update the regular expression run the following shell script
# (consult https://coreruleset.org/docs/development/regex_assembly/ for details):
# crs-toolchain regex update 941390
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx (?i)\b(?:eval|set(?:timeout|interval)|new[\s\v]+Function|a(?:lert|tob)|btoa)[\s\v]*\(" \
"id:941390,\
phase:2,\
block,\
capture,\
t:none,t:htmlEntityDecode,t:jsDecode,\
msg:'Javascript method detected',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'attack-xss',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
#
# JavaScript function without parentheses
# Reference: https://portswigger.net/research/the-seventh-way-to-call-a-javascript-function-without-parentheses
#
# Example Payloads:
# [].sort.call`${alert}1337`
# [].map.call`${eval}\\u{61}lert\x281337\x29`
# Reflect.apply.call`${navigation.navigate}${navigation}${[name]}`
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx ((?:\[[^\]]*\][^.]*\.)|Reflect[^.]*\.).*(?:map|sort|apply)[^.]*\..*call[^`]*`.*`" \
"id:941400,\
phase:2,\
block,\
capture,\
t:none,t:urlDecodeUni,t:compressWhitespace,\
msg:'XSS JavaScript function without parentheses',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'attack-xss',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 2" "id:941013,phase:1,pass,nolog,skipAfter:END-REQUEST-941-APPLICATION-ATTACK-XSS"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 2" "id:941014,phase:2,pass,nolog,skipAfter:END-REQUEST-941-APPLICATION-ATTACK-XSS"
#
# -= Paranoia Level 2 =- (apply only when tx.detection_paranoia_level is sufficiently high: 2 or higher)
#
#
# This is a stricter sibling of rule 941100.
#
SecRule REQUEST_FILENAME|REQUEST_HEADERS:Referer "@detectXSS" \
"id:941101,\
phase:1,\
block,\
capture,\
t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls,\
msg:'XSS Attack Detected via libinjection',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-xss',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
tag:'paranoia-level/2',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl2=+%{tx.critical_anomaly_score}'"
#
# -=[ XSS Filters - Category 2 ]=-
# XSS vectors making use of event handlers like onerror, onload etc, e.g.,
#
# We are not listing all the known event handlers like rule 941160, but we
# limit the alerts to keywords of 3-25 characters after the prefix ("on").
#
# The shortest known event is "onget". The longest known event is "onmozorientationchange"
# with 23 chars after the prefix. 25 chars adds a little bit of safety.
#
# This rule has been moved to PL2 since it has a tendency to trigger on random input.
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|REQUEST_HEADERS:Referer|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx (?i)[\s\"'`;/0-9=\x0B\x09\x0C\x3B\x2C\x28\x3B]on[a-zA-Z]{3,25}[\s\x0B\x09\x0C\x3B\x2C\x28\x3B]*?=[^=]" \
"id:941120,\
phase:2,\
block,\
capture,\
t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls,\
msg:'XSS Filter - Category 2: Event Handler Vector',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-xss',\
tag:'paranoia-level/2',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl2=+%{tx.critical_anomaly_score}'"
#
# -=[ XSS Filters - Category 5 ]=-
# HTML attributes - src, style and href
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@rx (?i)\b(?:s(?:tyle|rc)|href)\b[\s\S]*?=" \
"id:941150,\
phase:2,\
block,\
capture,\
t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls,\
msg:'XSS Filter - Category 5: Disallowed HTML Attributes',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-xss',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
tag:'paranoia-level/2',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl2=+%{tx.critical_anomaly_score}'"
#
# [Deny List Keywords from Node-Validator]
# https://raw.github.com/chriso/node-validator/master/validator.js
# This rule is a stricter sibling of 941180 (PL1)
#
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|REQUEST_FILENAME|XML:/* "@contains -->" \
"id:941181,\
phase:2,\
block,\
capture,\
t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:lowercase,t:removeNulls,\
msg:'Node-Validator Deny List Keywords',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-xss',\
tag:'paranoia-level/2',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/242',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.xss_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl2=+%{tx.critical_anomaly_score}'"
#
# -=[ XSS Filters from IE ]=-
# Detect tags that are the most common direct HTML injection points.
#
#
#