The following cheat sheet serves as a guide for implementing HTML 5 in a secure fashion.
Web Messaging (also known as Cross Domain Messaging) provides a means of messaging between documents from different origins in a way that is generally safer than the multiple hacks used in the past to accomplish this task. However, there are still some recommendations to keep in mind:
- When posting a message, explicitly state the expected origin as the second argument to
postMessagerather than*in order to prevent sending the message to an unknown origin after a redirect or some other means of the target window's origin changing. - The receiving page should always:
- Check the
originattribute of the sender to verify the data is originating from the expected location. - Perform input validation on the
dataattribute of the event to ensure that it's in the desired format.
- Check the
- Don't assume you have control over the
dataattribute. A single Cross Site Scripting flaw in the sending page allows an attacker to send messages of any given format. - Both pages should only interpret the exchanged messages as data. Never evaluate passed messages as code (e.g. via
eval()) or insert it to a page DOM (e.g. viainnerHTML), as that would create a DOM-based XSS vulnerability. For more information see DOM based XSS Prevention Cheat Sheet. - To assign the data value to an element, instead of using a insecure method like
element.innerHTML=data;, use the safer option:element.textContent=data; - Check the origin properly exactly to match the FQDN(s) you expect. Note that the following code:
if(message.origin.indexOf(".owasp.org")!=-1) { /* ... */ }is very insecure and will not have the desired behavior asowasp.org.attacker.comwill match. - If you need to embed external content/untrusted gadgets and allow user-controlled scripts (which is highly discouraged), please check the information on sandboxed frames.
- Validate URLs passed to
XMLHttpRequest.open. Current browsers allow these URLs to be cross domain; this behavior can lead to code injection by a remote attacker. Pay extra attention to absolute URLs. - Ensure that URLs responding with
Access-Control-Allow-Origin: *do not include any sensitive content or information that might aid attacker in further attacks. Use theAccess-Control-Allow-Originheader only on chosen URLs that need to be accessed cross-domain. Don't use the header for the whole domain. - Allow only selected, trusted domains in the
Access-Control-Allow-Originheader. Prefer allowing specific domains over blocking or allowing any domain (do not use*wildcard nor blindly return theOriginheader content without any checks). - Keep in mind that CORS does not prevent the requested data from going to an unauthorized location. It's still important for the server to perform usual CSRF prevention.
- While the Fetch Standard recommends a pre-flight request with the
OPTIONSverb, current implementations might not perform this request, so it's important that "ordinary" (GETandPOST) requests perform any access control necessary. - Discard requests received over plain HTTP with HTTPS origins to prevent mixed content bugs.
- Don't rely only on the Origin header for Access Control checks. Browser always sends this header in CORS requests, but may be spoofed outside the browser. Application-level protocols should be used to protect sensitive data.
- Check out WebSocket Security Cheat Sheet to learn about WebSocket specific protections.
- Validate URLs passed to the
EventSourceconstructor, even though only same-origin URLs are allowed. - As mentioned before, process the messages (
event.data) as data and never evaluate the content as HTML or script code. - Always check the origin attribute of the message (
event.origin) to ensure the message is coming from a trusted domain. Use an allow-list approach.
- Also known as Offline Storage, Web Storage. Underlying storage mechanism may vary from one user agent to the next. In other words, any authentication your application requires can be bypassed by a user with local privileges to the machine on which the data is stored. Therefore, it's recommended to avoid storing any sensitive information in local storage where authentication would be assumed.
- Due to the browser's security guarantees it is appropriate to use local storage where access to the data is not assuming authentication or authorization.
- Use the object sessionStorage instead of localStorage if persistent storage is not needed. sessionStorage object is available only to that window/tab until the window is closed.
- A single Cross Site Scripting can be used to steal all the data in these objects, so again it's recommended not to store sensitive information in local storage.
- A single Cross Site Scripting can be used to load malicious data into these objects too, so don't consider objects in these to be trusted.
- Pay extra attention to "localStorage.getItem" and "setItem" calls implemented in HTML5 page. It helps in detecting when developers build solutions that put sensitive information in local storage, which can be a severe risk if authentication or authorization to that data is incorrectly assumed.
- Do not store session identifiers in local storage as the data is always accessible by JavaScript. Cookies can mitigate this risk using the
httpOnlyflag. - There is no way to restrict the visibility of an object to a specific path like with the attribute path of HTTP Cookies, every object is shared within an origin and protected with the Same Origin Policy. Avoid hosting multiple applications on the same origin, all of them would share the same localStorage object, use different subdomains instead.
- Web SQL Database was deprecated by the W3C in 2010 and is removed from all major browsers: Chromium dropped support in version 119 (October 2023) and Safari/Firefox never shipped it for third-party origins. Do not use Web SQL. If you specifically need an SQL interface in the browser, prefer running an embedded engine such as the official SQLite WebAssembly build (
sqlite-wasm), backed by IndexedDB or the Origin Private File System (OPFS) for persistence. - The current standard for client-side structured storage is IndexedDB, a transactional key-value store that has been a W3C Recommendation since 2015 and is supported in all evergreen browsers.
- Underlying storage mechanisms vary across user agents and operating systems. A user (or any process running with that user's privileges, including malware) with read access to the browser profile directory on disk can read or modify the stored data, so do not assume client-side storage provides confidentiality. Do not store session tokens, credentials, or other secrets in IndexedDB unless they are encrypted with a key that is not itself recoverable from the browser (for example, derived from a user-supplied passphrase that is never persisted, or wrapped by a non-extractable Web Crypto
CryptoKey). - A single Cross-Site Scripting vulnerability can read or write any data in IndexedDB; treat its contents as untrusted input on read.
- Apply the same input validation and output encoding rules to data coming from IndexedDB as you would to data coming from the network.
- The Geolocation API requires that user agents ask for the user's permission before calculating location. Whether or how this decision is remembered varies from browser to browser. Some user agents require the user to visit the page again in order to turn off the ability to get the user's location without asking, so for privacy reasons, it's recommended to require user input before calling
getCurrentPositionorwatchPosition.
- Web Workers are allowed to use
XMLHttpRequestobject to perform in-domain and Cross Origin Resource Sharing requests. See relevant section of this Cheat Sheet to ensure CORS security. - While Web Workers don't have access to DOM of the calling page, malicious Web Workers can use excessive CPU for computation, leading to Denial of Service condition or abuse Cross Origin Resource Sharing for further exploitation. Ensure code in all Web Workers scripts is not malevolent. Don't allow creating Web Worker scripts from user supplied input.
- Validate messages exchanged with a Web Worker. Do not try to exchange snippets of JavaScript for evaluation e.g. via
eval()as that could introduce a DOM Based XSS vulnerability.
Attack is described in detail in this article.
To summarize, it's the capacity to act on parent page's content or location from a newly opened page via the back link exposed by the opener JavaScript object instance.
It applies to an HTML link or a JavaScript window.open function using the attribute/instruction target to specify a target loading location that does not replace the current location and then makes the current window/tab available.
To prevent this issue, the following actions are available:
Cut the back link between the parent and the child pages:
- For HTML links:
- To cut this back link, add the attribute
rel="noopener"on the tag used to create the link from the parent page to the child page. This attribute value cuts the link, but depending on the browser, lets referrer information be present in the request to the child page. - To also remove the referrer information use this attribute value:
rel="noopener noreferrer".
- To cut this back link, add the attribute
- For the JavaScript
window.openfunction, add the valuesnoopener,noreferrerin the windowFeatures parameter of thewindow.openfunction.
As the behavior using the elements above is different between the browsers, either use an HTML link or JavaScript to open a window (or tab), then use this configuration to maximize the cross supports:
- For HTML links, add the attribute
rel="noopener noreferrer"to every link. - For JavaScript, use this function to open a window (or tab):
function openPopup(url, name, windowFeatures){
//Open the popup and set the opener and referrer policy instruction
var newWindow = window.open(url, name, 'noopener,noreferrer,' + windowFeatures);
//Reset the opener link
newWindow.opener = null;
}- Add the HTTP response header
Referrer-Policy: no-referrerto every HTTP response sent by the application (Header Referrer-Policy information. This configuration will ensure that no referrer information is sent along with requests from the page.
Compatibility matrix:
- Use the
sandboxattribute of aniframefor untrusted content. - The
sandboxattribute of aniframeenables restrictions on content within aniframe. The following restrictions are active when thesandboxattribute is set:- All markup is treated as being from a unique origin.
- All forms and scripts are disabled.
- All links are prevented from targeting other browsing contexts.
- All features that trigger automatically are blocked.
- All plugins are disabled.
It is possible to have a fine-grained control over iframe capabilities using the value of the sandbox attribute.
- In old versions of user agents where this feature is not supported, this attribute will be ignored. Use this feature as an additional layer of protection or check if the browser supports sandboxed frames and only show the untrusted content if supported.
- Apart from this attribute, to prevent Clickjacking attacks and unsolicited framing it is encouraged to use the header
X-Frame-Optionswhich supports thedenyandsame-originvalues. Other solutions like framebustingif(window!==window.top) { window.top.location=location;}are not recommended.
- Protect the input values from being cached by the browser.
Access a financial account from a public computer. Even though one is logged-off, the next person who uses the machine can log-in because the browser autocomplete functionality. To mitigate this, we tell the input fields not to assist in any way.
<input type="text" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></input>Text areas and input fields for PII (name, email, address, phone number) and login credentials (username, password) should be prevented from being stored in the browser. Use these HTML5 attributes to prevent the browser from storing PII from your form:
spellcheck="false"autocomplete="off"autocorrect="off"autocapitalize="off"
- The HTML5 Application Cache (
<html manifest="...">and.appcachefiles) has been removed from all major browsers (Firefox 85, Chrome 93). Do not use it for new applications and migrate any remaining usage to Service Workers with the Cache API. - Service Workers run on a separate, scriptable thread and intercept network requests for the registered scope. Because they can transparently serve cached responses, they have a significant security impact:
- Only register Service Workers from your own origin and only serve the worker script over HTTPS with a long-cache-busting filename (e.g.
sw.<hash>.js). - Validate that the scope of the Service Worker is restricted (use the
scopeoption or theService-Worker-Allowedresponse header) so a compromised worker cannot intercept unrelated paths. - A malicious or compromised Service Worker can intercept every request from its scope until it is unregistered or the cache TTL expires; have a documented kill-switch (e.g. an unregister flow you can ship in a hotfix).
- Do not cache responses that contain sensitive data. Send
Cache-Control: no-storeon those responses so the Cache API will not retain them.
- Only register Service Workers from your own origin and only serve the worker script over HTTPS with a long-cache-busting filename (e.g.
- The best practice now is to determine the capabilities that a browser supports and augment with substitutes only for capabilities that are not directly supported. Do not fall back to obsolete browser plugins — Adobe Flash Player reached end-of-life on 31 December 2020 and is removed from all browsers; Java applets, Silverlight, and ActiveX are likewise unsupported. Native HTML5 (
<video>,<audio>,<canvas>, WebAssembly) covers these legacy use cases.
Consult the project OWASP Secure Headers in order to obtains the list of HTTP security headers that an application should use to enable defenses at browser level.