In Russian: https://blog.deteact.com/ru/bitrix-waf-bypass/
UPD: CVE-2020-13758 assigned
Sometimes when exploiting reflected XSS the input parameters get injected directly into the body of the <script> tag. Typically, this means that the exploit is trivial: HTML entity encoding will not prevent it, and many firewalls (including now obsolete Chrome XSS Auditor) won’t either. But CMS Bitrix has its own built-in proactive filter (WAF) for this case, and it operates similar to XSS Auditor.
While fuzzing one of the Mail.ru services eligible for the Bug Bounty I encountered an entry point where the GET parameter was reflected in the body of <script>…</script> tag. But it was not possible to make a simple PoC because the application was built using Bitrix and the WAF module was activated.
Any attempts to insert an interesting code lead to the whole script body being replaced by the placeholder <!— deleted by Bitrix WAF —>.
For testing purposes, we deployed a Bitrix CMS application with WAF module activated and added the following code to one of the pages (/waf-bypass.php):
If a single quote (which terminates the JS string) and an alert call (as well as any other function) are passed to the vulnerable page parameter, the WAF cuts out the entire script:
However, during the fuzzing we found out that the mitigation does not work when the vulnerable parameter contains a null byte (%00):
Thus, we get a full payload for XSS exploitation:
This is what the result looks like on the page:
The root cause
The mitigation is implemented in the post-filtration module to protect against XSS. The module works similarly to the XSS Auditor and tries to find the user input (like GET or POST parameters) in the body of the script tags on the page.
For some reason this module cuts out the null bytes from the input parameter values, so in our case the script tag body can’t be matched against input parameter value since the body contains \x00, and the parameter value does not.
Vulnerable code line is located in ./bitrix/modules/security/classes/general.post_filter.php/post_filter.php where null byte chr(0) is cut out in the addVariable method:
The isDangerBody function tries to find user input in the executable script body, and this is where the original $body value and the array of parameters (with \x00 removed) are passed to the findInArray function:
Remember that WAFs are almost always bypassable and the may contain weaknesses and vulnerabilities themselves. You should not rely on third-party mitigation solutions and firewalls, you should build a secure development process and regularly conduct penetration testing of applications.
Specifically in this case you can remove the str_replace call from the addVariable function (or to apply the same modification to the $body variable in the isDangerBody function) to correct the weakness in the WAF itself.