Web security refers to the measures taken to protect websites, web applications, and web services from malicious attacks and unauthorized access. It involves securing both the server and client-side components to ensure data privacy, integrity, and availability.
The Open Web Application Security Project (OWASP) is a nonprofit organization that aims to improve software security. OWASP provides free resources like tools, guides, and the famous OWASP Top 10, which is a list of the most critical web application security vulnerabilities. Understanding these vulnerabilities is fundamental for securing web applications.
Injection occurs when an attacker supplies untrusted data to an interpreter as part of a command or query, tricking the interpreter into executing unintended commands.
An attacker can input a specially crafted SQL query to manipulate the backend database.
SELECT * FROM users WHERE username = 'admin' AND password = '';
username: 'admin'
password: ' OR '1'='1
SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1' = '1';
'1'='1'
always evaluates to true, so the attacker can log in without a valid password.SELECT * FROM users WHERE username = ? AND password = ?;
Broken authentication occurs when application functions related to authentication and session management are improperly implemented, allowing attackers to compromise passwords, keys, or session tokens.
A website uses predictable session IDs. After logging in, a user receives a session ID in their cookies like:
Session ID: 12345
An attacker can guess the session ID and use it to impersonate a user. If they can guess or predict the session ID, they can hijack a session and gain unauthorized access.
Sensitive data exposure happens when sensitive information (credit cards, health records, passwords, etc.) is not properly protected, leaving it exposed to attackers.
A website allows users to enter credit card details and submits this information over an unsecured HTTP connection.
An XXE vulnerability occurs when an application processes XML input that includes a reference to an external entity, potentially allowing attackers to access internal files, perform SSRF attacks, or execute remote code.
An application parses XML files like:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
xxe
entity tries to read the content of the /etc/passwd
file on a Unix system, which may contain sensitive information.parserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Broken access control occurs when restrictions on what authenticated users are allowed to do are improperly enforced. Attackers can act as users or admins, or access unauthorized resources.
A user has a standard account, but through URL manipulation, they try to access the admin page:
http://example.com/admin/deleteUser?userId=1
Security misconfigurations arise when the application or its underlying infrastructure has been set up in an insecure way. This includes using default credentials, unnecessary features, or debug modes.
A production server still has the default admin username and password (admin:admin
). An attacker can simply log in to the admin panel.
XSS occurs when an attacker injects malicious scripts into web pages viewed by other users. The victim’s browser executes the script in the context of the trusted site, potentially leading to stolen session cookies or user impersonation.
A blog comment section allows users to post comments without sanitizing the input. An attacker posts a comment like this:
<script>
alert("Your session has been hijacked!");
</script>
<
, >
, and &
.comment = htmlspecialchars(comment, ENT_QUOTES, 'UTF-8');
Insecure deserialization occurs when untrusted data is used to recreate objects (deserialize), leading to potential manipulation of the object or execution of arbitrary code.
An attacker modifies a serialized object before sending it to the server:
{
"username": "user",
"isAdmin": true
}
This vulnerability occurs when applications use software components (libraries, frameworks) that have known security vulnerabilities. Attackers exploit these weaknesses to compromise the system.
Using an outdated version of a popular library (e.g., Log4j) that has a known remote code execution vulnerability allows attackers to execute arbitrary code on your server.
Without proper logging and monitoring, attacks can go undetected, and security breaches may not be identified in a timely manner, allowing attackers to cause prolonged damage.
A web application does not log failed login attempts. An attacker uses brute force to guess passwords, but there’s no alert because logging was insufficient.
This document provides a starting point for AJAX security and will be updated regularly to include more detailed information about specific frameworks and technologies.
.innerText
instead of .innerHTML
The use of .innerText
will prevent most XSS problems as it automatically encodes the text.
eval()
, new Function()
, or other code evaluation toolsThe eval()
function is dangerous and should never be used. Needing to use eval
usually indicates a design flaw.
When using data to build HTML, scripts, CSS, XML, JSON, etc., ensure the data is properly encoded to prevent injection vulnerabilities and preserve the logical meaning. Check out the OWASP Java Encoder Project.
Remember that users control the client-side logic. Browser plugins can set breakpoints, skip code, change values, etc. Never rely on client logic for security.
Like security logic, ensure that any business rules or logic are duplicated on the server side to prevent users from bypassing important logic.
Writing serialization code is difficult, and even small mistakes can lead to large security issues. Use existing frameworks to handle this functionality. Check the JSON page for resources.
Dynamically building XML or JSON can introduce injection bugs. Use encoding libraries or safe JSON/XML libraries to make attributes and element data safe.
Any secrets transmitted to the client can be discovered by the user. Keep all sensitive data on the server.
Use TLS/SSL for encryption and handle encryption on the server.
Never perform security-sensitive logic on the client side. Always handle it on the server to prevent bypassing.
Review the Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet.
Review the AngularJS JSON Hijacking Defense Mechanism.
Always wrap JSON responses in an object to avoid security vulnerabilities.
[{ "object": "inside an array" }]
Not exploitable:
{ "object": "not inside an array" }
{ "result": [{ "object": "inside an array" }] }
Avoid writing your own serialization logic. Use well-reviewed libraries and be cautious with reference vs. value types.
Even if you only expect AJAX client-side code to call your services, users can call them directly. Always validate inputs as they are under user control.
Use your framework’s built-in tools to handle XML and JSON. Manually building these can lead to security issues.
Use third-party libraries to validate web services against schemas to prevent security vulnerabilities.
Content Security Policy (CSP) is a web security standard designed to prevent various types of attacks like Cross-Site Scripting (XSS), data injection, and other code execution vulnerabilities. It controls which resources a browser is allowed to load for a web page.
By restricting the origins from which content can be loaded, CSP acts as a layer of defense to reduce the likelihood of code injection attacks. Without a proper CSP, malicious scripts can be injected and executed, which can compromise user data and site integrity.
Here are five common CSP directives with examples:
default-src
: This specifies the default source for all content types (scripts, images, etc.) that are not explicitly set by other directives.
Content-Security-Policy: default-src 'self';
Example: Only allow resources from the same origin ('self'
).
script-src
: Restricts where JavaScript can be loaded from.
Content-Security-Policy: script-src 'self' https://trusted-scripts.com;
Example: Allow scripts only from the same origin ('self'
) and a trusted third-party domain (trusted-scripts.com
).
img-src
: Controls where images can be loaded from.
Content-Security-Policy: img-src 'self' https://images.com;
Example: Only allow images from the same origin and images.com
.
style-src
: Restricts where CSS can be loaded from.
Content-Security-Policy: style-src 'self' 'unsafe-inline';
Example: Allow CSS from the same origin but also permit inline styles (not recommended unless necessary).
frame-ancestors
: Restricts who can embed your site in an iframe.
Content-Security-Policy: frame-ancestors 'none';
Example: Prevent your site from being embedded in any iframe to avoid clickjacking attacks.
CSP is not implemented by default in React or Node.js. You need to configure and apply it manually to enforce the policy on your web application.
In a React application, you can configure the CSP in the index.html
file located in the public
folder.
Add the following meta tag inside the <head>
section of public/index.html
:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://trusted-scripts.com; img-src 'self' https://images.com; style-src 'self' 'unsafe-inline'; frame-ancestors 'none';"
/>
This example restricts scripts, images, and styles, allowing only trusted sources.
In a Node.js project, you can configure CSP using middleware, such as Helmet. Helmet is a security library for Express.js that helps secure your app by setting various HTTP headers, including CSP.
Install Helmet:
npm install helmet
Apply CSP in your Node.js app:
const helmet = require("helmet");
const express = require("express");
const app = express();
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://trusted-scripts.com"],
imgSrc: ["'self'", "https://images.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
frameAncestors: ["'none'"],
},
})
);
app.get("/", (req, res) => {
res.send("CSP is enabled!");
});
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
This example sets up a CSP policy in a Node.js application with Helmet.
index.html
file.Make sure to audit and test your CSP settings regularly to ensure your site is secure while still functioning as expected.
The web’s security model is based on a same-origin policy. For example, code from https://mybank.com
must have access to only https://mybank.com
’s data, and https://evil.example.com
must never be allowed access. Each origin is, in theory, kept isolated from the rest of the web, giving developers a safe sandbox to build in. In practice, however, attackers have found several ways to subvert the system.
Cross-site scripting (XSS) attacks, for example, bypass the same-origin policy by tricking a site into delivering malicious code along with the intended content. This is a huge problem, as browsers trust all of the code that shows up on a page as being legitimately part of that page’s security origin. The XSS Cheat Sheet is an old but representative cross-section of the methods an attacker might use to violate this trust by injecting malicious code. If an attacker successfully injects any code at all, they’ve compromised the user session and gained access to private information.
This page outlines Content Security Policy (CSP) as a strategy for reducing the risk and impact of XSS attacks in modern browsers.
To implement an effective CSP, take the following steps:
eval()
.XSS attacks exploit the browser’s inability to distinguish between script that’s part of your application and script that’s been maliciously injected by a third party. For example, the Google +1 button at the bottom of this page loads and executes code from https://apis.google.com/js/plusone.js
in the context of this page’s origin. We trust that code, but we can’t expect the browser to figure out on its own that code from apis.google.com
is safe to run, while code from apis.evil.example.com
probably isn’t. The browser happily downloads and executes any code a page requests, regardless of the source.
CSP’s Content-Security-Policy HTTP header lets you create an allowlist of sources of trusted content, and tells the browser to execute or render only resources from those sources. Even if an attacker can find a hole to inject a script through, the script won’t match the allowlist, and therefore won’t be executed.
We trust apis.google.com
to deliver valid code, and we trust ourselves to do the same. Here’s an example policy that allows scripts to execute only when they come from one of those two sources:
Content-Security-Policy: script-src 'self' https://apis.google.com
The script-src
directive controls a set of script-related privileges for a page. This header allows 'self'
as one valid source of script, and https://apis.google.com
as another. The browser can now download and execute JavaScript from apis.google.com
over HTTPS, as well as from the current page’s origin, but not from any other origin. If an attacker injects code into your site, the browser throws an error and doesn’t run the injected script.
Console error:
Refused to load the script 'http://evil.example.com/evil.js' because it violates the following Content Security Policy directive: script-src 'self' https://apis.google.com
The console shows an error when a script tries to run from an origin not on the allowlist.
CSP provides a set of policy directives that enable granular control over the resources a page is allowed to load, including script-src
from the previous example.
The following list outlines the rest of the resource directives as of level 2. A level 3 specification has been drafted, but it’s largely unimplemented in the major browsers.
<base>
element.child-src https://youtube.com
enables embedding videos from YouTube but not from other origins.font-src https://themes.googleusercontent.com
.<form>
tags.<frame>
, <iframe>
, <embed>
, and <applet>
tags. It can’t be used in <meta>
tags or for HTML resources.child-src
.<meta>
tags.By default, the browser loads the associated resource from any origin, with no restrictions, unless you set a policy with a specific directive. To override the default, specify a default-src directive. This directive defines the defaults for any unspecified directive that ends with -src
. For example, if you set default-src
to https://example.com
, and you don’t specify a font-src
directive, then you can load fonts only from https://example.com
.
The following directives don’t use default-src
as a fallback. Failing to set them is the same as allowing anything:
Here’s your content formatted in Markdown:
To use CSP directives, list them in the HTTP header with directives separated by colons. Make sure to list all required resources of a specific type in a single directive as follows:
script-src https://host1.com https://host2.com
The following is an example of multiple directives, in this case for a web app that loads all its resources from a content delivery network at https://cdn.example.net
, and doesn’t use framed content or plugins:
Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'
Modern browsers support the unprefixed Content-Security-Policy header. This is the recommended header. The X-WebKit-CSP
and X-Content-Security-Policy
headers you might see in online tutorials are deprecated.
CSP is defined on a page-by-page basis. You’ll need to send the HTTP header with every response that you want to protect. This lets you fine-tune the policy for specific pages based on their specific needs. For example, if one set of pages in your site has a +1 button, while others don’t, you can allow the button code to load only when necessary.
The source list for each directive is flexible. You can specify sources by scheme (e.g., data:
, https:
) or ranging in specificity from hostname-only (example.com
, which matches any origin on that host: any scheme, any port) to a fully qualified URI (https://example.com:443
, which matches only HTTPS, only example.com
, and only port 443). Wildcards are accepted, but only as a scheme, a port, or in the leftmost position of the hostname: *://*.example.com:*
would match all subdomains of example.com
(but not example.com
itself), using any scheme, on any port.
The source list also accepts four keywords:
'none'
matches nothing.'self'
matches the current origin, but not its subdomains.'unsafe-inline'
allows inline JavaScript and CSS. For more information, see Avoid inline code.'unsafe-eval'
allows text-to-JavaScript mechanisms like eval
. For more information, see Avoid eval().These keywords require single quotes. For example, script-src 'self'
(with quotes) authorizes the execution of JavaScript from the current host; script-src self
(no quotes) allows JavaScript from a server named “self” (and not from the current host), which probably isn’t what you meant.
There’s one more directive worth talking about: sandbox. It’s a bit different from the others we’ve looked at, as it places restrictions on actions that the page can take rather than on resources that the page can load. If the sandbox directive is present, the page is treated as though it was loaded inside of an <iframe>
with a sandbox attribute. This can have a wide range of effects on the page, such as forcing the page into a unique origin and preventing form submission, among others. You can find full details on valid sandboxing attributes in the “Sandboxing” section of the HTML5 spec.
CSP’s preferred delivery mechanism is an HTTP header. It can be useful, however, to set a policy on a page directly in the markup. Do that using a <meta>
tag with an http-equiv
attribute:
<meta
http-equiv="Content-Security-Policy"
content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'"
/>
This can’t be used for frame-ancestors
, report-uri
, or sandbox
.
As powerful as the origin-based allowlists used in CSP directives are, they can’t solve the biggest threat posed by XSS attacks: inline script injection. If an attacker can inject a script tag that directly contains some malicious payload (such as <script>sendMyDataToEvilDotCom()</script>
), the browser has no way to distinguish it from a legitimate inline script tag. CSP solves this problem by banning inline script entirely.
This ban includes not only scripts embedded directly in script tags, but also inline event handlers and javascript:
URLs. You’ll need to move the content of script tags into an external file, and replace javascript:
URLs and <a ... onclick="[JAVASCRIPT]">
with appropriate addEventListener()
calls. For example, you might rewrite the following:
<script>
function doAmazingThings() {
alert("YOU ARE AMAZING!");
}
</script>
<button onclick="doAmazingThings();">Am I amazing?</button>
To something more like:
<!-- amazing.html -->
<script src="amazing.js"></script>
<button id="amazing">Am I amazing?</button>
// amazing.js
function doAmazingThings() {
alert("YOU ARE AMAZING!");
}
document.addEventListener("DOMContentLoaded", function () {
document.getElementById("amazing").addEventListener("click", doAmazingThings);
});
The rewritten code is not only compatible with CSP, but also aligned with web design best practices. Inline JavaScript mixes structure and behavior in ways that make code confusing. It’s also more complicated to cache and compile. Moving your code into external resources makes your pages perform better.
Moving inline style tags and attributes into external stylesheets is also strongly recommended to protect your site against CSS-based data exfiltration attacks.
Warning: Banning inline scripts and styles is CSP’s most important security feature. Only use these methods if a script or style sheet absolutely must be inline.
You can enable inline scripts and styles by adding 'unsafe-inline'
as an allowed source in a script-src
or style-src
directive. CSP Level 2 also lets you add specific inline scripts to your allowlist using either a cryptographic nonce (number used once) or hash as follows.
To use a nonce, give your script tag a nonce
attribute. Its value must match one in the list of trusted sources. For example:
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// Some inline code I can't remove yet, but need to as soon as possible.
</script>
Add the nonce to your script-src
directive following the nonce-
keyword:
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Nonces must be regenerated for every page request, and they must be unguessable.
Hashes work in a similar way. Instead of adding code to the script tag, create an SHA hash of the script itself and add it to the script-src
directive. For example, if your page contained this:
<script>
alert("Hello, world.");
</script>
Your policy must contain this:
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
The sha*
prefix specifies the algorithm that generates the hash. The previous example uses sha256-
, but CSP also supports sha384-
and sha512-
. When generating the hash, omit the <script>
tags. Capitalization and whitespace matter, including leading and trailing whitespace.
Solutions for generating SHA hashes are available in many languages. Using Chrome 40 or later, you can open DevTools and then reload your page. The Console tab shows error messages with the correct SHA-256 hash for each of your inline scripts.
eval()
Even when an attacker can’t inject script directly, they might be able to trick your application into converting input text into executable JavaScript and executing it on their behalf. eval()
, new Function()
, setTimeout([string], …)
, and setInterval([string], ...)
are all vectors attackers can use to execute malicious code through injected text. CSP’s default response to this risk is to completely block all of these vectors.
This has the following effects on the way you build applications:
JSON.parse
, instead of relying on eval
. Safe JSON operations are available in every browser since IE8.You must rewrite any setTimeout
or setInterval
calls you make using inline functions instead of strings. For example, if your page contains the following:
setTimeout("document.querySelector('a').style.display = 'none';", 10);
Rewrite it as:
setTimeout(function () {
document.querySelector("a").style.display = "none";
}, 10);
new Function()
often to speed up template generation at runtime, which allows evaluation of malicious text. Some frameworks support CSP out of the box, falling back to a robust parser in the absence of eval
. AngularJS’s ng-csp
directive is a good example of this. However, we recommend using a templating language that offers precompilation, such as Handlebars. Precompiling your templates can make the user experience even faster than the fastest runtime implementation, as well as making your site more secure.If eval()
or other text-to-JavaScript functions are essential to your application, you can enable them by adding 'unsafe-eval'
as an allowed source in a script-src
directive. We strongly discourage this because of the code injection risk it presents.
To notify the server of bugs that might allow malicious injection, you can tell the browser to POST JSON-formatted violation reports to a location specified in a report-uri
directive:
Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
These reports look like the following:
{
"csp-report": {
"document-uri": "http://example.org/page.html",
"referrer": "http://evil.example.com/",
"blocked-uri": "http://evil.example.com/evil.js",
"violated-directive": "script-src 'self' https://apis.google.com",
"original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
}
}
The report contains helpful information for finding the cause of a policy violation, including the page it happened on (document-uri
), that page’s referrer (referrer
), the resource that violated the page’s policy (blocked-uri
), the specific directive it violated (violated-directive
), and the page’s complete policy (original-policy
).
If you’re just starting out with CSP, we recommend using report-only mode to evaluate the state of your app before you change your policy. To do this, instead of sending a Content-Security-Policy
header, send a Content-Security-Policy-Report-Only
header:
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
The policy specified in report-only mode doesn’t block restricted resources, but it does send violation reports to the location you specify. You can even send both headers, to enforce one policy while monitoring another. This is a great way to test changes to your CSP while enforcing your current policy: turn on reporting for a new policy, monitor the violation reports and fix any bugs, and when you’re satisfied with the new policy, start enforcing it.
The first step towards crafting a policy for your app is to evaluate the resources it loads. When you understand your app’s structure, create a policy based on its requirements. The following sections walk through a few common use cases and the decision process for supporting them following the CSP guidelines.
Facebook’s Like button has several implementation options. We recommend using the <iframe>
version to keep it sandboxed from the rest of your site. It needs a child-src https://facebook.com
directive to function properly.
X’s Tweet button relies on access to a script. Move the script it provides into an external JavaScript file, and use the directive script-src https://platform.twitter.com; child-src https://platform.twitter.com
.
Other platforms have similar requirements and can be addressed similarly. To test these resources, we recommend setting a default-src
of 'none'
and watching your console to determine which resources you’ll need to enable.
To use multiple widgets, combine the directives as follows:
script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com
For some websites, you’ll want to make sure that only local resources can be loaded. The following example develops a CSP for a banking site, starting with a default policy that blocks everything (default-src 'none'
).
The site loads all images, style, and script from a CDN at https://cdn.mybank.net
, and connects to https://api.mybank.com/
using XHR to retrieve data. It uses frames, but only for pages local to the site (no third-party origins). There’s no Flash on the site, no fonts, no extras. The most restrictive CSP header it can send is this:
Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'
The following is an example CSP for a forum administrator who wants to ensure that all resources on their forum are only loaded using secure channels but is inexperienced at coding and doesn’t have the resources to rewrite third-party forum software full of inline scripts and styles:
Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'
Although https:
is specified in default-src
, the script
and style
directives don’t automatically inherit that source. Each directive overwrites the default for that specific type of resource.
Content Security Policy Level 2 is a W3C recommended standard. W3C’s Web Application Security Working Group is developing the specification’s next iteration, Content Security Policy Level 3.