Security Vulnerabilities in JavaScript Applications
In today’s digital world, web applications must take various security measures to protect user data and provide a secure experience. However, some common security vulnerabilities overlooked by developers can jeopardize this data. This article will examine significant security vulnerabilities that can be encountered in applications developed using JavaScript, along with examples of each.
1. Cross-Site Scripting (XSS)
XSS is a vulnerability that occurs when a web application reflects user-inputted data directly to users without sufficient validation and sanitization. There are three types of XSS vulnerabilities:
• Reflected XSS: The user sends malicious code to the application via the URL, and this code is executed in the application’s immediate response.
• Stored XSS: Malicious data added by the user is stored on the server and later loaded and executed by other users.
• DOM-Based XSS: Occurs on the DOM manipulated by JavaScript in the browser; in this case, user input may contain malicious code.
Modern JavaScript libraries and frameworks offer built-in security measures against XSS attacks. For example, React automatically escapes user inputs in HTML. However, in some cases, manual security measures may be necessary.
🚫 Bad Example
In the following example, user input is directly inserted into HTML without any security measures, making this example vulnerable to XSS attacks when executed through the URL below:
import * as React from 'react';
export default function App() {
const params = new URLSearchParams(window.location.search);
/* htmlContent=<img src="http://unsplash.it/100/100?random"
onload="window.location.href='https://malicious-website.com'" */
const htmlContent = params.get('htmlContent');
return (
<div
dangerouslySetInnerHTML={{
__html: htmlContent
}}
/>
);
}
✅ Good Example
In the following example, user input is sanitized using DOMPurify, allowing it to be displayed safely on the page. dangerouslySetInnerHTML should not be used unless necessary, and user inputs should be displayed as plain text.
import * as React from 'react';
import DOMPurify from 'dompurify';
export default function App() {
const params = new URLSearchParams(window.location.search);
/* htmlContent=<img src="http://unsplash.it/100/100?random"
onload="window.location.href='https://malicious-website.com'" */
let htmlContent = params.get('htmlContent');
htmlContent = DOMPurify.sanitize(htmlContent);
return (
<div
dangerouslySetInnerHTML={{
__html: htmlContent
}}
/>
);
}
2. Sensitive Data Exposure
This vulnerability occurs when a web application fails to protect users’ personal data (passwords, credit card information, health information, etc.). Such situations typically arise due to a lack of encryption, poorly configured data storage, or insufficient security measures during data transmission. Attackers can exploit these weaknesses to gain access to sensitive information and use it for malicious purposes.
🚫 Bad Example
In the following example, sensitive credit card information is stored in local storage and sent via URL parameters, compromising data security.
import React, { useState } from 'react';
const PaymentForm = () => {
const [cardInfo, setCardInfo] = useState({
cardNumber: '',
expirationDate: '',
cvv: ''
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setCardInfo({ ...cardInfo, [name]: value });
};
const handleSave = () => {
localStorage.setItem('cardInfo', JSON.stringify(cardInfo));
alert('Credit card information saved!');
};
const handleSend = () => {
const url = `https://example.com/api/payment
?cardNumber=${cardInfo.cardNumber}
&expirationDate=${cardInfo.expirationDate}
&cvv=${cardInfo.cvv}`;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Payment process failed.');
}
alert('Payment successful!');
})
.catch(error => {
alert('An error occurred during payment: ' + error.message);
});
};
return (
<div>
<h1>Credit Card Information</h1>
<input type="text" name="cardNumber" placeholder="Credit Card Number" onChange={handleInputChange} />
<input type="text" name="expirationDate" placeholder="Expiration Date (MM/YY)" onChange={handleInputChange} />
<input type="text" name="cvv" placeholder="CVV" onChange={handleInputChange} />
<button type="button" onClick={handleSave}>Save Credit Card Information</button>
<button type="button" onClick={handleSend}>Send Payment</button>
</div>
);
};
✅ Good Example
In the following example, credit card information collected from the user is sent securely via a POST request, and it is not stored in local storage. This method helps to protect sensitive data.
import React, { useState } from 'react';
const PaymentForm = () => {
const [cardInfo, setCardInfo] = useState({
cardNumber: '',
expirationDate: '',
cvv: ''
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setCardInfo({ ...cardInfo, [name]: value });
};
const handleSend = () => {
fetch('https://example.com/api/payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(cardInfo)
})
.then(response => {
if (!response.ok) {
throw new Error('Payment process failed.');
}
alert('Payment successful!');
})
.catch(error => {
alert('An error occurred during payment: ' + error.message);
});
};
return (
<div>
<h1>Credit Card Information</h1>
<input type="text" name="cardNumber" placeholder="Credit Card Number" onChange={handleInputChange} />
<input type="text" name="expirationDate" placeholder="Expiration Date (MM/YY)" onChange={handleInputChange} />
<input type="text" name="cvv" placeholder="CVV" onChange={handleInputChange} />
<button type="button" onClick={handleSend}>Send Payment</button>
</div>
);
};
3. Unvalidated Redirects and Forwards
This vulnerability occurs when the URLs to which users are redirected are not sufficiently validated. An attacker can redirect users to a malicious website, exposing them to phishing attacks and harmful content.
🚫 Bad Example
In the following example, the redirecturi parameter obtained from the user is used directly for redirection, increasing the risk of redirecting to a malicious website.
import React, { useEffect } from 'react';
export function Login() {
useEffect(() => {
const queryParams = new URLSearchParams(window.location.search);
// redirecturi -> https://malicious-website.com
const redirecturi = queryParams.get('redirecturi');
if (redirecturi) {
window.open(redirecturi);
} else {
alert('Invalid URL parameter!');
}
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
✅ Good Example
In the following example, the redirecturi parameter is validated against an allowedOrigin, ensuring that redirection occurs only to valid root URLs. This prevents malicious redirections.
The useNavigate and useLocation hooks manage redirects safely. useLocation checks the security of the current URL, while useNavigate ensures redirection only occurs to secure URLs, preventing access to malicious sites.
import React, { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
export function Login() {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
const queryParams = new URLSearchParams(location.search);
// redirecturi -> https://malicious-website.com
const redirecturi = queryParams.get('redirecturi');
const allowedOrigin = window.location.origin;
if (redirecturi && redirecturi.startsWith(allowedOrigin)) {
navigate(redirecturi);
} else {
alert('Invalid or unsafe URL parameter!');
}
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
4. Using Known Vulnerable Components
Security vulnerabilities arise from the use of outdated third-party libraries or components with known vulnerabilities in the application. These weaknesses can be exploited by attackers, leading to data leaks.
🚫 Bad Example
In the following package.json file, an old version of the lodash library with a known vulnerability is used. In this case, outdated libraries compromise the security of the application.
{
"name": "ecommerce-app",
"version": "1.0.0",
"description": "An e-commerce application",
"dependencies": {
"react": "^17.0.2",
"lodash": "4.17.19",
"axios": "^0.21.1",
"express": "^4.17.1"
}
}
✅ Good Example
In the following package.json file, the latest versions of libraries are used, ensuring that vulnerabilities are minimized. Using tools like npm audit helps detect vulnerabilities in dependencies.
{
"name": "ecommerce-app",
"version": "1.0.0",
"description": "An e-commerce application",
"dependencies": {
"react": "^18.0.0",
"lodash": "^4.17.21",
"axios": "^0.21.1",
"express": "^4.17.1"
}
}
5. Using Components From Untrusted Sources
Using libraries or components from untrusted sources can compromise the security of the application, as they may contain malicious code in the background.
🚫 Bad Example
In the following example, a library from an untrusted source has been integrated:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My E-commerce App</title>
<script src="https://malicious-site.com/unsafe-library.js"></script>
</head>
<body>
</body>
</html>
✅ Good Example
When developing integrated applications, it is essential to ensure that libraries and components come from trusted sources. This minimizes potential security vulnerabilities.
File Integrity: The integrity attribute allows you to check whether a file has the expected content. If the file does not match the specified hash, the browser will not load it, indicating that it may have been maliciously altered.
Cross-Origin Security: The crossorigin attribute helps securely load resources from different domains. This allows the browser to make requests without sending authentication information, reducing security vulnerabilities.
In the following example, a library from a trusted source is used:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My E-commerce App</title>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/some-library/1.0.0/library.js"
integrity="sha384-abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
crossorigin="anonymous"></script>
</head>
<body>
</body>
</html>
6. Content Security Policy (CSP)
CSP is a security mechanism that determines which sources can be loaded on a web page. It helps protect against attacks like XSS.
🚫 Bad Example
The following example shows a page without security measures. In this case, a malicious source could be loaded:
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>My E-commerce App</title>
</head>
<body>
<script src="https://malicious-website.com/malicious.js"></script>
</body>
</html>
✅ Good Example
The security mechanism of CSP only allows scripts loaded from the same origin and a specific trusted website (https://trusted-website.com). This prevents attackers from loading harmful JavaScript code.
In the following example, a page with security measures using a CSP header is shown:
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>My E-commerce App</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://trusted-website.com;">
</head>
<body>
<script
src="https://trusted-website.com/script.js"
integrity="sha384-abc1234567890defghijklmnopqrstuvwxyz"
crossorigin="anonymous">
</script>
</body>
</html>
7. Secure Cookies
Ensuring that cookies are transmitted only over HTTPS provides protection against malicious attacks. It is recommended to use the HttpOnly and SameSite flags together.
🚫 Bad Example
In the following example, no security flags are specified for the cookie:
document.cookie = "sessionId=abc123";
✅ Good Example
The following code shows the necessary flags added to enhance cookie security:
• Secure: The cookie can only be transmitted over HTTPS, preventing a malicious attacker from capturing it over HTTP.
• HttpOnly: This prevents JavaScript from accessing the cookie, providing protection against XSS attacks.
• SameSite=Strict: The cookie can only be used in requests originating from the same site, providing protection against CSRF attacks.
Using these flags enhances the security of cookies, protecting against attacks:
document.cookie = "sessionId=abc123; Secure; HttpOnly; SameSite=Strict";
In conclusion, understanding and preventing these security vulnerabilities is crucial for ensuring user safety and enhancing the success of web applications. If you would like to learn more about security topics, feel free to reach out to me.