Why Web Application Security Matters
Web applications are among the most targeted systems on the internet today. From small business websites to enterprise platforms, every web application represents a potential entry point for attackers seeking data, resources, or access. Understanding these attacks is not just for security professionals--developers building modern web applications with frameworks like Next.js must understand these threats to build truly secure systems.
The security landscape has evolved significantly, with the OWASP Top 10 2025 representing the most current consensus on critical web application security risks. Modern web development practices, particularly those involving JavaScript and frameworks like React and Next.js, introduce unique security considerations that developers must address at every level of the application stack.
Web applications make tempting targets because they are inherently accessible, visible, and often handle sensitive data. Unlike internal systems hidden behind firewalls, web applications must be reachable to function, making them continuously exposed to potential attackers Codecademy. An attacker compromising a web application can steal personal information, commit fraud, infect visitors with malware, or use the compromised server as a gateway to deeper network access.
OWASP Top 10 2025 Overview
The Open Web Application Security Project (OWASP) Top 10 represents the definitive list of the most critical security risks facing web applications. Updated for 2025, this list reflects the current threat landscape based on data analysis and community input from security professionals worldwide OWASP Top 10:2025.
The 2025 List
The OWASP Top 10 2025 includes several categories that modern JavaScript developers must understand:
- Broken Access Control - Remains the number one vulnerability, emphasizing that even sophisticated applications can fail to properly enforce who can access what resources
- Security Misconfiguration - Insecure defaults and incomplete configurations that leave applications exposed
- Software Supply Chain Failures - New category reflecting third-party dependency risks that characterize modern JavaScript development
- Cryptographic Failures - Previously 'Sensitive Data Exposure,' highlighting the importance of proper encryption and secure data handling
- Injection - SQL, LDAP, and command injection attacks that continue to plague applications
- Insecure Design - Architectural and design flaws that create systemic vulnerabilities
- Authentication Failures - Session and credential management issues that compromise user identities
- Software or Data Integrity Failures - Unsigned updates and deserialization vulnerabilities
- Security Logging and Alerting Failures - Inadequate monitoring and response capabilities
- Mishandling of Exceptional Conditions - Error handling and exception management weaknesses
For developers working with Next.js and similar frameworks, understanding these categories provides a mental checklist for security review. The continued presence of Injection and Cross-Site Scripting vulnerabilities in the top ranks indicates that even with modern frameworks, these fundamental issues remain relevant Rapid7.
Injection Attacks
Injection attacks represent one of the oldest and most dangerous categories of web application vulnerabilities. These attacks occur when untrusted data is sent to an interpreter as part of a command or query, allowing attackers to execute unintended commands or access unauthorized data Codecademy.
SQL Injection
SQL injection attacks target databases by injecting malicious SQL code through user input fields. When applications concatenate user input directly into SQL queries without proper sanitization, attackers can manipulate the query structure to access, modify, or delete database contents Rapid7.
Vulnerable Code Example
// VULNERABLE: Never concatenate user input into queries
const username = req.body.username;
const password = req.body.password;
const query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
An attacker entering ' OR '1'='1 as the username could transform this query to select all users, bypassing authentication entirely. More sophisticated attacks can drop entire tables, extract sensitive data, or execute operating system commands on the database server.
Secure Code Example
// SECURE: Use parameterized queries
const result = await db.query(
'SELECT * FROM users WHERE username = $1 AND password = $2',
[username, password]
);
This approach separates SQL code from data, making injection impossible regardless of the input values.
LDAP Injection
Lightweight Directory Access Protocol (LDAP) injection follows similar principles but targets directory services used for authentication and user management. Applications that construct LDAP queries from user input without proper validation are vulnerable to attackers manipulating directory searches Codecademy.
Prevention Strategies
The primary defense against injection attacks is input validation combined with parameterized queries or stored procedures. Never concatenate user input directly into commands or queries, whether for databases, LDAP directories, or operating system commands. For JavaScript applications using ORMs like Prisma or Sequelize, these tools typically handle parameterization automatically--but developers must use them correctly rather than falling back to raw queries. When building custom API integrations, always use parameterized queries to prevent injection vulnerabilities.
Cross-Site Scripting (XSS)
Cross-site scripting attacks target users rather than the application itself. When a web application includes user input in its output without proper sanitization, attackers can inject malicious JavaScript code that runs in other users' browsers Rapid7.
Types of XSS Attacks
Reflected XSS
Occurs when user input is immediately returned by the application without permanent storage. A search query displayed in the results, for example, could execute injected JavaScript if the input is rendered without escaping. The attack requires tricking a user into clicking a specially crafted link containing the malicious script Codecademy.
Stored XSS
More dangerous because the malicious script is permanently stored on the server and served to all users who view the affected content. Comment sections, forum posts, and user profiles have historically been vectors for stored XSS attacks, with the injected script executing automatically for every visitor Rapid7.
DOM-based XSS
Represents the most modern form, where the attack occurs entirely in the client's DOM manipulation without any server interaction. This makes it particularly relevant for single-page applications and frameworks like React that extensively manipulate the DOM based on user input and application state.
Prevention Strategies
The fundamental defense against XSS is output encoding--converting potentially dangerous characters into their safe equivalents before including user input in HTML, JavaScript, or URL contexts. React's JSX automatically escapes content by default, providing protection when using the framework correctly:
// React automatically escapes this - SAFE
function SafeComponent({ userInput }) {
return <div>{userInput}</div>;
}
// Using dangerouslySetInnerHTML - REQUIRES SANITIZATION
function UnsafeComponent({ userContent }) {
return <div dangerouslySetInnerHTML={{ __html: userContent }} />;
}
Content Security Policy headers provide defense-in-depth by controlling what JavaScript can execute on a page. A properly configured CSP can prevent inline script execution, making many XSS attacks impossible even if malicious code is injected:
// Next.js middleware for CSP headers
const cspPolicy = {
'default-src': ["'self'"],
'script-src': ["'self'", "https://trusted.cdn.com"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", "data:", "https:"],
};
For React and Next.js applications, understanding these protections is essential for building secure user interfaces that resist XSS attacks.
Cross-Site Request Forgery (CSRF)
Cross-site request forgery attacks exploit the trust that web applications have in browsers. When users are logged into an application, their browser automatically includes authentication cookies with every request. CSRF attacks trick browsers into sending forged requests that appear legitimate to the application Rapid7.
How CSRF Attacks Work
The attack scenario typically unfolds like this:
- User logs into their application and remains logged in
- User visits a malicious website while their session is still active
- Malicious page contains hidden HTML or JavaScript that sends a request to the target application--for example, a transfer request
- Browser includes authentication cookies automatically with the forged request
- Target application receives what appears to be a legitimate request from the authenticated user Codecademy
The attack exploits the browser's automatic cookie inclusion behavior. The application has no way to distinguish between a request initiated by the user through the legitimate interface and one triggered secretly by the malicious page.
Prevention Methods
Modern applications prevent CSRF attacks using anti-CSRF tokens--unique, unpredictable values included in forms and validated by the server on each request. Because the malicious page cannot read or guess these tokens, it cannot forge valid requests.
SameSite cookie attributes provide another defense layer by controlling when cookies are sent with cross-site requests:
// Set SameSite cookie attribute for CSRF protection
response.cookies.set('session_id', sessionId, {
sameSite: 'strict', // Cookie only sent with same-site requests
secure: true,
httpOnly: true,
});
The SameSite attribute can be set to 'strict' for maximum protection (no cross-site cookie sending), 'lax' for a balanced approach (cookies sent with top-level navigations and GET requests), or 'none' for explicit opt-in to cross-site sending (requires Secure flag). Double-submit patterns and origin checking provide additional verification mechanisms. For REST APIs, using POST, PUT, DELETE, and PATCH methods instead of GET for state-changing operations provides inherent protection.
Directory Traversal
Directory traversal attacks exploit path traversal vulnerabilities to access files and directories outside the intended application scope. By manipulating file paths in URLs or form inputs, attackers can escape the web root and read or write files throughout the server filesystem Codecademy.
Understanding Path Traversal
The classic traversal sequence uses .. (parent directory) to navigate up the filesystem hierarchy. A URL like /files?file=../../../etc/passwd might allow an attacker to read the system's password file on a vulnerable server. More sophisticated attacks can write files, execute code, or access configuration files containing secrets.
Modern JavaScript applications face these vulnerabilities when processing file paths, whether for static file serving, file uploads, or dynamic content rendering.
Prevention Techniques
Path traversal is prevented by validating and normalizing file paths before use:
const path = require('path');
const fs = require('fs');
function safeReadFile(requestedPath, baseDir) {
// Resolve to absolute path
const absolutePath = path.resolve(requestedPath);
// Verify path is within allowed directory
if (!absolutePath.startsWith(baseDir)) {
throw new Error('Access denied');
}
// Now safe to read
return fs.readFileSync(absolutePath, 'utf8');
}
The path.resolve() function normalizes paths but does not prevent traversal--it simply resolves to the absolute path. Proper prevention requires ensuring the resolved path falls within an allowed directory using startsWith() validation. Allowlist validation for file paths provides another layer of protection. When possible, map user inputs to specific allowed values rather than accepting arbitrary paths. For file uploads in enterprise applications, generate unique filenames and store them in non-public directories, serving files through application code that validates access.
Security Misconfiguration
Security misconfiguration represents one of the most common vulnerability categories, encompassing any insecure default settings, incomplete configurations, or improperly configured permissions OWASP Top 10:2025.
Common Misconfigurations
- Default credentials on administrative interfaces
- Unnecessary services enabled in production
- Verbose error messages exposing stack traces
- Missing security headers
- Overly permissive CORS policies
JavaScript-Specific Considerations
Environment variables in Next.js applications require careful handling. While environment variables prefixed with NEXT_PUBLIC_ are exposed to the browser, other variables should remain server-side only:
# These are exposed to the browser (careful with sensitive data)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_ANALYTICS_ID=GA-XXXXX
# These remain server-only (safer for secrets)
DATABASE_URL=postgresql://...
API_KEY=sk-xxxxx
Misunderstanding this distinction has led to production secrets being exposed in client-side code.
CORS Configuration
CORS misconfiguration frequently exposes APIs to unauthorized access:
// Secure CORS configuration for Next.js API routes
const corsOptions = {
origin: process.env.ALLOWED_ORIGINS?.split(',') || false,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
optionsSuccessStatus: 204,
};
Security Headers
Security headers provide defense-in-depth against various attack categories:
// next.config.js with security headers
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
],
},
];
},
};
Regular configuration audits and automated security scanning help prevent misconfigurations from reaching production. When deploying secure web applications, always review environment configurations, CORS policies, and security headers as part of your deployment checklist.
Broken Access Control
Broken access control remains the number one vulnerability in the OWASP Top 10 2025, reflecting the critical importance of properly enforcing authorization throughout applications OWASP Top 10:2025.
Access Control Concepts
Authentication verifies who a user is--typically through passwords, tokens, or other credentials. Authorization determines what an authenticated user is allowed to do. Broken access control occurs when applications fail to properly enforce these authorization rules, allowing users to access resources or perform actions beyond their permissions.
Common Vulnerabilities
Insecure Direct Object References (IDOR)
Occurs when applications expose internal implementation details like database keys in URLs or API endpoints:
// VULNERABLE: User can change ID to access any profile
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id); // No authorization check
res.json(user);
});
// SECURE: Verify user can access the requested resource
app.get('/api/users/:id', async (req, res) => {
const currentUser = req.user;
const requestedUser = await db.users.findById(req.params.id);
if (currentUser.id !== requestedUser.id && !currentUser.isAdmin) {
return res.status(403).json({ error: 'Access denied' });
}
res.json(requestedUser);
});
Missing Function-Level Access Control
Allows regular users to access administrative endpoints simply because the administrative UI doesn't show those options to them. Attackers can discover these endpoints through API documentation, guesswork, or web crawlers.
Next.js Middleware for Access Control
// middleware.ts for Next.js access control
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token');
const path = request.nextUrl.pathname;
// Require authentication for protected routes
if (path.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Check admin access for admin routes
if (path.startsWith('/admin') && token) {
const userRole = getUserRoleFromToken(token);
if (userRole !== 'admin') {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
return NextResponse.next();
}
Defense strategies include implementing role-based access control (RBAC), enforcing authorization at every endpoint, using indirect references for sensitive resources, and logging access control failures for monitoring. For secure API development, always implement proper authorization checks regardless of how the endpoint might be accessed.
Best Practices for Preventing Attacks
Building secure web applications requires integrating security throughout the development lifecycle rather than treating it as an afterthought.
Development Practices
Security should be considered from the earliest design phases. Threat modeling helps identify potential attack vectors before they're implemented. Code reviews should include security considerations, and security testing should be part of the continuous integration pipeline.
Static analysis tools can identify common vulnerabilities during development:
# Run security audits during development
npm audit --audit-level=high
# Use ESLint security plugins
npm install @eslint-plugin-security
Deployment Considerations
Production deployments require specific security configurations:
- Enable HTTPS everywhere and enforce TLS
- Configure security headers appropriately
- Use environment variables for secrets, never hardcode them
- Implement rate limiting to prevent abuse
- Enable logging and monitoring for attack detection
Ongoing Maintenance
Security is not a one-time effort. Regular updates to dependencies address newly discovered vulnerabilities. Periodic security assessments identify emerging risks. Monitoring for unusual activity helps detect attacks in progress.
Performance and Security Balance
Modern security practices often align with performance goals:
- HTTPS enables HTTP/2 and HTTP/3, often improving performance
- Caching reduces server load while still enabling security headers
- Content Security Policy can prevent resource loading that would slow page rendering
- Proper authentication implementation avoids redundant database queries
The key is thoughtful implementation that provides genuine security without unnecessary overhead. Building secure applications from the start through our full-stack development services ensures security is integrated throughout the codebase rather than bolted on afterward.
Web application attacks continue to evolve as attackers discover new vulnerabilities and techniques. The OWASP Top 10 2025 provides a roadmap for understanding the most critical risks. By understanding how attackers work, developers can build applications that resist attack and protect users' data and privacy.
Frequently Asked Questions
What is the OWASP Top 10?
The OWASP Top 10 is a regularly updated report that outlines the most critical security risks to web applications. It represents a broad consensus among security experts about the most dangerous vulnerabilities and is used as a reference standard for developers and security teams worldwide.
How does SQL injection work?
SQL injection occurs when an attacker inserts malicious SQL code into input fields that are directly concatenated into database queries. This allows the attacker to manipulate the query's logic, potentially accessing, modifying, or deleting database contents they shouldn't be able to reach.
What is the difference between XSS and CSRF?
XSS (Cross-Site Scripting) involves injecting malicious JavaScript that runs in other users' browsers, potentially stealing data or performing actions on their behalf. CSRF (Cross-Site Request Forgery) tricks authenticated users into unknowingly submitting requests to a web application where their browser automatically includes authentication cookies.
How can I prevent XSS in React applications?
React automatically escapes content rendered in JSX, providing protection against most XSS attacks. Avoid using dangerouslySetHTML or similar mechanisms that bypass this protection. Additionally, implement Content Security Policy (CSP) headers to provide defense-in-depth.
What are environment variables in Next.js?
Environment variables in Next.js are prefixed with NEXT_PUBLIC_ to be exposed to the browser (like API URLs), while those without the prefix remain server-only (like database credentials). Never include sensitive secrets in NEXT_PUBLIC_ variables.
How do I secure my API endpoints?
Implement proper authentication to verify user identity, authorization to check permissions for each action, input validation to sanitize all incoming data, and rate limiting to prevent abuse. Use HTTPS exclusively and implement appropriate CORS policies.
Sources
- OWASP Top 10:2025 - Authoritative source for web application security risks
- Codecademy Web Application Attacks - Technical explanations with code examples
- Rapid7 Web Application Vulnerabilities - Enterprise security perspective