This lesson on Security Fundamentals is hands-on and example-driven. Identify and mitigate the OWASP Top 10 web application vulnerabilities across your system architectures. You will apply defense-in-depth patterns including parameterized queries, strict access controls, robust hashing, and strict server-side allowlisting to secure systems against modern attack vectors.
What You'll Be Able To Do
- Enforce the Principle of Least Privilege across role-based access control endpoints
- Implement parameterized database queries to eliminate SQL and command injection vectors
- Configure one-way cryptographic hashing algorithms for user credential storage
- Apply strict network allowlists to mitigate Server-Side Request Forgery vulnerabilities
- Establish comprehensive server-side input validation and security logging routines
Detailed Concept Walkthrough
1. Broken Access Control Mitigation
Access control ensures that authenticated actors only execute actions and access data explicitly permitted by their assigned authorization scope. Flaws occur when authorization checks are missing, client-side only, or decoupled from the underlying data layer.
- Mechanism: Apply the Principle of Least Privilege (PoLP) by default, denying access to all endpoints until an explicit role-based or attribute-based rule permits it. Every request must be independently authenticated and authorized at the API layer regardless of UI-level hiding.
- Under the Hood: Systems maintain role matrices or claim tokens (such as signed JWTs or session objects); the backend middleware intercepts the request lifecycle, verifies the caller identity, and enforces contextual permission checks before executing domain logic.
- Best Practice: Never rely on hidden UI elements, obfuscated URLs, or client-side routing guards for security; always validate permissions directly against the authenticated user context on every backend endpoint invocation.
# Python/Flask decorator enforcing access control checks
from functools import wraps
from flask import request, abort, g
def require_role(required_role):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Ensure user is authenticated and has required privilege
if not g.user or g.user.role != required_role:
abort(403) # Forbidden access
return f(*args, **kwargs)
return decorated_function
return decorator
Key Takeaway: Enforce authorization checks on every single request at the backend layer rather than relying on UI state.
2. Cryptographic and Authentication Safeguards
Cryptographic failures and broken authentication compromise data integrity, user sessions, and sensitive payload confidentiality across network boundaries. Defense requires modern, salted, adaptive hashing functions for data at rest and mandatory transport-layer encryption for data in transit.
- Mechanism: Store credentials exclusively as irreversible, computationally expensive hashes using dedicated algorithms like bcrypt or Argon2 with unique salts. Never implement homegrown cryptographic routines or fast generic hashing algorithms for password storage.
- Under the Hood: Adaptive hashing functions deliberately introduce computational work factors and memory overheads that dramatically slow down brute-force and dictionary attacks on leaked credential tables.
- Best Practice: Terminate all unencrypted HTTP traffic by forcing TLS/HTTPS across every public and inter-service communication channel, while implementing strict rate limiting on all login endpoints.
import bcrypt
# Hash a user password with an automated cryptographic salt
password_bytes = b"SuperSecureP@ss123"
salt = bcrypt.gensalt(rounds=12)
hashed_password = bcrypt.hashpw(password_bytes, salt)
# Securely verify password matches the stored hash
is_valid = bcrypt.checkpw(password_bytes, hashed_password)
Key Takeaway: Never store plaintext credentials or use fast hashes; use adaptive algorithms like bcrypt and force TLS everywhere.
3. Injection Defense via Parameterization
Injection occurs when untrusted input is concatenated directly into command or query strings, causing the interpreter to execute injected payload logic. Treating untrusted input strictly as data rather than executable syntax neutralizes the vulnerability.
- Mechanism: Parameterized queries and prepared statements separate SQL statement structure from user-supplied data variables during query compilation. The database engine compiles the abstract syntax tree first, rendering any injected control syntax inert.
- Under the Hood: The database driver sends the query template and data parameters in separate protocol packets or pre-compiled execution plans, ensuring user input never alters the parser grammar.
- Best Practice: Implement rigorous schema-based validation and sanitization on both client and server layers, but treat server-side parameterized queries as the non-negotiable primary defense.
import sqlite3
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
# Parameterized query separating command logic from user-controlled input
user_input_id = "admin' OR '1'='1"
cursor.execute("SELECT * FROM users WHERE username = ?", (user_input_id,))
user_record = cursor.fetchone()
Key Takeaway: Always use parameterized queries to separate database execution commands from user-supplied data strings.
4. Mitigating Server-Side Request Forgery
Server-Side Request Forgery (SSRF) occurs when a backend server fetches remote resources using untrusted, user-supplied URLs without restricting destination networks. Attackers abuse this to target internal-only microservices, loopback adapters, and cloud metadata APIs.
- Mechanism: Construct strict allowlists containing explicitly permitted domains, protocols, and IP destinations, rather than relying on blocklists that can be bypassed via DNS rebinding or alternative encodings.
- Under the Hood: The server resolves domain names into IP addresses before issuing the transport request, verifying that the destination IP does not land inside private address spaces like RFC 1918 subnets or cloud link-local metadata endpoints (e.g., 169.254.169.254).
- Best Practice: Block access to loopback (127.0.0.1) and internal network segments at the network routing layer while isolating outbound fetch workers within dedicated network zones.
import ipaddress
import socket
from urllib.parse import urlparse
ALLOWED_DOMAINS = {"api.example.com", "static.trusted.org"}
def is_safe_url(target_url):
parsed = urlparse(target_url)
if parsed.scheme not in ("http", "https") or parsed.hostname not in ALLOWED_DOMAINS:
return False
ip = socket.gethostbyname(parsed.hostname)
return not ipaddress.ip_address(ip).is_private
Key Takeaway: Mitigate SSRF by enforcing strict domain allowlists and blocking server fetches to internal and private IP ranges.
Topics Covered in Security Fundamentals
- Broken Access Control (0:00 - 1:30) — Covers authorization flaws allowing users to exceed permitted roles and explains least privilege enforcement.
- Cryptographic Failures (1:30 - 3:00) — Discusses risks associated with unprotected data and establishes requirements for TLS and salted password hashing.
- Injection Attacks (3:00 - 4:30) — Details how malicious input alters execution syntax and demonstrates prepared statements as a countermeasure.
- Insecure Design (4:30 - 5:30) — Examines missing structural controls like rate limiting and audit logging during application architecture.
- Security Misconfiguration (5:30 - 6:30) — Highlights the dangers of default settings, exposed directories, and bloated attack surfaces.
- Outdated Components (6:30 - 7:30) — Explains risks tied to unpatched third-party dependencies and the necessity of regular updates.
- Authentication Failures (7:30 - 8:30) — Covers session management flaws, credential brute-forcing, and proper one-way hashing strategies.
- Software Integrity Failures (8:30 - 9:30) — Focuses on insecure deserialization, untrusted code dependencies, and data verification checks.
- Logging Failures (9:30 - 10:30) — Emphasizes the need for security audit logs and monitoring to detect incident anomalies.
- Server-Side Request Forgery (10:30 - 13:33) — Breaks down server-side fetching vulnerabilities and teaches allowlisting against internal network probing.
System Design Fundamentals Cheat Sheet
-
bcrypt.hashpw(pass, salt)— Generates a salted, slow hash for credential storagebcrypt.hashpw(b"secret", bcrypt.gensalt(12)) -
cursor.execute(sql, params)— Executes parameterized query preventing SQL injection payloadscursor.execute("SELECT * FROM accounts WHERE id = ?", (acc_id,)) -
Role-Based Access Control— Restricts action execution to authorized user rolesif user.role != 'Admin': abort(403) -
URL Destination Allowlist— Restricts outbound HTTP fetches to approved endpointsassert urlparse(url).hostname in {"api.partner.com"} -
IP Range Verification— Blocks requests directed at internal/private networksassert not ipaddress.ip_address(ip).is_private -
Audit Logging— Records security events for anomaly detectionlogger.warning(f"Auth fail: {user_ip}")
Comparison Table
| Vulnerability Type | Primary Root Cause | Mandatory Defense |
|---|---|---|
| Broken Access Control | Missing backend privilege checks | Principle of Least Privilege |
| Injection (SQL/Cmd) | Unsanitized input concatenated in code | Parameterized queries and prepared statements |
| Cryptographic Failures | Weak hashing or unencrypted transport | Use bcrypt/argon2 and enforce HTTPS |
| Server-Side Request Forgery | Server fetching untrusted URLs | Strict domain and IP allowlisting |
Common Pitfalls
- Mistake: Relying entirely on frontend input validation and UI role checks. Avoid: Validating all inputs and enforcing authorization independently on the server for every request.
- Mistake: Using blacklist filters to block malicious URLs and SSRF payloads. Avoid: Implementing strict allowlists of explicitly approved destination domains and protocols.
- Mistake: Storing passwords using fast hashing algorithms like MD5 or SHA256. Avoid: Utilizing computationally expensive, salted hashing algorithms such as bcrypt or Argon2.
- Mistake: Concatenating raw user strings directly into dynamic database query statements. Avoid: Passing input values exclusively via parameterized queries and prepared statements.
FAQs
- Why is client-side validation insufficient for web application security? Attackers can bypass browser checks entirely by sending crafted requests directly to your backend API via tools like cURL. Server-side validation is mandatory.
- Why is allowlisting preferred over blocklisting for SSRF defense? Blocklists are easily bypassed using alternate IP encodings, redirects, or DNS tricks. Allowlists explicitly restrict traffic to known safe domains.
- What differentiates Insecure Design from an implementation bug? Insecure Design involves missing fundamental security mechanisms like rate limits or audit trails in the architecture, regardless of code correctness.