Security Principles

Introduction

Security is not an afterthought - it's a fundamental aspect of software development that must be considered from the ground up. Understanding core security principles helps developers build systems that protect data, maintain user privacy, and resist attacks. This reading introduces the foundational concepts that underpin all secure systems.

In this reading, you'll learn about the CIA triad (Confidentiality, Integrity, Availability), defense in depth, the principle of least privilege, and threat modeling. These principles form the bedrock of security thinking and will guide your approach to building secure applications.

Learning Objectives

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

  • Explain the CIA triad and apply it to system design
  • Implement defense in depth strategies
  • Apply the principle of least privilege to code and systems
  • Perform basic threat modeling for applications
  • Identify security considerations in design decisions
  • Understand the security mindset for development

The CIA Triad

The CIA triad represents the three core objectives of information security:

Confidentiality

Confidentiality ensures that information is accessible only to those authorized to access it. This prevents unauthorized disclosure of sensitive data.

Examples:

  • Encrypting data in transit (HTTPS)
  • Encrypting data at rest (database encryption)
  • Access controls and permissions
  • Authentication mechanisms

Threats to Confidentiality:

  • Eavesdropping on network traffic
  • Unauthorized access to files or databases
  • Social engineering attacks
  • Data leaks through improper disposal

Code Example - Poor Confidentiality:

# BAD: Logging sensitive information
def process_payment(card_number, cvv, amount):
    print(f"Processing payment: Card={card_number}, CVV={cvv}, Amount={amount}")
    # Process payment...
    return True

Code Example - Better Confidentiality:

# GOOD: Masking sensitive data in logs
def process_payment(card_number, cvv, amount):
    masked_card = f"****-****-****-{card_number[-4:]}"
    print(f"Processing payment: Card={masked_card}, Amount={amount}")
    # CVV should never be logged or stored
    # Process payment...
    return True

Integrity

Integrity ensures that information is accurate, complete, and has not been modified by unauthorized parties. It guarantees that data remains trustworthy and uncorrupted.

Examples:

  • Checksums and hash functions
  • Digital signatures
  • Version control
  • Input validation

Threats to Integrity:

  • Man-in-the-middle attacks modifying data
  • SQL injection altering database records
  • Unauthorized file modifications
  • Data corruption

Code Example - Poor Integrity:

# BAD: No verification of data integrity
def update_user_balance(user_id, new_balance):
    query = f"UPDATE users SET balance = {new_balance} WHERE id = {user_id}"
    execute_query(query)

Code Example - Better Integrity:

# GOOD: Validation and audit trail
def update_user_balance(user_id, new_balance, authorized_by):
    # Validate inputs
    if not isinstance(new_balance, (int, float)) or new_balance < 0:
        raise ValueError("Invalid balance amount")

    # Use parameterized queries
    query = "UPDATE users SET balance = ? WHERE id = ?"
    execute_query(query, (new_balance, user_id))

    # Maintain audit trail
    log_balance_change(user_id, new_balance, authorized_by, timestamp=now())

Availability

Availability ensures that information and resources are accessible to authorized users when needed. Systems must be resilient and able to recover from failures.

Examples:

  • Redundant systems and backups
  • DDoS protection
  • Failover mechanisms
  • Regular maintenance and updates

Threats to Availability:

  • Denial of Service (DoS) attacks
  • Hardware failures
  • Network outages
  • Resource exhaustion

Code Example - Poor Availability:

# BAD: No rate limiting, vulnerable to DoS
@app.route('/api/search')
def search():
    query = request.args.get('q')
    # Expensive database operation with no limits
    results = database.search_all_fields(query)
    return jsonify(results)

Code Example - Better Availability:

from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(app, key_func=get_remote_address)

# GOOD: Rate limiting protects against abuse
@app.route('/api/search')
@limiter.limit("10 per minute")
def search():
    query = request.args.get('q', '')

    # Validate input length
    if len(query) > 100:
        return jsonify({"error": "Query too long"}), 400

    # Limit result set
    results = database.search_all_fields(query, limit=50)
    return jsonify(results)

Defense in Depth

Defense in depth is a security strategy that employs multiple layers of defense mechanisms. If one layer fails, others still provide protection. Think of it as having multiple locks on your door, an alarm system, and a guard dog - not just relying on a single lock.

Layers of Defense

  1. Physical Security: Data center access controls, locked server rooms
  2. Network Security: Firewalls, network segmentation, IDS/IPS
  3. Host Security: OS hardening, antivirus, patch management
  4. Application Security: Input validation, authentication, authorization
  5. Data Security: Encryption, tokenization, data masking

Practical Example: Web Application Defense

# Layer 1: Network - Firewall rules restrict access to specific IPs/ports

# Layer 2: Application - Authentication required
@app.route('/admin/users')
@require_authentication
@require_role('admin')
def admin_users():
    # Layer 3: Input validation
    page = request.args.get('page', 1, type=int)
    if page < 1 or page > 1000:
        return "Invalid page", 400

    # Layer 4: Parameterized queries prevent SQL injection
    query = "SELECT id, username, email FROM users LIMIT ? OFFSET ?"
    offset = (page - 1) * 20
    users = db.execute(query, (20, offset))

    # Layer 5: Output encoding prevents XSS
    return render_template('admin_users.html', users=users)

# Layer 6: HTTPS encryption for data in transit
# Layer 7: Database encryption for data at rest

Defense in Depth Checklist

  • [ ] Network perimeter security (firewall, WAF)
  • [ ] Secure authentication and session management
  • [ ] Input validation and output encoding
  • [ ] Principle of least privilege for all accounts
  • [ ] Encryption for data in transit and at rest
  • [ ] Regular security updates and patches
  • [ ] Monitoring and logging
  • [ ] Backup and disaster recovery plans

Principle of Least Privilege

The principle of least privilege states that any user, program, or process should have only the minimum privileges necessary to perform its function. This limits the potential damage from accidents or attacks.

Application to Different Levels

User Privileges

# BAD: Everyone gets admin access
def create_user(username, email):
    user = User(username=username, email=email, role='admin')
    db.save(user)
    return user

# GOOD: Default to minimal privileges
def create_user(username, email, role='user'):
    allowed_roles = ['user', 'moderator', 'admin']
    if role not in allowed_roles:
        role = 'user'

    user = User(username=username, email=email, role=role)
    db.save(user)
    return user

Database Permissions

# BAD: Application uses database admin account
DATABASE_CONFIG = {
    'user': 'root',
    'password': 'root_password',
    'database': 'myapp'
}

# GOOD: Application uses limited service account
DATABASE_CONFIG = {
    'user': 'myapp_service',  # Has SELECT, INSERT, UPDATE only
    'password': 'secure_password',
    'database': 'myapp'
}

# The myapp_service account should NOT have:
# - DROP table permissions
# - CREATE user permissions
# - Access to other databases
# - GRANT permissions

File System Permissions

import os

# BAD: Files created with overly permissive access
def save_user_file(user_id, content):
    filepath = f'/var/app/uploads/{user_id}.txt'
    with open(filepath, 'w') as f:
        f.write(content)
    os.chmod(filepath, 0o777)  # Everyone can read, write, execute!

# GOOD: Minimal necessary permissions
def save_user_file(user_id, content):
    filepath = f'/var/app/uploads/{user_id}.txt'

    # Create with secure default permissions
    with open(filepath, 'w') as f:
        f.write(content)

    # Owner can read/write, group can read, others have no access
    os.chmod(filepath, 0o640)

API and Service Access

// BAD: API key with full access to all services
const apiClient = new CloudProvider({
  apiKey: MASTER_API_KEY,  // Can create, delete, modify anything
  permissions: '*'
});

// GOOD: Limited scope API keys
const storageClient = new CloudProvider({
  apiKey: STORAGE_READ_ONLY_KEY,
  permissions: ['storage.objects.get', 'storage.objects.list'],
  resources: ['bucket-name/public/*']
});

Threat Modeling

Threat modeling is the practice of identifying potential security threats to a system and determining countermeasures to prevent or mitigate those threats. It's best done during the design phase but can be applied to existing systems.

STRIDE Framework

STRIDE is a popular threat modeling framework that categorizes threats:

  • Spoofing: Pretending to be someone/something else
  • Tampering: Modifying data or code
  • Repudiation: Denying actions were performed
  • Information Disclosure: Exposing information to unauthorized parties
  • Denial of Service: Making systems unavailable
  • Elevation of Privilege: Gaining unauthorized access levels

Threat Modeling Process

  1. Define the system: Create diagrams showing components, data flows, trust boundaries
  2. Identify threats: Use STRIDE or other frameworks to find potential threats
  3. Determine countermeasures: Decide how to address each threat
  4. Validate: Review and test the mitigations

Practical Example: Login System

Let's threat model a simple login system:

User -> [Internet] -> Load Balancer -> Web Server -> Database

Trust Boundaries:

  • Between user and internet (untrusted -> untrusted)
  • Between internet and load balancer (untrusted -> trusted network)
  • Between web server and database (trusted -> trusted)

Threat Analysis:

Threat TypeThreatCountermeasure
SpoofingAttacker impersonates legitimate userMulti-factor authentication, strong password policy
TamperingAttacker modifies session cookieSign cookies, use secure flags, HTTPS only
RepudiationUser denies performing actionAudit logs with timestamps and IP addresses
Information DisclosurePassword exposed in transitHTTPS encryption, never log passwords
Denial of ServiceBrute force login attemptsRate limiting, account lockout, CAPTCHA
Elevation of PrivilegeAttacker gains admin accessRole-based access control, least privilege

Code Implementation of Countermeasures

from datetime import datetime, timedelta
import hashlib
import secrets

class LoginSystem:
    def __init__(self):
        self.failed_attempts = {}  # Track failed login attempts
        self.locked_accounts = {}   # Track locked accounts

    def login(self, username, password, ip_address):
        # Countermeasure: Denial of Service (rate limiting)
        if self.is_rate_limited(ip_address):
            self.log_security_event('rate_limit_exceeded', username, ip_address)
            return {"success": False, "error": "Too many attempts. Try again later."}

        # Countermeasure: Denial of Service (account lockout)
        if self.is_account_locked(username):
            self.log_security_event('locked_account_attempt', username, ip_address)
            return {"success": False, "error": "Account is locked. Contact support."}

        # Retrieve user from database
        user = self.get_user(username)

        if not user:
            # Countermeasure: Information Disclosure (timing attack prevention)
            # Still verify a dummy password to keep timing consistent
            self.verify_password("dummy_password", "dummy_hash")
            self.record_failed_attempt(username, ip_address)
            return {"success": False, "error": "Invalid credentials"}

        # Countermeasure: Spoofing (strong password verification)
        if not self.verify_password(password, user.password_hash):
            self.record_failed_attempt(username, ip_address)

            # Lock account after 5 failed attempts
            if self.get_failed_attempts(username) >= 5:
                self.lock_account(username)
                self.log_security_event('account_locked', username, ip_address)

            return {"success": False, "error": "Invalid credentials"}

        # Successful login
        self.clear_failed_attempts(username)

        # Countermeasure: Tampering (secure session token)
        session_token = self.create_secure_session(user.id)

        # Countermeasure: Repudiation (audit logging)
        self.log_security_event('successful_login', username, ip_address)

        return {
            "success": True,
            "session_token": session_token,
            "user_id": user.id
        }

    def create_secure_session(self, user_id):
        # Generate cryptographically secure random token
        token = secrets.token_urlsafe(32)
        expiry = datetime.now() + timedelta(hours=2)

        # Store session with user_id mapping
        self.store_session(token, user_id, expiry)

        return token

    def log_security_event(self, event_type, username, ip_address):
        # Countermeasure: Repudiation (comprehensive audit trail)
        log_entry = {
            'timestamp': datetime.now().isoformat(),
            'event': event_type,
            'username': username,
            'ip_address': ip_address,
            'user_agent': self.get_user_agent()
        }
        # Write to secure, append-only log
        self.write_audit_log(log_entry)

Security Mindset

Developing a security mindset means thinking like an attacker while building defenses. Here are key mental models:

Assume Breach

Assume that your system will be breached at some point. Design with this in mind:

  • Limit blast radius of any single compromise
  • Implement detection mechanisms
  • Have incident response plans
  • Regularly test backups and recovery

Zero Trust

Never trust, always verify. Don't assume that being inside the network means being safe:

  • Authenticate and authorize every request
  • Verify all inputs, even from "trusted" sources
  • Encrypt data in transit, even on internal networks

Security is a Process, Not a Product

Security requires ongoing effort:

  • Regular security reviews and audits
  • Continuous monitoring and logging
  • Keeping dependencies updated
  • Security training for all team members
  • Incident response drills

Common Security Anti-Patterns

Security Through Obscurity

# BAD: Relying on secrecy of algorithm
def weak_encrypt(data):
    # "My custom encryption algorithm nobody knows about"
    return ''.join(chr(ord(c) + 3) for c in data)

# GOOD: Use proven cryptographic libraries
from cryptography.fernet import Fernet

def strong_encrypt(data, key):
    f = Fernet(key)
    return f.encrypt(data.encode())

Trusting Client-Side Validation Only

// BAD: Only client-side validation
function submitForm() {
    const price = document.getElementById('price').value;
    if (price > 0) {
        // Attacker can modify this in browser
        fetch('/api/purchase', {
            method: 'POST',
            body: JSON.stringify({ price: price })
        });
    }
}
# GOOD: Always validate on server
@app.route('/api/purchase', methods=['POST'])
def purchase():
    data = request.get_json()

    # Never trust client input
    price = data.get('price')

    if not isinstance(price, (int, float)) or price <= 0:
        return {"error": "Invalid price"}, 400

    # Look up actual price from database
    actual_price = get_product_price(data.get('product_id'))

    if price != actual_price:
        return {"error": "Price mismatch"}, 400

    process_purchase(price)
    return {"success": True}

Exercises

Basic Exercises

  1. CIA Triad Analysis: For each scenario, identify which aspect of the CIA triad is violated:

    • A company's website is taken offline by a DDoS attack
    • An employee emails confidential customer data to their personal account
    • A hacker modifies price information on an e-commerce site
  2. Least Privilege: Review this code and identify violations of least privilege:

def delete_user_comment(comment_id, user_id):
    # Any user can delete any comment
    query = f"DELETE FROM comments WHERE id = {comment_id}"
    execute_query(query)
  1. Defense in Depth: List at least 5 layers of defense for a web application that handles payment information.

Intermediate Exercises

  1. Threat Modeling: Create a STRIDE analysis for a password reset feature that:

    • Sends a reset link via email
    • Link expires after 1 hour
    • User clicks link and enters new password
  2. Secure File Upload: Write a secure file upload function that:

    • Validates file type and size
    • Prevents path traversal attacks
    • Stores files with minimal permissions
    • Generates unique, non-guessable filenames
  3. Audit Logging: Implement an audit logging system for user actions that:

    • Records who did what and when
    • Cannot be tampered with after creation
    • Doesn't leak sensitive information

Advanced Exercises

  1. Security Architecture: Design a secure architecture for a multi-tenant SaaS application where:

    • Multiple companies share the same infrastructure
    • Each company's data must be isolated
    • Administrative access must be carefully controlled
    • Apply all principles learned in this reading
  2. Incident Response: Create an incident response plan for a scenario where:

    • You discover that an API key with database access has been leaked to GitHub
    • The key has been public for 3 days
    • You're unsure if it has been used maliciously
    • What steps do you take, in what order?
  3. Security Review: Conduct a security review of this code and identify at least 5 security issues:

import sqlite3
import pickle

class UserManager:
    def __init__(self):
        self.db = sqlite3.connect('/tmp/users.db')
        self.admin_key = "admin123"

    def get_user(self, username):
        query = f"SELECT * FROM users WHERE username = '{username}'"
        result = self.db.execute(query).fetchone()
        return result

    def save_session(self, session_data):
        # Save session to file
        with open('/var/sessions/' + session_data['user_id'], 'wb') as f:
            pickle.dump(session_data, f)

    def is_admin(self, request):
        return request.cookies.get('admin') == 'true'

    def delete_user(self, user_id, admin_key):
        if admin_key == self.admin_key:
            query = f"DELETE FROM users WHERE id = {user_id}"
            self.db.execute(query)
            self.db.commit()

Summary

Security is built on fundamental principles that guide all design and implementation decisions:

  1. CIA Triad: Every security decision should consider Confidentiality, Integrity, and Availability. These three pillars ensure that data is protected, accurate, and accessible.

  2. Defense in Depth: Never rely on a single security control. Multiple layers of defense ensure that if one fails, others still protect the system.

  3. Least Privilege: Grant only the minimum permissions necessary. This applies to users, applications, services, and processes.

  4. Threat Modeling: Proactively identify threats using frameworks like STRIDE and design countermeasures before threats become real attacks.

  5. Security Mindset: Think like an attacker, assume breach, trust nothing, and treat security as an ongoing process.

These principles aren't just theoretical - they have practical applications in every line of code you write and every system you design. Security isn't a feature you add at the end; it's a fundamental quality that must be built in from the start.

As you continue to the next readings, you'll see how these principles apply to specific areas like cryptography, authentication, and secure coding practices. The foundation you've built here will help you understand not just what to do, but why it matters.

Key Takeaways

  • Security requires a shift in thinking from "how do I make this work?" to "how could this be attacked?"
  • No single security measure is sufficient; layered defenses are essential
  • The principle of least privilege prevents small mistakes from becoming catastrophic breaches
  • Threat modeling helps identify and address security issues before they're exploited
  • Security is everyone's responsibility, not just the security team's

Further Reading

  • "The Art of Software Security Assessment" by Mark Dowd
  • "Threat Modeling: Designing for Security" by Adam Shostack
  • OWASP Top 10 Project: https://owasp.org/www-project-top-ten/
  • Microsoft STRIDE Threat Modeling: https://docs.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats

Next Steps

Continue to 02-cryptography.md to learn about the cryptographic tools and techniques that protect data confidentiality and integrity.