CORS
Cross-Origin Resource Sharing or CORS is a mechanism that uses additional HTTP headers to grant a browser permission to access resources from a server at an origin different from the website origin.
An example of a cross-origin request is a web application served from http://mydomain.com that uses AJAX to make a request for http://yourdomain.com.
For security reasons, browsers restrict cross-origin HTTP requests initiated by JavaScript. XMLHttpRequest and fetch follow the same-origin policy, meaning a web application using those APIs can only request HTTP resources from the same origin the application was accessed, unless the response from the other origin includes the correct CORS headers.
- CORS behavior is not an error, it’s a security mechanism to protect users.
- CORS is designed to prevent a malicious website that a user may unintentionally visit from making a request to a legitimate website to read their personal data or perform actions against their will.
JSONP
What is JSONP?
JSONP stands for JSON with Padding. It is a technique used to bypass the same-origin policy in web browsers, which restricts web pages from making requests to a different domain than the one that served the web page.
How JSONP works
- Dynamic script tag creation: A
<script>tag is dynamically created and itssrcattribute is set to the URL of the data source, including a callback function name as a query parameter. - Server response: The server responds with a script that calls the callback function, passing the data as an argument.
- Callback execution: The browser executes the script, invoking the callback function with the data.
Example
<!DOCTYPE html>
<html>
<head>
<title>JSONP Example</title>
<script>
function handleResponse(data) {
console.log(data);
}
function fetchData() {
var script = document.createElement("script");
script.src = "https://example.com/data?callback=handleResponse";
document.body.appendChild(script);
}
</script>
</head>
<body>
<button onclick="fetchData()">Fetch Data</button>
</body>
</html>In this example, when the button is clicked, a <script> tag is created with
the src attribute set to https://example.com/data?callback=handleResponse.
The server at example.com responds with a script like this:
handleResponse({
name: "John",
age: 30,
});How JSONP is different from Ajax
- Transport mechanism: JSONP uses the
<script>tag to fetch data, whereas Ajax uses the XMLHttpRequest object. - Request type: JSONP is limited to GET requests, while Ajax can use various HTTP methods like GET, POST, PUT, DELETE, etc.
- Same-origin policy: JSONP can bypass the same-origin policy, while Ajax requests are subject to it unless CORS (Cross-Origin Resource Sharing) is used.
- Error handling: JSONP has limited error handling capabilities compared to Ajax.
Limitations of JSONP
- Security risks: JSONP can expose your application to cross-site scripting (XSS) attacks if the external server is compromised or malicious.
- Limited to GET requests: JSONP cannot be used for POST requests or other HTTP methods.
- Error handling: JSONP lacks robust error handling mechanisms compared to Ajax.
Content Security Policy (CSP)
Content Security Policy (CSP) is a security standard introduced to mitigate a range of attacks, including Cross-Site Scripting (XSS) and data injection attacks. CSP allows web developers to control the resources that a user agent is allowed to load for a given page. By specifying a whitelist of trusted content sources, CSP helps to prevent the execution of malicious content.
How CSP works
CSP works by allowing developers to define a set of rules that specify which sources of content are considered trustworthy. These rules are delivered to the browser via HTTP headers or meta tags. When the browser loads a page, it checks the CSP rules and blocks any content that does not match the specified sources.
Example of a CSP header
Here is an example of a simple CSP header that only allows scripts from the same origin:
Content-Security-Policy: script-src 'self'This policy tells the browser to only execute scripts that are loaded from the same origin as the page itself.
Common directives
default-src: Serves as a fallback for other resource types when they are not explicitly defined.script-src: Specifies valid sources for JavaScript.style-src: Specifies valid sources for CSS.img-src: Specifies valid sources for images.connect-src: Specifies valid sources for AJAX, WebSocket, and EventSource connections.font-src: Specifies valid sources for fonts.object-src: Specifies valid sources for plugins like Flash.
Benefits of using CSP
- Mitigates XSS attacks: By restricting the sources from which scripts can be loaded, CSP helps to prevent the execution of malicious scripts.
- Prevents data injection attacks: CSP can block the loading of malicious resources that could be used to steal data or perform other harmful actions.
- Improves security posture: Implementing CSP is a proactive measure that enhances the overall security of a web application.
Implementing CSP
CSP can be implemented using HTTP headers or meta tags. The HTTP header approach is generally preferred because it is more secure and cannot be easily overridden by attackers.
Using HTTP headers
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.comUsing meta tags
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://trusted.cdn.com"
/>Cross-Site Request Forgery (CSRF)
What is CSRF?
Cross-Site Request Forgery (CSRF) is a type of attack that occurs when a malicious website causes a user’s browser to perform an unwanted action on a different site where the user is authenticated. This can lead to unauthorized actions such as changing account details, making purchases, or other actions that the user did not intend to perform.
How does CSRF work?
- User authentication: The user logs into a trusted website (e.g., a banking site) and receives an authentication cookie.
- Malicious site: The user visits a malicious website while still logged into the trusted site.
- Unwanted request: The malicious site contains code that makes a request to the trusted site, using the user’s authentication cookie to perform actions on behalf of the user.
Mitigation techniques
Anti-CSRF tokens
One of the most effective ways to prevent CSRF attacks is by using anti-CSRF tokens. These tokens are unique and unpredictable values that are generated by the server and included in forms or requests. The server then validates the token to ensure the request is legitimate.
<form method="POST" action="/update-profile">
<input type="hidden" name="csrf_token" value="unique_token_value" />
<!-- other form fields -->
<button type="submit">Update Profile</button>
</form>On the server side, the token is validated to ensure it matches the expected value.
SameSite cookies
The SameSite attribute on cookies can help mitigate CSRF attacks by
restricting how cookies are sent with cross-site requests. The SameSite
attribute can be set to Strict, Lax, or None.
Set-Cookie: sessionId=abc123; SameSite=StrictStrict: Cookies are only sent in a first-party context and not with requests initiated by third-party websites.Lax: Cookies are not sent on normal cross-site subrequests (e.g., loading images), but are sent when a user navigates to the URL from an external site (e.g., following a link).None: Cookies are sent in all contexts, including cross-origin requests.
CORS (Cross-Origin Resource Sharing)
Properly configuring CORS can help prevent CSRF attacks by ensuring that only trusted origins can make requests to your server. This involves setting appropriate headers on the server to specify which origins are allowed to access resources.
Access-Control-Allow-Origin: https://trustedwebsite.comInput validation
What is input validation?
Input validation is the process of verifying that the data provided by a user or other external sources meets the expected format, type, and constraints before it is processed by the application. This can include checking for:
- Correct data type (e.g., string, number)
- Proper format (e.g., email addresses, phone numbers)
- Acceptable value ranges (e.g., age between 0 and 120)
- Required fields being filled
Types of input validation
-
Client-side validation: This occurs in the user’s browser before the data is sent to the server. It provides immediate feedback to the user and can improve the user experience. However, it should not be solely relied upon for security purposes, as it can be easily bypassed.
<form> <input type="text" id="username" required pattern="[A-Za-z0-9]{5,}" /> <input type="submit" /> </form> -
Server-side validation: This occurs on the server after the data has been submitted. It is essential for security because it ensures that all data is validated regardless of the client’s behavior.
const express = require("express"); const app = express(); app.post("/submit", (req, res) => { const username = req.body.username; if (!/^[A-Za-z0-9]{5,}$/.test(username)) { return res.status(400).send("Invalid username"); } // Proceed with processing the valid input });
Importance of input validation in security
-
Preventing SQL injection: By validating and sanitizing input, you can prevent attackers from injecting malicious SQL code into your database queries.
const username = req.body.username; const query = "SELECT * FROM users WHERE username = ?"; db.query(query, [username], (err, results) => { // Handle results }); -
Preventing cross-site scripting (XSS): Input validation helps ensure that user input does not contain malicious scripts that could be executed in the browser.
const sanitizeHtml = require("sanitize-html"); const userInput = req.body.comment; const sanitizedInput = sanitizeHtml(userInput); -
Preventing buffer overflow attacks: By validating the length of input data, you can prevent attackers from sending excessively large inputs that could cause buffer overflows and crash your application.
-
Ensuring data integrity: Input validation helps maintain the integrity of your data by ensuring that only properly formatted and expected data is processed and stored.
Best practices for input validation
- Always validate input on the server side, even if you also validate on the client side
- Use built-in validation functions and libraries where possible
- Sanitize input to remove or escape potentially harmful characters
- Implement whitelisting (allowing only known good input) rather than blacklisting (blocking known bad input)
- Regularly update and review your validation rules to address new security threats
Authentication JavaScript applications
Use HTTPS
Ensure that your application uses HTTPS to encrypt data in transit. This prevents man-in-the-middle attacks and ensures that data exchanged between the client and server is secure.
Token-based authentication
Use JSON Web Tokens (JWT) for token-based authentication. JWTs are compact, URL-safe tokens that can be used to securely transmit information between parties.
Example of generating a JWT
const jwt = require("jsonwebtoken");
const token = jwt.sign({ userId: 123 }, "your-256-bit-secret", {
expiresIn: "1h",
});Example of verifying a JWT
const jwt = require("jsonwebtoken");
try {
const decoded = jwt.verify(token, "your-256-bit-secret");
console.log(decoded);
} catch (err) {
console.error("Invalid token");
}Secure storage
Store sensitive data like tokens securely. Use localStorage or
sessionStorage for storing tokens, but be aware of their vulnerabilities. For
more security, consider using HttpOnly cookies.
Example of storing a token in `localStorage`
Server-side validation
Always validate tokens on the server side to ensure they are not tampered with. This adds an extra layer of security.
OAuth for third-party authentication
Use OAuth for third-party authentication. Libraries like Passport.js can simplify the implementation of OAuth in your application.
Example of using Passport.js for Google OAuth
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
passport.use(
new GoogleStrategy(
{
clientID: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
callbackURL: "http://www.example.com/auth/google/callback",
},
function (accessToken, refreshToken, profile, done) {
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return done(err, user);
});
}
)
);Role-based access control (RBAC)
Implement role-based access control to ensure that users have the appropriate permissions to access resources.
Example of RBAC middleware in Express.js
function checkRole(role) {
return function (req, res, next) {
if (req.user && req.user.role === role) {
next();
} else {
res.status(403).send("Forbidden");
}
};
}
// Usage
app.get("/admin", checkRole("admin"), (req, res) => {
res.send("Welcome, admin!");
});Preventing clickjacking attacks
Clickjacking is a type of attack where a malicious site tricks users into clicking on something different from what the user perceives, potentially leading to unauthorized actions or information disclosure. This is often achieved by embedding the target site in an invisible iframe and overlaying it with deceptive content.
Using the X-Frame-Options header
The X-Frame-Options HTTP header can be used to control whether a browser
should be allowed to render a page in a <frame>, <iframe>, <embed>, or
<object>. This header has three possible values:
DENY: Prevents the page from being displayed in a frame, regardless of the site attempting to do so.SAMEORIGIN: Allows the page to be displayed in a frame on the same origin as the page itself.ALLOW-FROM uri: Allows the page to be displayed in a frame only on the specified origin.
Example:
X-Frame-Options: DENYUsing the Content-Security-Policy header
The Content-Security-Policy (CSP) header provides a more flexible and modern
approach to prevent clickjacking. The frame-ancestors directive specifies
valid parents that may embed a page using <frame>, <iframe>, <object>,
<embed>, or <applet> tags.
Example:
Content-Security-Policy: frame-ancestors 'self'This directive can also specify multiple origins or use wildcards for more complex scenarios.
Example:
Content-Security-Policy: frame-ancestors 'self' https://trusted.comCombining both headers
For maximum compatibility and security, it is recommended to use both
X-Frame-Options and Content-Security-Policy headers together.
Example:
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'Preventing SQL injection vulnerabilities
To prevent SQL injection vulnerabilities in JavaScript applications, always use parameterized queries or prepared statements instead of string concatenation to construct SQL queries. This ensures that user input is treated as data and not executable code. Additionally, use ORM libraries that handle SQL injection prevention for you, and always validate and sanitize user inputs.
Use parameterized queries or prepared statements
Parameterized queries or prepared statements ensure that user input is treated as data and not executable code. This is the most effective way to prevent SQL injection.
Example using Node.js with the `mysql` library
const mysql = require("mysql");
const connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "password",
database: "test",
});
const userId = 1;
const query = "SELECT * FROM users WHERE id = ?";
connection.query(query, [userId], (error, results) => {
if (error) throw error;
console.log(results);
});Use ORM libraries
Object-Relational Mapping (ORM) libraries like Sequelize, TypeORM, or Mongoose (for MongoDB) abstract away SQL queries and handle SQL injection prevention internally.
Example using Sequelize
const { Sequelize, DataTypes } = require("sequelize");
const sequelize = new Sequelize("database", "username", "password", {
host: "localhost",
dialect: "mysql",
});
const User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
},
});
User.findAll({
where: {
id: 1,
},
}).then((users) => {
console.log(users);
});Validate and sanitize user inputs
Always validate and sanitize user inputs to ensure they conform to expected formats and do not contain malicious code.
Example using `validator` library
const validator = require("validator");
const userInput = "someUserInput";
if (validator.isAlphanumeric(userInput)) {
// Proceed with using the input
} else {
// Handle invalid input
}Use stored procedures
Stored procedures are precompiled SQL statements stored in the database. They can help prevent SQL injection by separating the SQL logic from user input.
Example
CREATE PROCEDURE GetUserById(IN userId INT)
BEGIN
SELECT * FROM users WHERE id = userId;
END;Use security libraries and frameworks
Leverage security libraries and frameworks that provide built-in protection against SQL injection.
Example using Express.js with `express-validator`
const { body, validationResult } = require("express-validator");
app.post(
"/user",
[body("username").isAlphanumeric(), body("email").isEmail()],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Proceed with safe input
}
);Handling sensitive data in JavaScript?
Avoid client-side storage for sensitive data
Storing sensitive data such as tokens, passwords, or personal information in
client-side storage like localStorage or sessionStorage is risky because it
can be easily accessed by malicious scripts. Instead, use secure cookies with
the HttpOnly and Secure flags.
// Example of setting a secure cookie in an Express.js server
res.cookie("token", token, { httpOnly: true, secure: true });Use HTTPS
Always use HTTPS to encrypt data in transit between the client and server. This ensures that sensitive data is not exposed to eavesdroppers.
Implement proper authentication and authorization
Ensure that your application has robust authentication and authorization mechanisms. Use libraries and frameworks that are well-tested and maintained.
// Example of using JSON Web Tokens (JWT) for authentication
const jwt = require("jsonwebtoken");
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, {
expiresIn: "1h",
});Sanitize and validate inputs
Always sanitize and validate user inputs to prevent injection attacks such as SQL injection and cross-site scripting (XSS).
// Example of input validation using the express-validator library
const { body, validationResult } = require("express-validator");
app.post(
"/submit",
[body("email").isEmail(), body("password").isLength({ min: 5 })],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Proceed with handling the request
}
);Use environment variables
Store sensitive data such as API keys and database credentials in environment variables rather than hardcoding them in your source code.
// Example of accessing environment variables in Node.js
const dbPassword = process.env.DB_PASSWORD;Regularly update dependencies
Keep your dependencies up to date to ensure that you have the latest security
patches. Use tools like npm audit to identify and fix vulnerabilities.
# Example of running npm audit
npm audit fixSecurity headers
Content-Security-Policy (CSP)
The Content-Security-Policy header helps prevent cross-site scripting (XSS) and other code injection attacks by specifying which content sources are allowed to be loaded on the web page. For example:
Content-Security-Policy: default-src 'self'; img-src 'self' https://example.com; script-src 'self' 'unsafe-inline'This policy allows content to be loaded only from the same origin ('self'), images from the same origin or https://example.com, and scripts from the same origin or inline scripts.
X-Content-Type-Options
The X-Content-Type-Options header prevents MIME type sniffing by instructing the browser to follow the declared Content-Type. This helps mitigate attacks based on content type misinterpretation. The most common value is nosniff:
X-Content-Type-Options: nosniffStrict-Transport-Security (HSTS)
The Strict-Transport-Security header enforces secure (HTTPS) connections to the server. It instructs the browser to only interact with the site using HTTPS, even if the user attempts to access it via HTTP. For example:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadThis policy tells the browser to enforce HTTPS for one year (max-age=31536000), including all subdomains (includeSubDomains), and allows the site to be included in browsers’ HSTS preload lists (preload).
X-Frame-Options
The X-Frame-Options header prevents clickjacking by controlling whether a page can be displayed in a frame. Common values are DENY and SAMEORIGIN:
X-Frame-Options: DENYThis policy prevents the page from being displayed in a frame, iframe, or object.
X-XSS-Protection
The X-XSS-Protection header enables the cross-site scripting (XSS) filter built into most browsers. It can block pages or sanitize scripts that appear to be malicious. For example:
X-XSS-Protection: 1; mode=blockThis policy enables the XSS filter and instructs the browser to block the page if an attack is detected.
Referrer-Policy
The Referrer-Policy header controls how much referrer information is included with requests. It helps protect user privacy and can prevent information leakage. Common values include no-referrer, no-referrer-when-downgrade, and strict-origin-when-cross-origin:
Referrer-Policy: no-referrerThis policy ensures that no referrer information is sent with requests.
Cross-Site Scripting (XSS)
Cross-Site Scripting (XSS) is a type of security vulnerability typically found in web applications. It allows attackers to inject malicious scripts into content from otherwise trusted websites. These scripts can then be executed in the context of the user’s browser, leading to various malicious activities such as:
- Stealing cookies and session tokens
- Defacing websites
- Redirecting users to malicious sites
- Logging keystrokes
There are three main types of XSS attacks:
- Stored XSS: The malicious script is permanently stored on the target server, such as in a database, comment field, or forum post.
- Reflected XSS: The malicious script is reflected off a web server, such as in an error message, search result, or any other response that includes some or all of the input sent to the server.
- DOM-based XSS: The vulnerability exists in the client-side code rather than the server-side code. The malicious script is executed as a result of modifying the DOM environment in the victim’s browser.
How can you prevent XSS?
Validate and sanitize user inputs
Always validate and sanitize user inputs to ensure they do not contain malicious code. Use libraries and frameworks that provide built-in functions for input validation and sanitization.
const sanitizeHtml = require('sanitize-html');
const cleanInput = sanitizeHtml(userInput);Use Content Security Policy (CSP)
A Content Security Policy (CSP) is a security feature that helps prevent XSS attacks by specifying which dynamic resources are allowed to load. This can be done by setting HTTP headers.
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;Escape data before rendering
Always escape data before rendering it in the browser to prevent the execution of malicious scripts. Use functions provided by your framework or library to escape HTML, JavaScript, and other content.
const escapeHtml = (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
const safeOutput = escapeHtml(userInput);Use HTTP-only cookies
Set cookies to be HTTP-only to prevent client-side scripts from accessing them. This can help mitigate the risk of session hijacking.
Set-Cookie: sessionId=abc123; HttpOnly;Regularly update dependencies
Keep your libraries and frameworks up to date to ensure you have the latest security patches and features.
Improving security in JavaScript code
Static code analysis tools
Static code analysis tools examine your code without executing it. They can identify potential security vulnerabilities by analyzing the code structure and patterns.
-
ESLint: A popular linting tool for JavaScript that can be extended with security-focused plugins like
eslint-plugin-securityto catch common security issues.npm install eslint eslint-plugin-security --save-dev// .eslintrc.json { "plugins": ["security"], "extends": ["plugin:security/recommended"] } -
SonarQube: A platform that provides continuous inspection of code quality and security vulnerabilities. It supports JavaScript and integrates with various CI/CD pipelines.
Dynamic analysis tools
Dynamic analysis tools test your application while it is running to identify security vulnerabilities.
-
OWASP ZAP (Zed Attack Proxy): An open-source tool for finding security vulnerabilities in web applications. It can be used to perform automated scans and manual testing.
zap.sh -daemon -port 8080 -config api.disablekey=true -
Burp Suite: A comprehensive platform for web application security testing. It includes tools for scanning, crawling, and exploiting vulnerabilities.
Dependency checkers
Dependency checkers analyze the third-party libraries and packages your project depends on to identify known vulnerabilities.
-
npm audit: A built-in tool for npm that checks for vulnerabilities in your project’s dependencies.
npm audit -
Snyk: A tool that continuously monitors your dependencies for vulnerabilities and provides fixes.
snyk test
Manual code reviews
Manual code reviews involve developers examining each other’s code to identify potential security issues. This technique is effective because it leverages human intuition and experience.
Secure coding practices
Adhering to secure coding practices can help prevent security vulnerabilities from being introduced in the first place.
- Input validation: Always validate and sanitize user inputs to prevent injection attacks.
- Output encoding: Encode data before rendering it to prevent cross-site scripting (XSS) attacks.
- Use HTTPS: Ensure that your application uses HTTPS to encrypt data in transit.
- Principle of least privilege: Grant the minimum necessary permissions to users and services.