Frontend
This document is to help demonstrate the most common threats to applications on the frontend and how to mitigate them.
Checklist
Do | Don't |
---|---|
✔️ Sanitize user generated content | ❌ Use dangerouslySetInnerHTML |
✔️ Sanitize query input | ❌ Use unsafe JS evaluation (eval , setTimeout , setInterval , new Function() ) |
✔️ Sanitize script tags | |
✔️ Setup a CSP | |
✔️ Use CAPTCHAs |
Requirements
Tabnabbing
Tabnabbing is what a legitimate tab opens a malicious tab, the malicious tab can access and change window.opener.location
on the legitimate tab, changing the location on the original tab to a phishing scam or similar.
If you have a page where you allow user generated content (UGC) with links, you wouldn't want the link to tabnab nor do you want to be seen as a refer website to an unknown address.
It is advised when setting UGC links they either redirect using the current tab but if they do open up a new tab the rel attribute is set to 'noopener noreferrer'. If you use the Link component from cruk-react-components library this is happens automatically.
<a target="_blank" rel="noopener noreferrer" src="blah">
{" "}
blah{" "}
</a>
Cross site scripting (XSS)
Cross site scripting is someone tries inject dodgy code into your website, they could take over the donate buttons and start directing people to their payment for and all sorts of nasty stuff.
Reflected XSS
For example if you have code that renders a page like this:
const urlParams = new URLSearchParams(window.location.search);
const myParam = urlParams.get("message");
return <p>Status: {myParam}</p>;
Then someone can modify a url and start sharing around this:
https://insecure-website.com/status?message=<script>/*+Bad+stuff+here...+*/</script>
Then the "bad stuff" like nefarious scripts could be rendered like this:
<p>Status: <script>/* Bad stuff here... */</script></p>
The solution here is to sanitise anything that is pulled in from the query string before it is evaluated as code or rendered. We can write our own sanitise function that remove special characters, but there are libraries dedicated to this and most frontend frameworks for the most part will do this for you.
const urlParams = new URLSearchParams(window.location.search);
const myParam = urlParams.get("message");
const cleanParam = sanitize(myParam);
return <p>Status: {cleanParam}</p>;
Stored XSS
Stored XSS attacks are similar to reflected but the nefarious stuff is already in the database and it being rendered on the page so this is making a risk with user generated content.
For example if you had a page that rendered like this
<p>Name: {api_response_name}!</p>
An attacker could inject scripts into a page entering nefarious code in the field of the edit page like:
The solution is to make sure that any data coming from a user input is escaped and sanitised before it is used. Please use a library to do something like this again hopefully a framework like React should take care of this for you. Under the hood it is doing something like this:
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
The script tag vulnerability
First we need to take a look at when HTML parsing works correctly:
So this OK:
<div attributename="“value”">stuff</div>
This is an HTML syntax error:
<div attributename="“value”more”">stuff</div>
If you wanted quotes inside you could escape them like this:
<div attributename=“value"more”>stuff</div>
But in the script tag if we do this:
<script>
var s = "surprise!</script><script>alert(‘pwned!!!')</script>";
</script>
Now the HTML parser see </script>
and thinks it is back in control
There is a new opening <script>
tag that executes some malicious code.
This will happen anywhere the string </script>
appears inside the script tag.
There is also issues with multiline HTML comments which look like this normally:
<!–- I am an HTML comment -->
But the HTML parser will thing a comment is starting if there is an <!--
sequence anywhere in the code event in string like this:
<script>
var a = 'Consider this string: <!--’;
var b = '<script>';
</script>
<p>Any text</p>
<script>
var s = 'another script’;
</script>
Or even this JS like this:
<script>
if (x<!--y) { ... }
if ( player<script ) { ... }
</script>
Again if you are using a framework like React you don't need to think about this but if you are not, there are a few solutions. One is after serialization replacing characters with unicode characters that look exactly the same:
<script>
window.__INITIAL_STATE__ = <%- JSON.stringify(initialState).replace(/</g, '\\u003c') %>;
</script>
React vulnerabilities
For the most part if you are using React then you shouldn't need to worry about sanitising user data or inputs because React will do this for you but there are a few scenarios when either things can render unsanitised or dangerous scripts can be evaluated on the client without any protection.
dangerouslySetInnerHTML
You should avoid the aptly named dangerouslySetInnerHTML
attribute but there are a handful of situations when it is unavoidable, for example some GTM script configurations and if you want to set the value of something that looks like a form field but isn't, its an element with contenteditable
set to true. This functions will circumvent all the sanitisation and html escaping that React does for you, use it sparingly and carefully.
Unsafe JS evaluation
If you are dealing with user generate content there are some functions and patters that will attempt to evaluate and run code and must be used after you have made that content safe these include.
eval();
setTimeout();
setInterval();
new Function();
If any nefarious code ends up in any of the above React is not going to be able to save you as it will to be totally agnostic to what happens inside these functions. Make sure that you control what goes in to these functions before you call them and its probably safe to say that no one should ever be using these:
eval();
new Function();
Unsafe attributes
React is blind to whatever goes in to src
and href
which can be exploited like this:
<a href="javascript: alert('hello from javascript!')">
So again if these values come from user generated content make sure you sanitise them manually escaping special characters or set up a Content Security Policy (CSP) which stops these vulnerabilities at least for modern browsers which honour CSPs (not IE 11).
Use CAPTCHAs
We recommend using Google reCAPTCHA Enterprise reCAPTCHA Enterprise is useful when you want to detect automated attacks or threats against your website.
When an end user visits the web page, the following events are triggered in a sequence:
- The browser loads the customer web page stored on the backend/web server, and then loads the reCAPTCHA JavaScript from reCAPTCHA Enterprise.
- When the end user triggers an HTML action protected by reCAPTCHA such as login, the web page sends signals that are collected in the browser to reCAPTCHA Enterprise for analysis.
- reCAPTCHA Enterprise sends an encrypted reCAPTCHA token to the web page for later use.
- The web page sends the encrypted reCAPTCHA token to the backend/web server for assessment.
- The backend/web server sends the create assessment (assessments.create) request and the encrypted reCAPTCHA token to reCAPTCHA Enterprise.
- After assessing, reCAPTCHA Enterprise returns a score (from 0.0 through 1.0) and reason code (based on the interactions) to the backend/web server.
- Depending on the score, you (developer) can determine the next steps to take action on the user.
References & Further Reading
- Tabnabbing (https://pointjupiter.com/what-noopener-noreferrer-nofollow-explained/)
- Code injection (https://snyk.io/blog/5-ways-to-prevent-code-injection-in-javascript-and-node-js/)
- XSS (https://portswigger.net/web-security/cross-site-scripting)
- Script tag vulnerability (https://uploadcare.com/blog/vulnerability-in-html-design/)
- React security (https://reactjs.org/docs/dom-elements.html)
- Content Security Policies (https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)