Grammarly is the unicorn company that announced its open bug bounty program last September. Since that time, many security researchers posted their submissions and got paid well. Some of Grammarly’s issues are also useful for others. Like the recent XSS, that also bypasses an AWS WAF.

The recent XSS report is a bit different among others. First of all, it was submitted by Frans Rosen, one of the top HackerOne hackers. He is the 6th for the all-time rank.

Secondly, the report was paid for $3000 unlike tons of $50-100 XSSes on a platform.

The report title is: “Config override using non-validated query parameter allows at least reflected XSS by injecting configuration into state”

screenshot Frans report

This security issue is an excellent example of in-depth JavaScript research that Frans did.

Moreover, for some reason, an XSS payload that Frans used to validate this Grammarly vulnerability bypasses AWS WAF.

Let’s understand why.

The payload look like a very usual XSS for the first glance:{%22account%22:{%22subscription%22:%22javascript:alert(document.domain)//%22},%22api%22:{%22redirect%22:%22javascript:alert(document.domain)//%22}}

It consists of a JSON-encoded config parameter that vulnerable application parse. JSON attributes subscription and redirect contains an ordinary XSS sample of alert() function call. And if we will send this request to check how does the mod_security WAF detects it, we will get the following:

2021/03/03 01:10:44 [error] 41#41: *1 [client] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `28' ) [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "80"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 28)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.2.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname ""] [uri "/docs/new"] [unique_id "161473384455.381362"] [ref ""], client:, server: localhost, request: "GET /docs/new?config={%22account%22:{%22subscription%22:%22javascript:alert(document.domain)//%22},%22api%22:{%22redirect%22:%22javascript:alert(document.domain)//%22}} HTTP/1.1", host: "localhost:8080"

At the same time, AWS WAF will pass this payload with no blocking:

payload with JSON

But, if we will remove the JSON object and use the same payload in a plain text way, AWS WAF will block it:

payload without JSON

An attentive reader may notice that we modified the payload by adding onerror attribute to make this payload work in HTML attribute injection way. That’s true, but now we can add the JSON prefix back and see what happens:

 payload with JSON prefix

It passed! We basically found that adding JSON prefixes before payloads makes them invisible for AWS WAF. Let’s understand what happened and why.

We initially decided that the reason for this bypass is behind the JSON parser. As we already discussed recently in a post WAF JSON decoding capability required to protect against API threats like CVE-2020-13942 Apache Unomi RCE

But it was not even close since AWS WAF has no JSON parsing capabilities at all.

After minifying Grammarly payload, we will get that the following request will be blocked by AWS WAF:

After minifying Grammarly payload

But adding just one additional double quote into there bypass the WAF:

payload adding one additional double quote

The reason is not in JSON-like prefix, but the double-quote itself. We can easily remove the bracer charter and get the same bypass result with the payload ""onerror=javascript:alert('I-LOVE-AWS-WAF!').

It seems irrational but adding any numbers of double quotes before an XSS payloads bypass AWS WAF. We tested on 1500 rules enabled

tested on 1500 rules enabled

Please leave a comment if you have an idea why. Thanks for reading!