Cross-site scripting (XSS) is the unintentional execution of remote code by a web page.
The following examples will clarify what it means.
Imagine you have a plugin that adds a contact form to your page.
The code to output the form will be something that looks like this:
<form method="post" action="<?php echo $_SERVER["PHP_SELF"]"; ?>"> ... </form>
Only developers who are also criminals intentionally don’t neutralize the value of the superglobal variable $_SERVER, but we are all humans, and the plugin author may forget to do it.
In your installation, you may have a plugin that has this kind of vulnerability to XSS.
Why is that code vulnerable?
The superglobal variable $_SERVER[“PHP_SELF”] gets the URL, so hackers can use it to enter “/” and then some Cross-Site Scripting code.
This is just an example of a URL containing some characters to inject some JavaScript that was not intended by the plugin author: https://your-domain.com/example.php/%22%3E%3Cscript%3Ealert(‘Site hacked”)%3C/script%3E.
Entering that URL the plugin will output this:
<form method="post" action="example.php/"><script>alert('Site hacked')</script> ... </form>
This is really a not dangerous code, it will just open a popup with the message “Site hacked”.
It’s just an example to show you how hackers can make execute their code on a web page.
Who will write this kind of URL?
The hacker can for example send an email to someone with the link https://your-domain.com/example.php/%22%3E%3Cscript%3Ealert(‘Site hacked’)%3C/script%3E.
Clicking on the link, the victim will go to https://your-domain.com/.
If instead of that simple harmless code, the hacker provided a more complex link, the web page on https://your-domain.com/ can get all the data of the victim and send it to an external server that has nothing to do with that web page.
How to prevent this kind of Cross-Site Scripting?
The solution is very simple, and all developers know it. It’s enough to neutralize every input.
The following code would avoid this kind of vulnerability:
<form method=”post” action=”<?php echo htmlspecialchars($_SERVER[“PHP_SELF”]); ?>”>
…
</form>
The function htmlspecialchars() will convert special HTML entities back to characters, and even though the link contains the characters to output a script, because of the conversion, the script will be neutralized.
How to prevent Cross-Site Scripting of other plugins?
You are not the author of the vulnerable plugin, and you can’t directly modify the code, but you can add custom code to prevent this kind of vulnerability.
This very simple line of code will prevent Cross-Site Scripting caused by the poor code described above:
if( isset( $_SERVER["PHP_SELF"] ) ){ $_SERVER["PHP_SELF"] = htmlspecialchars( $_SERVER["PHP_SELF"] ); }
This code has to fire before other plugins may output something vulnerable on the page, so before the output of the theme.
If you need to create a functional plugin you can use the tool WordPress Plugin Builder.
The superglobal variable $_SERVER is not the only one that may introduce vulnerabilities when you have poorly coded plugins.
Another superglobal variable that should always be neutralized is $_GET.
The variable $_GET is an array containing all the URL query strings.
If for example the user writes https://your-domain.com?a=2&b=5&checkout=true, the array will be:
$_GET = array( ‘a’ => ‘2’,’b’ => ‘5’,’checkout’ => ‘true’ );
Many plugins use then that array to elaborate the data and maybe outputting something on the screen.
Here is an example:
<?php echo '<div>' . $_GET['a'] . '</div>'; ?>
Unfortunately, also this kind of very vulnerable code may be included in one of your plugins.
Imagine if the parameter a was <script src=”https://external-domain.com/example.js”></script>
The output of the plugin would be:
<div><script src="http://example.com/runme.js"></script></div>
You can imagine yourself what it means. An external script that executes code on your website.
To prevent this kind of vulnerability the plugin author had to escape the input coming with the superglobal variable $_GET.
The proper code was this:
<div><?php echo isset( $_GET['a'] ) ? esc_html( $_GET['a'] ) : ''; ?></div>
Inside the div tags, we don’t want any HTML, so we escape the input using the WordPress core function esc_html.
Can you prevent this kind of vulnerability introduced by other plugins?
Yes, you can, just add this code in a functional plugin:
if( !empty( $_GET ) ){ $remove = array( '(','.js' ); foreach( $_GET as $k => $v ){ $_GET[$k] = str_replace( $remove,'',htmlspecialchars( $v ) ); } }
In the code above we don’t use esc_html() because it’s too strict and may create problems for some plugins. But the function htmlspecialchars() together with replacing the characters “(” and the “.js” are enough to prevent the kind of Cross-Site Scripting described above.
The proposed code will not neutralize all kinds of Cross-Site Scripting, but it will surely help to prevent the most frequent injections.
Many websites are hacked because of this kind of vulnerability and many times hackers exploit the superglobal variables $_SERVER and $_GET. So just by adding the few lines of code described above you can drastically increase the level of security of your website, It doesn’t guarantee 100% protection, but it’s a very effective way to increase security without losing anything in terms of performance.
To limit the risk of Cross-Site Scripting you can also directly download the plugin Basic Security from the WordPress repository. It includes the code described above.
You will also find it on the page of plugins in your backend if you search for “Basic Security: Prevent Cross Site Scripting”.