Web Security Fundamentals - OWASP Top 10 and Practical Defenses

2025.12.02

The Importance of Web Security

Web applications are constantly exposed to attack risks. OWASP (Open Web Application Security Project) publishes the Top 10, summarizing the most critical security risks.

RankVulnerabilityDescription
1Broken Access ControlAccess to unauthorized resources
2Cryptographic FailuresWeak encryption, exposed data
3InjectionSQL, NoSQL, OS command injection
4Insecure DesignMissing security controls in design
5Security MisconfigurationDefault configs, exposed errors
6Vulnerable and Outdated ComponentsUnpatched dependencies
7Identification and Authentication FailuresWeak auth mechanisms
8Software and Data Integrity FailuresUnsigned updates, CI/CD issues
9Security Logging and Monitoring FailuresInsufficient visibility
10Server-Side Request Forgery (SSRF)Server-initiated malicious requests

XSS (Cross-Site Scripting)

Types of Attacks

TypeDescriptionExample
Reflected XSSScript in URL reflected in response/search?q=<script>alert('XSS')</script> reflected in HTML output
Stored XSSAttack code persists in databaseComment with script → Stored in DB → Shown to other users
DOM-based XSSClient-side JS inserts URL params into DOMJavaScript reads URL and writes to innerHTML

Defenses

// 1. HTML Escaping
function escapeHtml(text: string): string {
  const map: Record<string, string> = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;',
  };
  return text.replace(/[&<>"']/g, (m) => map[m]);
}

// 2. Use template engine auto-escaping
// React: JSX auto-escapes
<div>{userInput}</div> // Safe

// Dangerous: Avoid dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // Dangerous!

// 3. Content Security Policy (CSP)
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: `
      default-src 'self';
      script-src 'self' 'unsafe-eval';
      style-src 'self' 'unsafe-inline';
      img-src 'self' data: https:;
      font-src 'self';
      connect-src 'self' https://api.example.com;
    `.replace(/\n/g, ''),
  },
];

// 4. Use HttpOnly Cookies
res.cookie('session', token, {
  httpOnly: true,  // Not accessible from JavaScript
  secure: true,    // HTTPS required
  sameSite: 'strict',
  maxAge: 3600000,
});

CSRF (Cross-Site Request Forgery)

flowchart TB
    subgraph Step1["1. User logs into legitimate site"]
        U1["User"] -->|"Login"| B["Bank Site<br/>(Session)"]
    end

    subgraph Step2["2. User visits attacker's trap site"]
        U2["User"] --> A["Attacker Site"]
        A -->|"Hidden form auto-submit"| Form["&lt;form action='bank.com/transfer'&gt;<br/>to=attacker, amount=10000"]
    end

    subgraph Step3["3. Attack execution"]
        Form -->|"Request with user's session"| B
    end

Defenses

// 1. CSRF Token
import { randomBytes } from 'crypto';

function generateCsrfToken(): string {
  return randomBytes(32).toString('hex');
}

// Store token in session
app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = generateCsrfToken();
  }
  res.locals.csrfToken = req.session.csrfToken;
  next();
});

// Include token in form
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value={csrfToken} />
  ...
</form>

// Request validation
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.session.csrfToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  // Process request
});

// 2. SameSite Cookie
res.cookie('session', token, {
  sameSite: 'strict', // or 'lax'
  httpOnly: true,
  secure: true,
});

// 3. Origin Header Validation
app.use((req, res, next) => {
  const origin = req.headers.origin;
  const allowedOrigins = ['https://myapp.com'];

  if (req.method !== 'GET' && !allowedOrigins.includes(origin)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
});

SQL Injection

Vulnerable Code

// Dangerous example
app.get('/users', async (req, res) => {
  const { name } = req.query;
  // SQL Injection possible!
  const result = await db.query(`SELECT * FROM users WHERE name = '${name}'`);
  res.json(result);
});

// Attack example: ?name=' OR '1'='1
// SELECT * FROM users WHERE name = '' OR '1'='1'
// → All users retrieved

Defenses

// 1. Prepared Statements (Parameterized Queries)
app.get('/users', async (req, res) => {
  const { name } = req.query;
  const result = await db.query(
    'SELECT * FROM users WHERE name = $1',
    [name]
  );
  res.json(result);
});

// 2. Use an ORM
// Prisma
const users = await prisma.user.findMany({
  where: { name },
});

// Drizzle
const users = await db.select().from(usersTable).where(eq(usersTable.name, name));

// 3. Input Validation
import { z } from 'zod';

const QuerySchema = z.object({
  name: z.string().max(100).regex(/^[a-zA-Z0-9\s]+$/),
});

app.get('/users', async (req, res) => {
  const result = QuerySchema.safeParse(req.query);
  if (!result.success) {
    return res.status(400).json({ error: 'Invalid input' });
  }
  // ...
});

Authentication Security

// Secure Password Hashing
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// Secure JWT Usage
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_EXPIRES_IN = '15m';
const REFRESH_TOKEN_EXPIRES_IN = '7d';

function generateTokens(userId: string) {
  const accessToken = jwt.sign({ userId }, JWT_SECRET, {
    expiresIn: JWT_EXPIRES_IN,
    algorithm: 'HS256',
  });

  const refreshToken = jwt.sign({ userId, type: 'refresh' }, JWT_SECRET, {
    expiresIn: REFRESH_TOKEN_EXPIRES_IN,
    algorithm: 'HS256',
  });

  return { accessToken, refreshToken };
}

// Brute Force Prevention
import rateLimit from 'express-rate-limit';

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts max
  message: { error: 'Too many login attempts' },
  standardHeaders: true,
  legacyHeaders: false,
});

app.post('/login', loginLimiter, async (req, res) => {
  // Login processing
});

Security Headers

// middleware/security.ts
import helmet from 'helmet';

app.use(helmet());

// Or individual settings
app.use((req, res, next) => {
  // XSS filter
  res.setHeader('X-XSS-Protection', '1; mode=block');

  // Prevent Content-Type sniffing
  res.setHeader('X-Content-Type-Options', 'nosniff');

  // Prevent Clickjacking
  res.setHeader('X-Frame-Options', 'DENY');

  // Force HTTPS
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

  // Referrer Policy
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Permissions Policy
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

  next();
});

Security Checklist

CategoryCheck Item
AuthenticationPassword hashing (bcrypt/Argon2)
AuthenticationSession fixation protection
AuthenticationMFA support
InputValidate all inputs
InputUse parameterized SQL queries
OutputHTML escaping
CommunicationHTTPS required
CookieHttpOnly, Secure, SameSite
HeadersCSP, HSTS configured
DependenciesVulnerability scanning (npm audit)
← Back to list